Skip to content

Commit db0b8ac

Browse files
Added support for download from HTTPS only
1 parent 6571f44 commit db0b8ac

File tree

8 files changed

+115
-40
lines changed

8 files changed

+115
-40
lines changed

Diff for: .github/actions/build-native-binary/action.yaml

+30-1
Original file line numberDiff line numberDiff line change
@@ -100,11 +100,38 @@ runs:
100100
make install
101101
shell: bash
102102

103+
- name: Generate SSL key and certificate
104+
run: |
105+
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
106+
-keyout /etc/ssl/private/selfupdateagent.key \
107+
-out /etc/ssl/certs/selfupdateagent.crt \
108+
-config utest/sua-certificate.config
109+
shell: bash
110+
111+
- name: Install and configure apache2
112+
run: |
113+
sudo apt install apache2
114+
sudo a2enmod ssl
115+
sudo cp utest/sua-apache2.conf /etc/apache2/sites-available/sua-apache2.conf
116+
sudo a2ensite sua-apache2
117+
sudo service apache2 start
118+
shell: bash
119+
120+
- name: Generate fake bundle
121+
run: |
122+
head -c 1 < /dev/urandom | sudo tee /var/www/html/bundle
123+
shell: bash
124+
125+
- name: Create /data/selfupdates
126+
run: |
127+
sudo mkdir -p /data/selfupdates
128+
sudo chown `whoami`:`whoami` /data/selfupdates
129+
shell: bash
130+
103131
- name: Run unit tests for amd64
104132
if: ${{ inputs.arch == 'amd64' }}
105133
run: |
106134
cd dist_${{ inputs.arch }}/utest
107-
file TestSelfUpdateAgent
108135
./TestSelfUpdateAgent > ../../unit_tests_report_${{ inputs.arch }}.txt
109136
shell: bash
110137

@@ -116,6 +143,8 @@ runs:
116143
distro: ubuntu_latest
117144
dockerRunArgs: |
118145
--volume "${PWD}:/sua"
146+
--volume "/data/selfupdates:/data/selfupdates"
147+
--volume "/etc/ssl/certs:/etc/ssl/certs"
119148
--net=host
120149
shell: /bin/sh
121150
install: |

Diff for: src/Context.h

+5-4
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@ namespace sua {
5555
std::shared_ptr<IMqttMessagingProtocol> messagingProtocol;
5656
std::shared_ptr<IMqttProcessor> mqttProcessor;
5757
std::shared_ptr<IBundleChecker> bundleChecker;
58-
std::string updatesDirectory = "/data/selfupdates";
59-
std::string tempFileName = "/temp_file";
60-
bool downloadMode = true;
61-
bool fallbackMode = false;
58+
std::string updatesDirectory = "/data/selfupdates";
59+
std::string tempFileName = "/temp_file";
60+
std::string certificateFileName = "/etc/ssl/certs/selfupdateagent.crt";
61+
bool downloadMode = true;
62+
bool fallbackMode = false;
6263

6364
DesiredState desiredState;
6465
CurrentState currentState;

Diff for: src/Download/Downloader.cpp

+9-5
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
// SPDX-License-Identifier: Apache-2.0
1616

1717
#include "Download/Downloader.h"
18+
#include "Context.h"
1819
#include "Logger.h"
1920
#include "Patterns/Dispatcher.h"
2021
#include "TechCodes.h"
@@ -84,7 +85,7 @@ namespace {
8485
return written;
8586
}
8687

87-
sua::TechCode download(const char* url)
88+
sua::TechCode download(sua::Context & context, const char* url)
8889
{
8990
CURLcode gres = curl_global_init(CURL_GLOBAL_ALL);
9091
if(gres != 0) {
@@ -118,6 +119,8 @@ namespace {
118119

119120
curl_easy_setopt(easy_handle, CURLOPT_URL, url);
120121
curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &response_code);
122+
curl_easy_setopt(easy_handle, CURLOPT_CAINFO, context.certificateFileName.c_str());
123+
curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYPEER, 1);
121124
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, write_data);
122125
curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, fp);
123126
curl_easy_setopt(easy_handle, CURLOPT_FOLLOWLOCATION, 1L);
@@ -149,16 +152,17 @@ namespace sua {
149152

150153
const std::string Downloader::EVENT_DOWNLOADING = "Downloader/Downloading";
151154

152-
Downloader::Downloader(const std::string& download_dir, const std::string& filename)
155+
Downloader::Downloader(Context & context)
156+
: _context(context)
153157
{
154-
const std::string filepath = download_dir + filename;
155-
strncpy(FILE_DIR, download_dir.c_str(), FILENAME_MAX - 1);
158+
const std::string filepath = _context.updatesDirectory + _context.tempFileName;
159+
strncpy(FILE_DIR, _context.updatesDirectory.c_str(), FILENAME_MAX - 1);
156160
strncpy(FILE_PATH, filepath.c_str(), FILENAME_MAX - 1);
157161
}
158162

159163
TechCode Downloader::start(const std::string & input)
160164
{
161-
return download(input.c_str());
165+
return download(_context, input.c_str());
162166
}
163167

164168
} // namespace sua

