Skip to content

Commit

Permalink
Added support for download from HTTPS only
Browse files Browse the repository at this point in the history
  • Loading branch information
OleksandrChaika committed Jun 27, 2023
1 parent 6571f44 commit db0b8ac
Show file tree
Hide file tree
Showing 8 changed files with 115 additions and 40 deletions.
31 changes: 30 additions & 1 deletion .github/actions/build-native-binary/action.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,11 +100,38 @@ runs:
make install
shell: bash

- name: Generate SSL key and certificate
run: |
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/ssl/private/selfupdateagent.key \
-out /etc/ssl/certs/selfupdateagent.crt \
-config utest/sua-certificate.config
shell: bash

- name: Install and configure apache2
run: |
sudo apt install apache2
sudo a2enmod ssl
sudo cp utest/sua-apache2.conf /etc/apache2/sites-available/sua-apache2.conf
sudo a2ensite sua-apache2
sudo service apache2 start
shell: bash

- name: Generate fake bundle
run: |
head -c 1 < /dev/urandom | sudo tee /var/www/html/bundle
shell: bash

- name: Create /data/selfupdates
run: |
sudo mkdir -p /data/selfupdates
sudo chown `whoami`:`whoami` /data/selfupdates
shell: bash

- name: Run unit tests for amd64
if: ${{ inputs.arch == 'amd64' }}
run: |
cd dist_${{ inputs.arch }}/utest
file TestSelfUpdateAgent
./TestSelfUpdateAgent > ../../unit_tests_report_${{ inputs.arch }}.txt
shell: bash

Expand All @@ -116,6 +143,8 @@ runs:
distro: ubuntu_latest
dockerRunArgs: |
--volume "${PWD}:/sua"
--volume "/data/selfupdates:/data/selfupdates"
--volume "/etc/ssl/certs:/etc/ssl/certs"
--net=host
shell: /bin/sh
install: |
Expand Down
9 changes: 5 additions & 4 deletions src/Context.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,11 @@ namespace sua {
std::shared_ptr<IMqttMessagingProtocol> messagingProtocol;
std::shared_ptr<IMqttProcessor> mqttProcessor;
std::shared_ptr<IBundleChecker> bundleChecker;
std::string updatesDirectory = "/data/selfupdates";
std::string tempFileName = "/temp_file";
bool downloadMode = true;
bool fallbackMode = false;
std::string updatesDirectory = "/data/selfupdates";
std::string tempFileName = "/temp_file";
std::string certificateFileName = "/etc/ssl/certs/selfupdateagent.crt";
bool downloadMode = true;
bool fallbackMode = false;

DesiredState desiredState;
CurrentState currentState;
Expand Down
14 changes: 9 additions & 5 deletions src/Download/Downloader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
// SPDX-License-Identifier: Apache-2.0

#include "Download/Downloader.h"
#include "Context.h"
#include "Logger.h"
#include "Patterns/Dispatcher.h"
#include "TechCodes.h"
Expand Down Expand Up @@ -84,7 +85,7 @@ namespace {
return written;
}

sua::TechCode download(const char* url)
sua::TechCode download(sua::Context & context, const char* url)
{
CURLcode gres = curl_global_init(CURL_GLOBAL_ALL);
if(gres != 0) {
Expand Down Expand Up @@ -118,6 +119,8 @@ namespace {

curl_easy_setopt(easy_handle, CURLOPT_URL, url);
curl_easy_getinfo(easy_handle, CURLINFO_RESPONSE_CODE, &response_code);
curl_easy_setopt(easy_handle, CURLOPT_CAINFO, context.certificateFileName.c_str());
curl_easy_setopt(easy_handle, CURLOPT_SSL_VERIFYPEER, 1);
curl_easy_setopt(easy_handle, CURLOPT_WRITEFUNCTION, write_data);
curl_easy_setopt(easy_handle, CURLOPT_WRITEDATA, fp);
curl_easy_setopt(easy_handle, CURLOPT_FOLLOWLOCATION, 1L);
Expand Down Expand Up @@ -149,16 +152,17 @@ namespace sua {

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

Downloader::Downloader(const std::string& download_dir, const std::string& filename)
Downloader::Downloader(Context & context)
: _context(context)
{
const std::string filepath = download_dir + filename;
strncpy(FILE_DIR, download_dir.c_str(), FILENAME_MAX - 1);
const std::string filepath = _context.updatesDirectory + _context.tempFileName;
strncpy(FILE_DIR, _context.updatesDirectory.c_str(), FILENAME_MAX - 1);
strncpy(FILE_PATH, filepath.c_str(), FILENAME_MAX - 1);
}

TechCode Downloader::start(const std::string & input)
{
return download(input.c_str());
return download(_context, input.c_str());
}

} // namespace sua
5 changes: 4 additions & 1 deletion src/Download/Downloader.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,14 @@ namespace sua {
class Downloader : public IDownloader
{
public:
Downloader(const std::string& download_dir, const std::string& filename);
Downloader(class Context & context);

static const std::string EVENT_DOWNLOADING;

TechCode start(const std::string & input) override;

private:
class Context & _context;
};

} // namespace sua
Expand Down
30 changes: 22 additions & 8 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ SUA_SERVER sets and overrides MQTT server address to connect
-p, --path path where downloaded update bundles will be stored (default is '/data/selfupdates')
-s, --server MQTT broker server to connect (default is 'tcp://mosquitto:1883')
(has precedence over SUA_SERVER environment variable)
-c, --ca path to certificate to verify connection with bundle server (default is '/etc/ssl/certs/selfupdateagent.crt')
-v, --version display version (Git hash and build number) used to build SUA and exit
)";

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

const char * env_server = std::getenv("SUA_SERVER");
if(env_server) {
Expand Down Expand Up @@ -114,6 +116,16 @@ int main(int argc, char* argv[])
continue;
}

if(("-c" == opt) || ("--ca" == opt)) {
if (argValue.empty()) {
std::cout << "Missing path to .crt for '" << opt << "'" << std::endl
<< help_string << std::endl;
return 1;
}
pathToCertificate = argValue;
continue;
}

std::cout << "Invalid argument '" << opt << "'" << std::endl
<< help_string << std::endl;
return 1;
Expand Down Expand Up @@ -182,20 +194,22 @@ int main(int argc, char* argv[])

sua::SelfUpdateAgent sua;
sua::Context & ctx = sua.context();
ctx.stateMachine = std::make_shared<sua::FSM>(sua.context());
ctx.installerAgent = installerAgent;
ctx.downloadMode = downloadMode;
ctx.downloaderAgent = std::make_shared<sua::Downloader>(hostPathToSelfupdateDir, ctx.tempFileName);
ctx.messagingProtocol = std::make_shared<sua::MqttMessagingProtocolJSON>();
ctx.updatesDirectory = hostPathToSelfupdateDir;
ctx.bundleChecker = std::make_shared<sua::BundleChecker>();
ctx.mqttProcessor = std::make_shared<sua::MqttProcessor>(ctx);
ctx.certificateFileName = pathToCertificate;
ctx.updatesDirectory = hostPathToSelfupdateDir;
ctx.stateMachine = std::make_shared<sua::FSM>(sua.context());
ctx.installerAgent = installerAgent;
ctx.downloadMode = downloadMode;
ctx.downloaderAgent = std::make_shared<sua::Downloader>(ctx);
ctx.messagingProtocol = std::make_shared<sua::MqttMessagingProtocolJSON>();
ctx.bundleChecker = std::make_shared<sua::BundleChecker>();
ctx.mqttProcessor = std::make_shared<sua::MqttProcessor>(ctx);

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

sua.init();
sua.start(conf);
Expand Down
37 changes: 16 additions & 21 deletions utest/TestSelfUpdateScenarios.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ using ::testing::_;
#include "Mqtt/MqttMessagingProtocolJSON.h"
#include "Mqtt/MqttMessage.h"
#include "Install/DummyRaucInstaller.h"
#include "Download/Downloader.h"

#include "MockFSM.h"
#include "MockDownloader.h"
#include "MockFSM.h"
#include "MockMqttProcessor.h"

using namespace std::chrono_literals;
Expand Down Expand Up @@ -118,7 +119,7 @@ namespace {
"config": [
{
"key": "image",
"value": "{}"
"value": "https://127.0.0.1/bundle"
}
]
}
Expand Down Expand Up @@ -177,8 +178,7 @@ namespace {
sua::Logger::instance().init();
sua::Logger::instance().setLogLevel(sua::Logger::Level::All);

downloader = std::make_shared<MockDownloader>();
ctx().downloaderAgent = downloader;
ctx().downloaderAgent = std::make_shared<sua::Downloader>(ctx());

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

ctx().messagingProtocol = std::make_shared<sua::MqttMessagingProtocolJSON>();
ctx().bundleChecker = std::make_shared<sua::BundleChecker>();
}
}

sua::SelfUpdateAgent sua;

std::shared_ptr<MockFSM> fsm;
std::shared_ptr<MockMqttProcessor> mqttProcessor;
std::shared_ptr<MockRaucInstaller> installerAgent;
std::shared_ptr<MockDownloader> downloader;

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

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

sua.init();
start();

EXPECT_CALL(*downloader, start(_)).WillOnce(Return(sua::TechCode::DownloadFailed));

triggerIdentify(BUNDLE_11, "1.1");
trigger(COMMAND_DOWNLOAD);

Expand All @@ -295,9 +297,7 @@ namespace {
TEST_F(TestSelfUpdateScenarios, downloadedBundleVersionMismatchWithSpec_endsInFailedState)
{
expectedStates = {"Uninitialized", "Connected", "Downloading", "Installing", "Failed"};
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloaded, M::VersionChecking, M::Rejected};

EXPECT_CALL(*downloader, start(_)).WillOnce(Return(sua::TechCode::OK));
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloading, M::Downloaded, M::VersionChecking, M::Rejected};

sua.init();
start();
Expand All @@ -313,9 +313,8 @@ namespace {
TEST_F(TestSelfUpdateScenarios, downloadSucceeds_waitsForInstallCommand)
{
expectedStates = {"Uninitialized", "Connected", "Downloading", "Installing"};
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloaded};
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloading, M::Downloaded};

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

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

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

sua.init();
Expand All @@ -350,11 +348,10 @@ namespace {
TEST_F(TestSelfUpdateScenarios, installSucceeds_waitsInInstalledState)
{
expectedStates = {"Uninitialized", "Connected", "Downloading", "Installing", "Installed"};
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloaded,
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloading, M::Downloaded,
M::VersionChecking, M::Installing, M::Installing, M::Installing, M::Installing, M::Installed,
M::CurrentState};

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

Expand All @@ -372,10 +369,9 @@ namespace {
TEST_F(TestSelfUpdateScenarios, installFails_endsInFailedState)
{
expectedStates = {"Uninitialized", "Connected", "Downloading", "Installing", "Failed"};
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloaded, M::VersionChecking,
M::Installing, M::Installing, M::Installing, M::Installing, M::InstallFailed};
expectedMessages = {M::SystemVersion, M::Identifying, M::Identified, M::Downloading, M::Downloaded,
M::VersionChecking, M::Installing, M::Installing, M::Installing, M::Installing, M::InstallFailed};

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

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

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

Expand Down
7 changes: 7 additions & 0 deletions utest/sua-apache2.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<VirtualHost *:443>
ServerName 127.0.0.1
DocumentRoot /var/www/html
SSLEngine on
SSLCertificateFile /etc/ssl/certs/selfupdateagent.crt
SSLCertificateKeyFile /etc/ssl/private/selfupdateagent.key
</VirtualHost>
22 changes: 22 additions & 0 deletions utest/sua-certificate.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[req]
default_bits = 2048
distinguished_name = req_distinguished_name
req_extensions = req_ext
x509_extensions = v3_req
prompt = no

[req_distinguished_name]
countryName = XX
stateOrProvinceName = N/A
localityName = N/A
organizationName = Self-signed certificate
commonName = 127.0.0.1

[req_ext]
subjectAltName = @alt_names

[v3_req]
subjectAltName = @alt_names

[alt_names]
IP.1 = 127.0.0.1

0 comments on commit db0b8ac

Please sign in to comment.