Diff for: src/Download/Downloader.h

+4-1
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ namespace sua {
2424
class Downloader : public IDownloader
2525
{
2626
public:
27-
Downloader(const std::string& download_dir, const std::string& filename);
27+
Downloader(class Context & context);
2828

2929
static const std::string EVENT_DOWNLOADING;
3030

3131
TechCode start(const std::string & input) override;
32+
33+
private:
34+
class Context & _context;
3235
};
3336

3437
} // namespace sua

Diff for: src/main.cpp

+22-8
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ SUA_SERVER sets and overrides MQTT server address to connect
4949
-p, --path path where downloaded update bundles will be stored (default is '/data/selfupdates')
5050
-s, --server MQTT broker server to connect (default is 'tcp://mosquitto:1883')
5151
(has precedence over SUA_SERVER environment variable)
52+
-c, --ca path to certificate to verify connection with bundle server (default is '/etc/ssl/certs/selfupdateagent.crt')
5253
-v, --version display version (Git hash and build number) used to build SUA and exit
5354
)";
5455

@@ -57,6 +58,7 @@ int main(int argc, char* argv[])
5758
std::string server{"tcp://mosquitto:1883"};
5859
std::string installer{"download"};
5960
std::string hostPathToSelfupdateDir{"/data/selfupdates"};
61+
std::string pathToCertificate{"/etc/ssl/certs/selfupdateagent.crt"};
6062

6163
const char * env_server = std::getenv("SUA_SERVER");
6264
if(env_server) {
@@ -114,6 +116,16 @@ int main(int argc, char* argv[])
114116
continue;
115117
}
116118

119+
if(("-c" == opt) || ("--ca" == opt)) {
120+
if (argValue.empty()) {
121+
std::cout << "Missing path to .crt for '" << opt << "'" << std::endl
122+
<< help_string << std::endl;
123+
return 1;
124+
}
125+
pathToCertificate = argValue;
126+
continue;
127+
}
128+
117129
std::cout << "Invalid argument '" << opt << "'" << std::endl
118130
<< help_string << std::endl;
119131
return 1;
@@ -182,20 +194,22 @@ int main(int argc, char* argv[])
182194

183195
sua::SelfUpdateAgent sua;
184196
sua::Context & ctx = sua.context();
185-
ctx.stateMachine = std::make_shared<sua::FSM>(sua.context());
186-
ctx.installerAgent = installerAgent;
187-
ctx.downloadMode = downloadMode;
188-
ctx.downloaderAgent = std::make_shared<sua::Downloader>(hostPathToSelfupdateDir, ctx.tempFileName);
189-
ctx.messagingProtocol = std::make_shared<sua::MqttMessagingProtocolJSON>();
190-
ctx.updatesDirectory = hostPathToSelfupdateDir;
191-
ctx.bundleChecker = std::make_shared<sua::BundleChecker>();
192-
ctx.mqttProcessor = std::make_shared<sua::MqttProcessor>(ctx);
197+
ctx.certificateFileName = pathToCertificate;
198+
ctx.updatesDirectory = hostPathToSelfupdateDir;
199+
ctx.stateMachine = std::make_shared<sua::FSM>(sua.context());
200+
ctx.installerAgent = installerAgent;
201+
ctx.downloadMode = downloadMode;
202+
ctx.downloaderAgent = std::make_shared<sua::Downloader>(ctx);
203+
ctx.messagingProtocol = std::make_shared<sua::MqttMessagingProtocolJSON>();
204+
ctx.bundleChecker = std::make_shared<sua::BundleChecker>();
205+
ctx.mqttProcessor = std::make_shared<sua::MqttProcessor>(ctx);
193206

194207
sua::Logger::info("SUA build number : '{}'", SUA_BUILD_NUMBER );
195208
sua::Logger::info("SUA commit hash : '{}'", SUA_COMMIT_HASH );
196209
sua::Logger::info("MQTT broker address : '{}://{}:{}'", transport, conf.brokerHost, conf.brokerPort);
197210
sua::Logger::info("Install method : '{}'", installer);
198211
sua::Logger::info("Updates directory : '{}'", ctx.updatesDirectory);
212+
sua::Logger::info("CA certificate : '{}'", ctx.certificateFileName);
199213

200214
sua.init();
201215
sua.start(conf);

Diff for: utest/TestSelfUpdateScenarios.cpp

+16-21
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,10 @@ using ::testing::_;
1515
#include "Mqtt/MqttMessagingProtocolJSON.h"
1616
#include "Mqtt/MqttMessage.h"
1717
#include "Install/DummyRaucInstaller.h"
18+
#include "Download/Downloader.h"
1819

19-
#include "MockFSM.h"
2020
#include "MockDownloader.h"
21+
#include "MockFSM.h"
2122
#include "MockMqttProcessor.h"
2223

2324
using namespace std::chrono_literals;
@@ -118,7 +119,7 @@ namespace {
118119
"config": [
119120
{
120121
"key": "image",
121-
"value": "{}"
122+
"value": "https://127.0.0.1/bundle"
122123
}
123124
]
124125
}
@@ -177,8 +178,7 @@ namespace {
177178
sua::Logger::instance().init();
178179
sua::Logger::instance().setLogLevel(sua::Logger::Level::All);
179180

180-
downloader = std::make_shared<MockDownloader>();
181-
ctx().downloaderAgent = downloader;
181+
ctx().downloaderAgent = std::make_shared<sua::Downloader>(ctx());
182182

183183
fsm = std::make_shared<MockFSM>(sua.context(), visitedStates);
184184
ctx().stateMachine = fsm;
@@ -191,14 +191,13 @@ namespace {
191191

192192
ctx().messagingProtocol = std::make_shared<sua::MqttMessagingProtocolJSON>();
193193
ctx().bundleChecker = std::make_shared<sua::BundleChecker>();
194-
}
194+
}
195195

196196
sua::SelfUpdateAgent sua;
197197

198198
std::shared_ptr<MockFSM> fsm;
199199
std::shared_ptr<MockMqttProcessor> mqttProcessor;
200200
std::shared_ptr<MockRaucInstaller> installerAgent;
201-
std::shared_ptr<MockDownloader> downloader;
202201

203202
std::string testBrokerHost = "localhost";
204203
int testBrokerPort = 1883;
@@ -280,11 +279,14 @@ namespace {
280279
expectedStates = {"Uninitialized", "Connected", "Downloading", "Failed"};
281280
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::DownloadFailed};
282281

283-
EXPECT_CALL(*downloader, start(_)).WillOnce(Return(sua::TechCode::DownloadFailed));
282+
auto downloader = std::make_shared<MockDownloader>();
283+
ctx().downloaderAgent = downloader;
284284

285285
sua.init();
286286
start();
287287

288+
EXPECT_CALL(*downloader, start(_)).WillOnce(Return(sua::TechCode::DownloadFailed));
289+
288290
triggerIdentify(BUNDLE_11, "1.1");
289291
trigger(COMMAND_DOWNLOAD);
290292

@@ -295,9 +297,7 @@ namespace {
295297
TEST_F(TestSelfUpdateScenarios, downloadedBundleVersionMismatchWithSpec_endsInFailedState)
296298
{
297299
expectedStates = {"Uninitialized", "Connected", "Downloading", "Installing", "Failed"};
298-
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloaded, M::VersionChecking, M::Rejected};
299-
300-
EXPECT_CALL(*downloader, start(_)).WillOnce(Return(sua::TechCode::OK));
300+
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloading, M::Downloaded, M::VersionChecking, M::Rejected};
301301

302302
sua.init();
303303
start();
@@ -313,9 +313,8 @@ namespace {
313313
TEST_F(TestSelfUpdateScenarios, downloadSucceeds_waitsForInstallCommand)
314314
{
315315
expectedStates = {"Uninitialized", "Connected", "Downloading", "Installing"};
316-
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloaded};
316+
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloading, M::Downloaded};
317317

318-
EXPECT_CALL(*downloader, start(_)).WillOnce(Return(sua::TechCode::OK));
319318
installerAgent->bundleUnderTest = BUNDLE_11;
320319

321320
sua.init();
@@ -331,9 +330,8 @@ namespace {
331330
TEST_F(TestSelfUpdateScenarios, installSetupFails_endsInIdleState)
332331
{
333332
expectedStates = {"Uninitialized", "Connected", "Downloading", "Installing", "Failed"};
334-
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloaded, M::VersionChecking, M::InstallFailed};
333+
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloading, M::Downloaded, M::VersionChecking, M::InstallFailed};
335334

336-
EXPECT_CALL(*downloader, start(_)).WillOnce(Return(sua::TechCode::OK));
337335
installerAgent->bundleUnderTest = BUNDLE_RAUC_SETUP_FAILS;
338336

339337
sua.init();
@@ -350,11 +348,10 @@ namespace {
350348
TEST_F(TestSelfUpdateScenarios, installSucceeds_waitsInInstalledState)
351349
{
352350
expectedStates = {"Uninitialized", "Connected", "Downloading", "Installing", "Installed"};
353-
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloaded,
351+
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloading, M::Downloaded,
354352
M::VersionChecking, M::Installing, M::Installing, M::Installing, M::Installing, M::Installed,
355353
M::CurrentState};
356354

357-
EXPECT_CALL(*downloader, start(_)).WillOnce(Return(sua::TechCode::OK));
358355
EXPECT_CALL(*installerAgent, succeeded()).WillOnce(Return(true));
359356
installerAgent->bundleUnderTest = BUNDLE_11;
360357

@@ -372,10 +369,9 @@ namespace {
372369
TEST_F(TestSelfUpdateScenarios, installFails_endsInFailedState)
373370
{
374371
expectedStates = {"Uninitialized", "Connected", "Downloading", "Installing", "Failed"};
375-
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloaded, M::VersionChecking,
376-
M::Installing, M::Installing, M::Installing, M::Installing, M::InstallFailed};
372+
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloading, M::Downloaded,
373+
M::VersionChecking, M::Installing, M::Installing, M::Installing, M::Installing, M::InstallFailed};
377374

378-
EXPECT_CALL(*downloader, start(_)).WillOnce(Return(sua::TechCode::OK));
379375
EXPECT_CALL(*installerAgent, succeeded()).WillOnce(Return(false));
380376
installerAgent->bundleUnderTest = BUNDLE_11;
381377

@@ -393,11 +389,10 @@ namespace {
393389
TEST_F(TestSelfUpdateScenarios, activationSucceeds_endsInIdleState)
394390
{
395391
expectedStates = {"Uninitialized", "Connected", "Downloading", "Installing", "Installed", "Activating", "Cleaning", "Idle"};
396-
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloaded, M::VersionChecking,
392+
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloading, M::Downloaded, M::VersionChecking,
397393
M::Installing, M::Installing, M::Installing, M::Installing, M::Installed, M::CurrentState,
398394
M::Activating, M::Activated, M::Cleaned, M::Complete};
399395

400-
EXPECT_CALL(*downloader, start(_)).WillOnce(Return(sua::TechCode::OK));
401396
EXPECT_CALL(*installerAgent, succeeded()).WillOnce(Return(true));
402397
installerAgent->bundleUnderTest = BUNDLE_11;
403398

Diff for: utest/sua-apache2.conf

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<VirtualHost *:443>
2+
ServerName 127.0.0.1
3+
DocumentRoot /var/www/html
4+
SSLEngine on
5+
SSLCertificateFile /etc/ssl/certs/selfupdateagent.crt
6+
SSLCertificateKeyFile /etc/ssl/private/selfupdateagent.key
7+
</VirtualHost>

Diff for: utest/sua-certificate.config

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[req]
2+
default_bits = 2048
3+
distinguished_name = req_distinguished_name
4+
req_extensions = req_ext
5+
x509_extensions = v3_req
6+
prompt = no
7+
8+
[req_distinguished_name]
9+
countryName = XX
10+
stateOrProvinceName = N/A
11+
localityName = N/A
12+
organizationName = Self-signed certificate
13+
commonName = 127.0.0.1
14+
15+
[req_ext]
16+
subjectAltName = @alt_names
17+
18+
[v3_req]
19+
subjectAltName = @alt_names
20+
21+
[alt_names]
22+
IP.1 = 127.0.0.1

0 commit comments

Comments
 (0)