From 636e9cd7a5d4e0ddbddcc541fc606cda15928f69 Mon Sep 17 00:00:00 2001 From: Phil Wise Date: Sun, 17 Oct 2021 14:54:10 +0100 Subject: [PATCH] Refactor initialization process The purpose of this change is to allow SotaUptaneClient to operate offline (before provisioning has completed on-line). At the moment SotaUptaneClient still requires provisioning to complete (see SotaUptaneClient::initialize()), but this refactor is a step along the path outlined in https://github.com/uptane/aktualizr/issues/8 Signed-off-by: Phil Wise --- src/libaktualizr/primary/CMakeLists.txt | 16 +- .../primary/device_cred_prov_test.cc | 51 +-- src/libaktualizr/primary/initializer.h | 77 ---- .../{initializer.cc => provisioner.cc} | 352 +++++++++--------- src/libaktualizr/primary/provisioner.h | 131 +++++++ ...nitializer_test.cc => provisioner_test.cc} | 86 +++-- .../primary/provisioner_test_utils.cc | 31 ++ .../primary/provisioner_test_utils.h | 11 + .../primary/reregistration_test.cc | 1 - src/libaktualizr/primary/sotauptaneclient.cc | 52 ++- src/libaktualizr/primary/sotauptaneclient.h | 16 +- src/libaktualizr/uptane/CMakeLists.txt | 10 +- .../uptane/uptane_network_test.cc | 22 +- src/libaktualizr/uptane/uptane_test.cc | 10 +- 14 files changed, 497 insertions(+), 369 deletions(-) delete mode 100644 src/libaktualizr/primary/initializer.h rename src/libaktualizr/primary/{initializer.cc => provisioner.cc} (83%) create mode 100644 src/libaktualizr/primary/provisioner.h rename src/libaktualizr/primary/{initializer_test.cc => provisioner_test.cc} (80%) create mode 100644 src/libaktualizr/primary/provisioner_test_utils.cc create mode 100644 src/libaktualizr/primary/provisioner_test_utils.h diff --git a/src/libaktualizr/primary/CMakeLists.txt b/src/libaktualizr/primary/CMakeLists.txt index 32289bd365..7a2eb0c668 100644 --- a/src/libaktualizr/primary/CMakeLists.txt +++ b/src/libaktualizr/primary/CMakeLists.txt @@ -1,12 +1,12 @@ set(SOURCES aktualizr.cc aktualizr_helpers.cc - initializer.cc + provisioner.cc reportqueue.cc secondary_provider.cc sotauptaneclient.cc) set(HEADERS aktualizr_helpers.h - initializer.h + provisioner.h reportqueue.h secondary_config.h secondary_provider_builder.h @@ -14,6 +14,10 @@ set(HEADERS aktualizr_helpers.h add_library(primary OBJECT ${SOURCES}) +add_library(provisioner_test_utils STATIC provisioner_test_utils.cc) +aktualizr_source_file_checks(provisioner_test_utils.cc provisioner_test_utils.h) + + add_aktualizr_test(NAME aktualizr SOURCES aktualizr_test.cc PROJECT_WORKING_DIRECTORY @@ -55,10 +59,10 @@ else (BUILD_OSTREE) aktualizr_source_file_checks(aktualizr_fullostree_test.cc download_nonostree_test.cc aktualizr_lite_test.cc) endif (BUILD_OSTREE) -add_aktualizr_test(NAME initializer - SOURCES initializer_test.cc +add_aktualizr_test(NAME provisioner + SOURCES provisioner_test.cc PROJECT_WORKING_DIRECTORY - LIBRARIES PUBLIC uptane_generator_lib) + LIBRARIES PUBLIC uptane_generator_lib provisioner_test_utils) add_aktualizr_test(NAME reportqueue SOURCES reportqueue_test.cc @@ -97,7 +101,7 @@ add_aktualizr_test(NAME metadata_expiration add_aktualizr_test(NAME device_cred_prov SOURCES device_cred_prov_test.cc PROJECT_WORKING_DIRECTORY - LIBRARIES uptane_generator_lib) + LIBRARIES uptane_generator_lib provisioner_test_utils) set_tests_properties(test_device_cred_prov PROPERTIES LABELS "crypto") diff --git a/src/libaktualizr/primary/device_cred_prov_test.cc b/src/libaktualizr/primary/device_cred_prov_test.cc index 11cd88eb80..2f3e33e9fc 100644 --- a/src/libaktualizr/primary/device_cred_prov_test.cc +++ b/src/libaktualizr/primary/device_cred_prov_test.cc @@ -9,7 +9,8 @@ #include "httpfake.h" #include "logging/logging.h" -#include "primary/initializer.h" +#include "primary/provisioner.h" +#include "primary/provisioner_test_utils.h" #include "primary/sotauptaneclient.h" #include "storage/invstorage.h" #include "uptane/uptanerepository.h" @@ -28,10 +29,10 @@ TEST(DeviceCredProv, DeviceIdFailure) { auto storage = INvStorage::newStorage(config.storage); auto http = std::make_shared(temp_dir.Path()); - KeyManager keys(storage, config.keymanagerConfig()); + auto keys = std::make_shared(storage, config.keymanagerConfig()); // Expect failure when trying to read the certificate to get the device ID. - EXPECT_THROW(Initializer(config.provision, storage, http, keys, {}), std::exception); + ExpectProvisionError(Provisioner(config.provision, storage, http, keys, {})); } /** @@ -49,10 +50,10 @@ TEST(DeviceCredProv, TlsFailure) { auto storage = INvStorage::newStorage(config.storage); auto http = std::make_shared(temp_dir.Path()); - KeyManager keys(storage, config.keymanagerConfig()); + auto keys = std::make_shared(storage, config.keymanagerConfig()); // Expect failure when trying to read the TLS credentials. - EXPECT_THROW(Initializer(config.provision, storage, http, keys, {}), Initializer::Error); + ExpectProvisionError(Provisioner(config.provision, storage, http, keys, {})); } /** @@ -79,9 +80,9 @@ TEST(DeviceCredProv, Incomplete) { boost::filesystem::copy_file("tests/test_data/device_cred_prov/ca.pem", temp_dir / "import/ca.pem"); auto storage = INvStorage::newStorage(config.storage); storage->importData(config.import); - KeyManager keys(storage, config.keymanagerConfig()); + auto keys = std::make_shared(storage, config.keymanagerConfig()); - EXPECT_THROW(Initializer(config.provision, storage, http, keys, {}), Initializer::Error); + ExpectProvisionError(Provisioner(config.provision, storage, http, keys, {})); } { @@ -93,9 +94,9 @@ TEST(DeviceCredProv, Incomplete) { boost::filesystem::copy_file("tests/test_data/device_cred_prov/client.pem", temp_dir / "import/client.pem"); auto storage = INvStorage::newStorage(config.storage); storage->importData(config.import); - KeyManager keys(storage, config.keymanagerConfig()); + auto keys = std::make_shared(storage, config.keymanagerConfig()); - EXPECT_THROW(Initializer(config.provision, storage, http, keys, {}), Initializer::Error); + ExpectProvisionError(Provisioner(config.provision, storage, http, keys, {})); } { @@ -107,9 +108,9 @@ TEST(DeviceCredProv, Incomplete) { boost::filesystem::copy_file("tests/test_data/device_cred_prov/pkey.pem", temp_dir / "import/pkey.pem"); auto storage = INvStorage::newStorage(config.storage); storage->importData(config.import); - KeyManager keys(storage, config.keymanagerConfig()); + auto keys = std::make_shared(storage, config.keymanagerConfig()); - EXPECT_THROW(Initializer(config.provision, storage, http, keys, {}), Initializer::Error); + ExpectProvisionError(Provisioner(config.provision, storage, http, keys, {})); } { @@ -122,9 +123,9 @@ TEST(DeviceCredProv, Incomplete) { boost::filesystem::copy_file("tests/test_data/device_cred_prov/client.pem", temp_dir / "import/client.pem"); auto storage = INvStorage::newStorage(config.storage); storage->importData(config.import); - KeyManager keys(storage, config.keymanagerConfig()); + auto keys = std::make_shared(storage, config.keymanagerConfig()); - EXPECT_THROW(Initializer(config.provision, storage, http, keys, {}), Initializer::Error); + ExpectProvisionError(Provisioner(config.provision, storage, http, keys, {})); } { @@ -137,9 +138,9 @@ TEST(DeviceCredProv, Incomplete) { boost::filesystem::copy_file("tests/test_data/device_cred_prov/pkey.pem", temp_dir / "import/pkey.pem"); auto storage = INvStorage::newStorage(config.storage); storage->importData(config.import); - KeyManager keys(storage, config.keymanagerConfig()); + auto keys = std::make_shared(storage, config.keymanagerConfig()); - EXPECT_THROW(Initializer(config.provision, storage, http, keys, {}), Initializer::Error); + ExpectProvisionError(Provisioner(config.provision, storage, http, keys, {})); } { @@ -152,9 +153,9 @@ TEST(DeviceCredProv, Incomplete) { boost::filesystem::copy_file("tests/test_data/device_cred_prov/pkey.pem", temp_dir / "import/pkey.pem"); auto storage = INvStorage::newStorage(config.storage); storage->importData(config.import); - KeyManager keys(storage, config.keymanagerConfig()); + auto keys = std::make_shared(storage, config.keymanagerConfig()); - EXPECT_THROW(Initializer(config.provision, storage, http, keys, {}), Initializer::Error); + ExpectProvisionError(Provisioner(config.provision, storage, http, keys, {})); } // Do one last round with all three files to make sure it actually works as @@ -169,9 +170,9 @@ TEST(DeviceCredProv, Incomplete) { boost::filesystem::copy_file("tests/test_data/device_cred_prov/pkey.pem", temp_dir / "import/pkey.pem"); auto storage = INvStorage::newStorage(config.storage); storage->importData(config.import); - KeyManager keys(storage, config.keymanagerConfig()); + auto keys = std::make_shared(storage, config.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(config.provision, storage, http, keys, {})); + ExpectProvisionOK(Provisioner(config.provision, storage, http, keys, {})); } /** @@ -195,9 +196,9 @@ TEST(DeviceCredProv, Success) { auto storage = INvStorage::newStorage(config.storage); storage->importData(config.import); auto http = std::make_shared(temp_dir.Path()); - KeyManager keys(storage, config.keymanagerConfig()); + auto keys = std::make_shared(storage, config.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(config.provision, storage, http, keys, {})); + ExpectProvisionOK(Provisioner(config.provision, storage, http, keys, {})); } /** @@ -226,8 +227,8 @@ TEST(DeviceCredProv, ReImportCert) { /* prepare storage initialized with device_id from config where cert CN and device id are differen*/ auto storage = INvStorage::newStorage(config.storage); storage->importData(config.import); - KeyManager keys(storage, config.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(config.provision, storage, http, keys, {})); + auto keys = std::make_shared(storage, config.keymanagerConfig()); + ExpectProvisionOK(Provisioner(config.provision, storage, http, keys, {})); std::string device_id; EXPECT_TRUE(storage->loadDeviceId(&device_id)); EXPECT_EQ(device_id, "AnYsTrInG"); @@ -237,8 +238,8 @@ TEST(DeviceCredProv, ReImportCert) { config.import.tls_clientcert_path = utils::BasedPath("newcert.pem"); auto storage = INvStorage::newStorage(config.storage); EXPECT_NO_THROW(storage->importData(config.import)); - KeyManager keys(storage, config.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(config.provision, storage, http, keys, {})); + auto keys = std::make_shared(storage, config.keymanagerConfig()); + ExpectProvisionOK(Provisioner(config.provision, storage, http, keys, {})); std::string device_id; EXPECT_TRUE(storage->loadDeviceId(&device_id)); EXPECT_EQ(device_id, "AnYsTrInG"); diff --git a/src/libaktualizr/primary/initializer.h b/src/libaktualizr/primary/initializer.h deleted file mode 100644 index 94f615ee13..0000000000 --- a/src/libaktualizr/primary/initializer.h +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef INITIALIZER_H_ -#define INITIALIZER_H_ - -#include "libaktualizr/secondaryinterface.h" - -#include "crypto/keymanager.h" -#include "http/httpinterface.h" -#include "libaktualizr/config.h" -#include "storage/invstorage.h" -#include "uptane/tuf.h" - -const int MaxInitializationAttempts = 3; - -class Initializer { - public: - Initializer(const ProvisionConfig& config_in, std::shared_ptr storage_in, - std::shared_ptr http_client_in, KeyManager& keys_in, - const std::map >& secondaries_in); - - class Error : public std::runtime_error { - public: - explicit Error(const std::string& what) : std::runtime_error(std::string("Initializer error: ") + what) {} - }; - class KeyGenerationError : public Error { - public: - explicit KeyGenerationError(const std::string& what) - : Error(std::string("Could not generate Uptane key pair: ") + what) {} - }; - class StorageError : public Error { - public: - explicit StorageError(const std::string& what) : Error(std::string("Storage error: ") + what) {} - }; - class ServerError : public Error { - public: - explicit ServerError(const std::string& what) : Error(std::string("Server error: ") + what) {} - }; - class ServerOccupied : public Error { - public: - ServerOccupied() : Error("") {} - }; - - private: - void initDeviceId(); - void resetDeviceId(); - void initEcuSerials(); - void initPrimaryEcuKeys(); - void initTlsCreds(); - void resetTlsCreds(); - void initSecondaryInfo(); - void initEcuRegister(); - bool loadSetTlsCreds(); - void initEcuReportCounter(); - - const ProvisionConfig& config_; - std::shared_ptr storage_; - std::shared_ptr http_client_; - KeyManager& keys_; - const std::map& secondaries_; - std::vector sec_info_; - EcuSerials new_ecu_serials_; - bool register_ecus_{false}; -}; - -class EcuCompare { - public: - explicit EcuCompare(std::pair ecu_in) - : serial(std::move(ecu_in.first)), hardware_id(std::move(ecu_in.second)) {} - bool operator()(const std::pair& in) const { - return (in.first == serial && in.second == hardware_id); - } - - private: - const Uptane::EcuSerial serial; - const Uptane::HardwareIdentifier hardware_id; -}; - -#endif // INITIALIZER_H_ diff --git a/src/libaktualizr/primary/initializer.cc b/src/libaktualizr/primary/provisioner.cc similarity index 83% rename from src/libaktualizr/primary/initializer.cc rename to src/libaktualizr/primary/provisioner.cc index a6cc8004e2..293be46f5c 100644 --- a/src/libaktualizr/primary/initializer.cc +++ b/src/libaktualizr/primary/provisioner.cc @@ -1,4 +1,4 @@ -#include "initializer.h" +#include "provisioner.h" #include @@ -9,20 +9,80 @@ #include "crypto/keymanager.h" #include "logging/logging.h" +using std::map; +using std::move; +using std::shared_ptr; + +Provisioner::Provisioner(const ProvisionConfig& config, shared_ptr storage, + shared_ptr http_client, shared_ptr keys, + const map>& secondaries) + : config_(config), + storage_(move(storage)), + http_client_(move(http_client)), + keys_(move(keys)), + secondaries_(secondaries) {} + +void Provisioner::SecondariesWereChanged() { current_state_ = State::kUnknown; } + +bool Provisioner::Attempt() { + try { + initDeviceId(); + + try { + initTlsCreds(); + } catch (const ServerOccupied& e) { + // if a device with the same ID has already been registered to the server, + // generate a new one + storage_->clearDeviceId(); + LOG_ERROR << "Device name is already registered. Retrying."; + throw; + } + + initPrimaryEcuKeys(); + + initEcuSerials(); + + initSecondaryInfo(); + + initEcuRegister(); + + initEcuReportCounter(); + + current_state_ = State::kOk; + return true; + } catch (const Provisioner::Error& ex) { + last_error_ = ex.what(); + current_state_ = State::kTemporaryError; + return false; + } catch (const std::exception& ex) { + LOG_DEBUG << "Provisioner::Attempt() caught an exception not deriving from Provisioner::Error"; + last_error_ = ex.what(); + current_state_ = State::kTemporaryError; + return false; + } +} + +bool Provisioner::ShouldAttemptAgain() const { + return current_state_ == State::kUnknown || current_state_ == State::kTemporaryError; +} + // Postcondition: device_id is in the storage -void Initializer::initDeviceId() { +void Provisioner::initDeviceId() { // If device_id is already stored, just return. std::string device_id; if (storage_->loadDeviceId(&device_id)) { return; } + LOG_WARNING << "No device ID yet..."; + // If device_id is specified in the config, use that. device_id = config_.device_id; if (device_id.empty()) { + LOG_WARNING << "device_id is empty... generating"; // Otherwise, try to read the device certificate if it is available. try { - device_id = keys_.getCN(); + device_id = keys_->getCN(); } catch (const std::exception& e) { // No certificate: for device credential provisioning, abort. For shared // credential provisioning, generate a random name. @@ -39,98 +99,15 @@ void Initializer::initDeviceId() { storage_->storeDeviceId(device_id); } -void Initializer::resetDeviceId() { storage_->clearDeviceId(); } - -// Postcondition [(serial, hw_id)] is in the storage -void Initializer::initEcuSerials() { - EcuSerials stored_ecu_serials; - storage_->loadEcuSerials(&stored_ecu_serials); - - std::string primary_ecu_serial_local = config_.primary_ecu_serial; - if (primary_ecu_serial_local.empty()) { - primary_ecu_serial_local = keys_.UptanePublicKey().KeyId(); - } - - std::string primary_ecu_hardware_id = config_.primary_ecu_hardware_id; - if (primary_ecu_hardware_id.empty()) { - primary_ecu_hardware_id = Utils::getHostname(); - if (primary_ecu_hardware_id.empty()) { - throw Error("Could not get current host name, please configure an hardware ID explicitly"); - } - } - - new_ecu_serials_.emplace_back(Uptane::EcuSerial(primary_ecu_serial_local), - Uptane::HardwareIdentifier(primary_ecu_hardware_id)); - for (const auto& s : secondaries_) { - new_ecu_serials_.emplace_back(s.first, s.second->getHwId()); - } - - register_ecus_ = stored_ecu_serials.empty(); - if (!stored_ecu_serials.empty()) { - // We should probably clear the misconfigured_ecus table once we have - // consent working. - std::vector found(stored_ecu_serials.size(), false); - - EcuCompare primary_comp(new_ecu_serials_[0]); - EcuSerials::const_iterator store_it; - store_it = std::find_if(stored_ecu_serials.cbegin(), stored_ecu_serials.cend(), primary_comp); - if (store_it == stored_ecu_serials.cend()) { - LOG_INFO << "Configured Primary ECU serial " << new_ecu_serials_[0].first << " with hardware ID " - << new_ecu_serials_[0].second << " not found in storage."; - register_ecus_ = true; - } else { - found[static_cast(store_it - stored_ecu_serials.cbegin())] = true; - } - - // Check all configured Secondaries to see if any are new. - for (auto it = secondaries_.cbegin(); it != secondaries_.cend(); ++it) { - EcuCompare secondary_comp(std::make_pair(it->second->getSerial(), it->second->getHwId())); - store_it = std::find_if(stored_ecu_serials.cbegin(), stored_ecu_serials.cend(), secondary_comp); - if (store_it == stored_ecu_serials.cend()) { - LOG_INFO << "Configured Secondary ECU serial " << it->second->getSerial() << " with hardware ID " - << it->second->getHwId() << " not found in storage."; - register_ecus_ = true; - } else { - found[static_cast(store_it - stored_ecu_serials.cbegin())] = true; - } - } - - // Check all stored Secondaries not already matched to see if any have been - // removed. Store them in a separate table to keep track of them. - std::vector::iterator found_it; - for (found_it = found.begin(); found_it != found.end(); ++found_it) { - if (!*found_it) { - auto not_registered = stored_ecu_serials[static_cast(found_it - found.begin())]; - LOG_INFO << "ECU serial " << not_registered.first << " with hardware ID " << not_registered.second - << " in storage was not found in Secondary configuration."; - register_ecus_ = true; - storage_->saveMisconfiguredEcu({not_registered.first, not_registered.second, EcuState::kOld}); - } - } - } -} - -// Postcondition: (public, private) is in the storage. It should not be stored until Secondaries are provisioned -void Initializer::initPrimaryEcuKeys() { - std::string key_pair; - try { - key_pair = keys_.generateUptaneKeyPair(); - } catch (const std::exception& e) { - throw KeyGenerationError(e.what()); - } - - if (key_pair.empty()) { - throw KeyGenerationError("Unknow error"); - } -} - -bool Initializer::loadSetTlsCreds() { - keys_.copyCertsToCurl(*http_client_); - return keys_.isOk(); +bool Provisioner::loadSetTlsCreds() { + keys_->copyCertsToCurl(*http_client_); + return keys_->isOk(); } -// Postcondition: TLS credentials are in the storage -void Initializer::initTlsCreds() { +// Postcondition: +// - TLS credentials are in the storage +// - This device_id is provisioned on the device gateway +void Provisioner::initTlsCreds() { if (loadSetTlsCreds()) { return; } @@ -202,21 +179,129 @@ void Initializer::initTlsCreds() { LOG_INFO << "Provisioned successfully on Device Gateway."; } -void Initializer::resetTlsCreds() { - if (config_.mode != ProvisionMode::kDeviceCred) { - storage_->clearTlsCreds(); +// Postcondition: (public, private) is in the storage. It should not be stored until Secondaries are provisioned +void Provisioner::initPrimaryEcuKeys() { + std::string key_pair; + try { + key_pair = keys_->generateUptaneKeyPair(); + } catch (const std::exception& e) { + throw KeyGenerationError(e.what()); + } + + if (key_pair.empty()) { + throw KeyGenerationError("Unknown error"); + } +} + +// Postcondition [(serial, hw_id)] is in the storage +void Provisioner::initEcuSerials() { + EcuSerials stored_ecu_serials; + storage_->loadEcuSerials(&stored_ecu_serials); + + std::string primary_ecu_serial_local = config_.primary_ecu_serial; + if (primary_ecu_serial_local.empty()) { + primary_ecu_serial_local = keys_->UptanePublicKey().KeyId(); + } + + std::string primary_ecu_hardware_id = config_.primary_ecu_hardware_id; + if (primary_ecu_hardware_id.empty()) { + primary_ecu_hardware_id = Utils::getHostname(); + if (primary_ecu_hardware_id.empty()) { + throw Error("Could not get current host name, please configure an hardware ID explicitly"); + } + } + + new_ecu_serials_.clear(); + new_ecu_serials_.emplace_back(Uptane::EcuSerial(primary_ecu_serial_local), + Uptane::HardwareIdentifier(primary_ecu_hardware_id)); + for (const auto& s : secondaries_) { + new_ecu_serials_.emplace_back(s.first, s.second->getHwId()); + } + + register_ecus_ = stored_ecu_serials.empty(); + if (!stored_ecu_serials.empty()) { + // We should probably clear the misconfigured_ecus table once we have + // consent working. + std::vector found(stored_ecu_serials.size(), false); + + EcuCompare primary_comp(new_ecu_serials_[0]); + EcuSerials::const_iterator store_it; + store_it = std::find_if(stored_ecu_serials.cbegin(), stored_ecu_serials.cend(), primary_comp); + if (store_it == stored_ecu_serials.cend()) { + LOG_INFO << "Configured Primary ECU serial " << new_ecu_serials_[0].first << " with hardware ID " + << new_ecu_serials_[0].second << " not found in storage."; + register_ecus_ = true; + } else { + found[static_cast(store_it - stored_ecu_serials.cbegin())] = true; + } + + // Check all configured Secondaries to see if any are new. + for (auto it = secondaries_.cbegin(); it != secondaries_.cend(); ++it) { + EcuCompare secondary_comp(std::make_pair(it->second->getSerial(), it->second->getHwId())); + store_it = std::find_if(stored_ecu_serials.cbegin(), stored_ecu_serials.cend(), secondary_comp); + if (store_it == stored_ecu_serials.cend()) { + LOG_INFO << "Configured Secondary ECU serial " << it->second->getSerial() << " with hardware ID " + << it->second->getHwId() << " not found in storage."; + register_ecus_ = true; + } else { + found[static_cast(store_it - stored_ecu_serials.cbegin())] = true; + } + } + + // Check all stored Secondaries not already matched to see if any have been + // removed. Store them in a separate table to keep track of them. + std::vector::iterator found_it; + for (found_it = found.begin(); found_it != found.end(); ++found_it) { + if (!*found_it) { + auto not_registered = stored_ecu_serials[static_cast(found_it - found.begin())]; + LOG_INFO << "ECU serial " << not_registered.first << " with hardware ID " << not_registered.second + << " in storage was not found in Secondary configuration."; + register_ecus_ = true; + storage_->saveMisconfiguredEcu({not_registered.first, not_registered.second, EcuState::kOld}); + } + } + } +} + +void Provisioner::initSecondaryInfo() { + sec_info_.clear(); + for (const auto& s : secondaries_) { + const Uptane::EcuSerial serial = s.first; + SecondaryInterface& sec = *s.second; + + SecondaryInfo info; + // If upgrading from the older version of the storage without the + // secondary_ecus table, we need to migrate the data. This should be done + // regardless of whether we need to (re-)register the ECUs. + // The ECU serials should be already initialized by this point. + if (!storage_->loadSecondaryInfo(serial, &info) || info.type.empty() || info.pub_key.Type() == KeyType::kUnknown) { + info.serial = serial; + info.hw_id = sec.getHwId(); + info.type = sec.Type(); + const PublicKey& p = sec.getPublicKey(); + if (p.Type() != KeyType::kUnknown) { + info.pub_key = p; + } + // If we don't need to register the ECUs, we still need to store this info + // to complete the migration. + if (!register_ecus_) { + storage_->saveSecondaryInfo(info.serial, info.type, info.pub_key); + } + } + // We will need this info later if the device is not yet provisioned + sec_info_.push_back(std::move(info)); } } // Postcondition: "ECUs registered" flag set in the storage -void Initializer::initEcuRegister() { +void Provisioner::initEcuRegister() { // Allow re-registration if the ECUs have changed. if (!register_ecus_) { LOG_DEBUG << "All ECUs are already registered with the server."; return; } - PublicKey uptane_public_key = keys_.UptanePublicKey(); + PublicKey uptane_public_key = keys_->UptanePublicKey(); if (uptane_public_key.Type() == KeyType::kUnknown) { throw StorageError("Invalid key in storage"); @@ -229,7 +314,7 @@ void Initializer::initEcuRegister() { Json::Value primary_ecu; primary_ecu["hardware_identifier"] = new_ecu_serials_[0].second.ToString(); primary_ecu["ecu_serial"] = new_ecu_serials_[0].first.ToString(); - primary_ecu["clientKey"] = keys_.UptanePublicKey().ToUptane(); + primary_ecu["clientKey"] = keys_->UptanePublicKey().ToUptane(); all_ecus["ecus"].append(primary_ecu); } @@ -263,36 +348,7 @@ void Initializer::initEcuRegister() { LOG_INFO << "ECUs have been successfully registered with the server."; } -void Initializer::initSecondaryInfo() { - for (const auto& s : secondaries_) { - const Uptane::EcuSerial serial = s.first; - SecondaryInterface& sec = *s.second; - - SecondaryInfo info; - // If upgrading from the older version of the storage without the - // secondary_ecus table, we need to migrate the data. This should be done - // regardless of whether we need to (re-)register the ECUs. - // The ECU serials should be already initialized by this point. - if (!storage_->loadSecondaryInfo(serial, &info) || info.type.empty() || info.pub_key.Type() == KeyType::kUnknown) { - info.serial = serial; - info.hw_id = sec.getHwId(); - info.type = sec.Type(); - const PublicKey& p = sec.getPublicKey(); - if (p.Type() != KeyType::kUnknown) { - info.pub_key = p; - } - // If we don't need to register the ECUs, we still need to store this info - // to complete the migration. - if (!register_ecus_) { - storage_->saveSecondaryInfo(info.serial, info.type, info.pub_key); - } - } - // We will need this info later if the device is not yet provisioned - sec_info_.push_back(std::move(info)); - } -} - -void Initializer::initEcuReportCounter() { +void Provisioner::initEcuReportCounter() { std::vector> ecu_cnt; if (storage_->loadEcuReportCounter(&ecu_cnt)) { @@ -307,41 +363,3 @@ void Initializer::initEcuReportCounter() { storage_->saveEcuReportCounter(Uptane::EcuSerial(ecu_serials[0].first.ToString()), 0); } - -// Postcondition: "ECUs registered" flag set in the storage -Initializer::Initializer(const ProvisionConfig& config_in, std::shared_ptr storage_in, - std::shared_ptr http_client_in, KeyManager& keys_in, - const std::map>& secondaries_in) - : config_(config_in), - storage_(std::move(storage_in)), - http_client_(std::move(http_client_in)), - keys_(keys_in), - secondaries_(secondaries_in) { - for (int i = 0; i < MaxInitializationAttempts; i++) { - initDeviceId(); - - try { - initTlsCreds(); - } catch (const ServerOccupied& e) { - // if a device with the same ID has already been registered to the server, - // generate a new one - resetDeviceId(); - LOG_ERROR << "Device name is already registered. Retrying."; - continue; - } - - initPrimaryEcuKeys(); - - initEcuSerials(); - - initSecondaryInfo(); - - initEcuRegister(); - - initEcuReportCounter(); - - return; - } - - throw Error(std::string("Initialization failed after ") + std::to_string(MaxInitializationAttempts) + " attempts"); -} diff --git a/src/libaktualizr/primary/provisioner.h b/src/libaktualizr/primary/provisioner.h new file mode 100644 index 0000000000..ea6fa36019 --- /dev/null +++ b/src/libaktualizr/primary/provisioner.h @@ -0,0 +1,131 @@ +#ifndef INITIALIZER_H_ +#define INITIALIZER_H_ + +#include "libaktualizr/secondaryinterface.h" + +#include "crypto/keymanager.h" +#include "http/httpinterface.h" +#include "libaktualizr/config.h" +#include "storage/invstorage.h" +#include "uptane/tuf.h" + +class Provisioner { + public: + enum class State { + kUnknown = 0, + kOk, + kTemporaryError, + // Note there is no 'Permanent' error here, because all the failure modes we have so far may recover + }; + + /** + * Provisioner gets the local system, represented by config, storage, key_manager and secondaries properly registered + * on the server (represented by http_client). The constructor doesn't do any work. Calling bool Attempt() will make + * one provisioning attempt (if necessary) and return true if provisioning is done. + * @param config + * @param storage + * @param http_client + * @param keys + * @param secondaries + */ + Provisioner(const ProvisionConfig& config, std::shared_ptr storage, + std::shared_ptr http_client, std::shared_ptr key_manager, + const std::map >& secondaries); + + /** + * Notify Provisioner that the secondaries passed in via the constructor have + * changed. + * This will reverts the provisioning state, so that Attempt() will cause + * provisioning to be attempted again. + */ + void SecondariesWereChanged(); + + /** + * Make one attempt at provisioning, if the provisioning hasn't already completed. + * If provisioning is already successful this is a no-op. + * use like: + * if (!provisioner_.Attempt()) { + * return error; + * } + * // Provisioned. Carry on as normal + * @returns whether the device is provisioned + */ + bool Attempt(); + + State CurrentState() const { return current_state_; } + + /** + * A textual description of the last cause for provisioning to fail. + */ + std::string LastError() const { return last_error_; }; + + /** + * Is is CurrentState() either kUnknown or kTemporaryError? + * To keep trying until provisioning succeeds or the retry count is hit, do: + * while(provisioner.ShouldAttemptAgain()) { provisioner.MakeAttempt(); } + * @return + */ + bool ShouldAttemptAgain() const; + + private: + class Error : public std::runtime_error { + public: + explicit Error(const std::string& what) : std::runtime_error(std::string("Initializer error: ") + what) {} + }; + + class KeyGenerationError : public Error { + public: + explicit KeyGenerationError(const std::string& what) + : Error(std::string("Could not generate Uptane key pair: ") + what) {} + }; + + class StorageError : public Error { + public: + explicit StorageError(const std::string& what) : Error(std::string("Storage error: ") + what) {} + }; + + class ServerError : public Error { + public: + explicit ServerError(const std::string& what) : Error(std::string("Server error: ") + what) {} + }; + + class ServerOccupied : public Error { + public: + ServerOccupied() : Error("device ID is already registered") {} + }; + + class EcuCompare { + public: + explicit EcuCompare(std::pair ecu_in) + : serial(std::move(ecu_in.first)), hardware_id(std::move(ecu_in.second)) {} + bool operator()(const std::pair& in) const { + return (in.first == serial && in.second == hardware_id); + } + + private: + const Uptane::EcuSerial serial; + const Uptane::HardwareIdentifier hardware_id; + }; + + void initDeviceId(); + bool loadSetTlsCreds(); + void initTlsCreds(); + void initPrimaryEcuKeys(); + void initEcuSerials(); + void initSecondaryInfo(); + void initEcuRegister(); + void initEcuReportCounter(); + + const ProvisionConfig& config_; + std::shared_ptr storage_; + std::shared_ptr http_client_; + std::shared_ptr keys_; + const std::map& secondaries_; + std::vector sec_info_; + EcuSerials new_ecu_serials_; + bool register_ecus_{false}; + State current_state_{State::kUnknown}; + std::string last_error_; +}; + +#endif // INITIALIZER_H_ diff --git a/src/libaktualizr/primary/initializer_test.cc b/src/libaktualizr/primary/provisioner_test.cc similarity index 80% rename from src/libaktualizr/primary/initializer_test.cc rename to src/libaktualizr/primary/provisioner_test.cc index a21d89b3c6..5f42634f16 100644 --- a/src/libaktualizr/primary/initializer_test.cc +++ b/src/libaktualizr/primary/provisioner_test.cc @@ -5,7 +5,8 @@ #include #include "httpfake.h" -#include "primary/initializer.h" +#include "primary/provisioner.h" +#include "primary/provisioner_test_utils.h" #include "primary/sotauptaneclient.h" #include "storage/invstorage.h" #include "utilities/utils.h" @@ -13,7 +14,7 @@ /* * Check that aktualizr creates provisioning data if they don't exist already. */ -TEST(Initializer, Success) { +TEST(Provisioner, Success) { RecordProperty("zephyr_key", "OTA-983,TST-153"); TemporaryDirectory temp_dir; auto http = std::make_shared(temp_dir.Path()); @@ -35,8 +36,9 @@ TEST(Initializer, Success) { EXPECT_FALSE(storage->loadPrimaryKeys(&public_key, &private_key)); // Initialize. - KeyManager keys(storage, conf.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + // auto keys = std::make_shared(storage, conf.keymanagerConfig()); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); // Then verify that the storage contains what we expect. EXPECT_TRUE(storage->loadTlsCreds(&ca, &cert, &pkey)); @@ -59,7 +61,7 @@ TEST(Initializer, Success) { * Check that aktualizr does NOT change provisioning data if they DO exist * already. */ -TEST(Initializer, InitializeTwice) { +TEST(Provisioner, InitializeTwice) { RecordProperty("zephyr_key", "OTA-983,TST-154"); TemporaryDirectory temp_dir; auto http = std::make_shared(temp_dir.Path()); @@ -79,8 +81,9 @@ TEST(Initializer, InitializeTwice) { // Intialize and verify that the storage contains what we expect. { - KeyManager keys(storage, conf.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); + + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); EXPECT_TRUE(storage->loadTlsCreds(&ca1, &cert1, &pkey1)); EXPECT_NE(ca1, ""); @@ -93,8 +96,8 @@ TEST(Initializer, InitializeTwice) { // Intialize again and verify that nothing has changed. { - KeyManager keys(storage, conf.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); std::string pkey2; std::string cert2; @@ -116,7 +119,7 @@ TEST(Initializer, InitializeTwice) { * Check that aktualizr does not generate a pet name when device ID is * specified. */ -TEST(Initializer, PetNameConfiguration) { +TEST(Provisioner, PetNameConfiguration) { RecordProperty("zephyr_key", "OTA-985,TST-146"); TemporaryDirectory temp_dir; const std::string test_name = "test-name-123"; @@ -128,8 +131,8 @@ TEST(Initializer, PetNameConfiguration) { auto storage = INvStorage::newStorage(conf.storage); auto http = std::make_shared(temp_dir.Path()); - KeyManager keys(storage, conf.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); { EXPECT_EQ(conf.provision.device_id, test_name); @@ -153,7 +156,7 @@ TEST(Initializer, PetNameConfiguration) { * already present in the device certificate's common name. This is the expected * behavior required to support replacing a Primary ECU. */ -TEST(Initializer, PetNameDeviceCert) { +TEST(Provisioner, PetNameDeviceCert) { std::string test_name; Config conf("tests/config/basic.toml"); @@ -162,10 +165,10 @@ TEST(Initializer, PetNameDeviceCert) { auto http = std::make_shared(temp_dir.Path()); conf.storage.path = temp_dir.Path(); auto storage = INvStorage::newStorage(conf.storage); - KeyManager keys(storage, conf.keymanagerConfig()); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); storage->storeTlsCert(Utils::readFile("tests/test_data/prov/client.pem")); - test_name = keys.getCN(); - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + test_name = keys->getCN(); + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); std::string devid; EXPECT_TRUE(storage->loadDeviceId(&devid)); EXPECT_EQ(devid, test_name); @@ -178,9 +181,9 @@ TEST(Initializer, PetNameDeviceCert) { auto http = std::make_shared(temp_dir.Path()); conf.storage.path = temp_dir.Path(); auto storage = INvStorage::newStorage(conf.storage); - KeyManager keys(storage, conf.keymanagerConfig()); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); storage->storeTlsCert(Utils::readFile("tests/test_data/prov/client.pem")); - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); std::string devid; EXPECT_TRUE(storage->loadDeviceId(&devid)); EXPECT_EQ(devid, test_name); @@ -190,7 +193,7 @@ TEST(Initializer, PetNameDeviceCert) { /** * Check that aktualizr generates a pet name if no device ID is specified. */ -TEST(Initializer, PetNameCreation) { +TEST(Provisioner, PetNameCreation) { RecordProperty("zephyr_key", "OTA-985,TST-145"); TemporaryDirectory temp_dir; @@ -205,8 +208,8 @@ TEST(Initializer, PetNameCreation) { { auto storage = INvStorage::newStorage(conf.storage); auto http = std::make_shared(temp_dir.Path()); - KeyManager keys(storage, conf.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); EXPECT_TRUE(storage->loadDeviceId(&test_name1)); EXPECT_NE(test_name1, ""); @@ -222,8 +225,8 @@ TEST(Initializer, PetNameCreation) { auto storage = INvStorage::newStorage(conf.storage); auto http = std::make_shared(temp_dir2.Path()); - KeyManager keys(storage, conf.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); EXPECT_TRUE(storage->loadDeviceId(&test_name2)); EXPECT_NE(test_name2, test_name1); @@ -235,8 +238,8 @@ TEST(Initializer, PetNameCreation) { conf.provision.device_id = ""; auto storage = INvStorage::newStorage(conf.storage); auto http = std::make_shared(temp_dir2.Path()); - KeyManager keys(storage, conf.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); std::string devid; EXPECT_TRUE(storage->loadDeviceId(&devid)); @@ -254,8 +257,8 @@ TEST(Initializer, PetNameCreation) { auto storage = INvStorage::newStorage(conf.storage); auto http = std::make_shared(temp_dir3.Path()); - KeyManager keys(storage, conf.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); std::string devid; EXPECT_TRUE(storage->loadDeviceId(&devid)); @@ -288,7 +291,7 @@ class HttpFakeDeviceRegistration : public HttpFake { }; /* Detect and recover from failed device provisioning. */ -TEST(Initializer, DeviceRegistration) { +TEST(Provisioner, DeviceRegistration) { TemporaryDirectory temp_dir; auto http = std::make_shared(temp_dir.Path()); Config conf("tests/config/basic.toml"); @@ -299,30 +302,30 @@ TEST(Initializer, DeviceRegistration) { conf.provision.primary_ecu_serial = "testecuserial"; auto storage = INvStorage::newStorage(conf.storage); - KeyManager keys(storage, conf.keymanagerConfig()); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); // Force a failure from the fake server due to device already registered. { http->retcode = InitRetCode::kOccupied; - EXPECT_THROW(Initializer(conf.provision, storage, http, keys, {}), Initializer::Error); + ExpectProvisionError(Provisioner(conf.provision, storage, http, keys, {}), "already registered"); } // Force an arbitrary failure from the fake server. { http->retcode = InitRetCode::kServerFailure; - EXPECT_THROW(Initializer(conf.provision, storage, http, keys, {}), Initializer::ServerError); + ExpectProvisionError(Provisioner(conf.provision, storage, http, keys, {}), "Server error"); } // Don't force a failure and make sure it actually works this time. { http->retcode = InitRetCode::kOk; - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); } } class HttpFakeEcuRegistration : public HttpFake { public: - HttpFakeEcuRegistration(const boost::filesystem::path& test_dir_in) : HttpFake(test_dir_in) {} + explicit HttpFakeEcuRegistration(const boost::filesystem::path& test_dir_in) : HttpFake(test_dir_in) {} HttpResponse post(const std::string& url, const Json::Value& data) override { if (url.find("/director/ecus") != std::string::npos) { @@ -343,7 +346,7 @@ class HttpFakeEcuRegistration : public HttpFake { }; /* Detect and recover from failed ECU registration. */ -TEST(Initializer, EcuRegisteration) { +TEST(Provisioner, EcuRegisteration) { TemporaryDirectory temp_dir; auto http = std::make_shared(temp_dir.Path()); Config conf("tests/config/basic.toml"); @@ -354,29 +357,30 @@ TEST(Initializer, EcuRegisteration) { conf.provision.primary_ecu_serial = "testecuserial"; auto storage = INvStorage::newStorage(conf.storage); - KeyManager keys(storage, conf.keymanagerConfig()); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); // Force a failure from the fake server due to ECUs already registered. { http->retcode = InitRetCode::kOccupied; - EXPECT_THROW(Initializer(conf.provision, storage, http, keys, {}), Initializer::ServerError); + ExpectProvisionError(Provisioner(conf.provision, storage, http, keys, {}), + "ECUs are unexpectedly already registered"); } // Force an arbitary failure from the fake server. { http->retcode = InitRetCode::kServerFailure; - EXPECT_THROW(Initializer(conf.provision, storage, http, keys, {}), Initializer::ServerError); + ExpectProvisionError(Provisioner(conf.provision, storage, http, keys, {}), "Server error"); } // Don't force a failure and make sure it actually works this time. { http->retcode = InitRetCode::kOk; - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); } } /* Use the system hostname as hardware ID if one is not provided. */ -TEST(Initializer, HostnameAsHardwareID) { +TEST(Provisioner, HostnameAsHardwareID) { TemporaryDirectory temp_dir; Config conf("tests/config/basic.toml"); conf.storage.path = temp_dir.Path(); @@ -387,10 +391,10 @@ TEST(Initializer, HostnameAsHardwareID) { { auto storage = INvStorage::newStorage(conf.storage); auto http = std::make_shared(temp_dir.Path()); - KeyManager keys(storage, conf.keymanagerConfig()); + auto keys = std::make_shared(storage, conf.keymanagerConfig()); EXPECT_TRUE(conf.provision.primary_ecu_hardware_id.empty()); - EXPECT_NO_THROW(Initializer(conf.provision, storage, http, keys, {})); + ExpectProvisionOK(Provisioner(conf.provision, storage, http, keys, {})); EcuSerials ecu_serials; EXPECT_TRUE(storage->loadEcuSerials(&ecu_serials)); diff --git a/src/libaktualizr/primary/provisioner_test_utils.cc b/src/libaktualizr/primary/provisioner_test_utils.cc new file mode 100644 index 0000000000..7f67b876cc --- /dev/null +++ b/src/libaktualizr/primary/provisioner_test_utils.cc @@ -0,0 +1,31 @@ + +#include +#include "primary/provisioner.h" + +// Test utility to run provisioning to completion and check the result +void ExpectProvisionOK(Provisioner&& provisioner) { + int attempts = 0; + bool last_attempt = false; + while (provisioner.ShouldAttemptAgain()) { + EXPECT_FALSE(last_attempt) << "Provisioner::Attempt() should return false iff ShouldAttemptAgain()"; + last_attempt = provisioner.Attempt(); + // Avoid infinite loops if the ShouldAttemptAgain doesn't work right + attempts++; + EXPECT_LE(attempts, 100) << "Far too many Provisioning attempts!"; + } + EXPECT_TRUE(last_attempt) << "Provisioner::Attempt() should return false iff ShouldAttemptAgain()"; + EXPECT_EQ(provisioner.CurrentState(), Provisioner::State::kOk); +} + +void ExpectProvisionError(Provisioner&& provisioner, const std::string& match = "") { + bool last_attempt; + for (int attempt = 0; attempt < 3; attempt++) { + EXPECT_TRUE(provisioner.ShouldAttemptAgain()); + last_attempt = provisioner.Attempt(); + EXPECT_FALSE(last_attempt) << "Provisioner::Attempt() should return false iff ShouldAttemptAgain()"; + } + EXPECT_TRUE(provisioner.ShouldAttemptAgain()) << "Expecting provisioning to fail"; + auto err_message = provisioner.LastError(); + auto matches = err_message.find(match); + EXPECT_NE(matches, std::string::npos) << "Error message didn't contain " << match << " actual:" << err_message; +} \ No newline at end of file diff --git a/src/libaktualizr/primary/provisioner_test_utils.h b/src/libaktualizr/primary/provisioner_test_utils.h new file mode 100644 index 0000000000..9fb2a92726 --- /dev/null +++ b/src/libaktualizr/primary/provisioner_test_utils.h @@ -0,0 +1,11 @@ + +#ifndef AKTUALIZR_PROVISIONER_TEST_UTILS_H +#define AKTUALIZR_PROVISIONER_TEST_UTILS_H + +// TODO documenti +void ExpectProvisionOK(Provisioner&& provisioner); + +// TODO documenti +void ExpectProvisionError(Provisioner&& provisioner, const std::string& match = ""); + +#endif // AKTUALIZR_PROVISIONER_TEST_UTILS_H \ No newline at end of file diff --git a/src/libaktualizr/primary/reregistration_test.cc b/src/libaktualizr/primary/reregistration_test.cc index ae8aeb3a52..c822a92f57 100644 --- a/src/libaktualizr/primary/reregistration_test.cc +++ b/src/libaktualizr/primary/reregistration_test.cc @@ -4,7 +4,6 @@ #include #include "httpfake.h" -#include "libaktualizr/aktualizr.h" #include "libaktualizr/config.h" #include "uptane_test_common.h" #include "virtualsecondary.h" diff --git a/src/libaktualizr/primary/sotauptaneclient.cc b/src/libaktualizr/primary/sotauptaneclient.cc index bce77a5acc..5ff6e4d137 100644 --- a/src/libaktualizr/primary/sotauptaneclient.cc +++ b/src/libaktualizr/primary/sotauptaneclient.cc @@ -1,18 +1,15 @@ #include "sotauptaneclient.h" #include -#include #include #include #include "crypto/crypto.h" #include "crypto/keymanager.h" -#include "initializer.h" #include "libaktualizr/campaign.h" #include "logging/logging.h" +#include "provisioner.h" #include "uptane/exceptions.h" - -#include "utilities/fault_injection.h" #include "utilities/utils.h" static void report_progress_cb(event::Channel *channel, const Uptane::Target &target, const std::string &description, @@ -24,6 +21,24 @@ static void report_progress_cb(event::Channel *channel, const Uptane::Target &ta (*channel)(event); } +SotaUptaneClient::SotaUptaneClient(Config &config_in, std::shared_ptr storage_in, + std::shared_ptr http_in, + std::shared_ptr events_channel_in, + const Uptane::EcuSerial &primary_serial, const Uptane::HardwareIdentifier &hwid) + : config(config_in), + storage(std::move(storage_in)), + http(std::move(http_in)), + package_manager_(PackageManagerFactory::makePackageManager(config.pacman, config.bootloader, storage, http)), + key_manager_(std::make_shared(storage, config.keymanagerConfig())), + uptane_fetcher(new Uptane::Fetcher(config, http)), + events_channel(std::move(events_channel_in)), + primary_ecu_serial_(primary_serial), + primary_ecu_hw_id_(hwid), + provisioner_(config.provision, storage, http, key_manager_, secondaries) { + report_queue = std_::make_unique(config, http, storage); + secondary_provider_ = SecondaryProviderBuilder::Build(config, storage, package_manager_); +} + void SotaUptaneClient::addSecondary(const std::shared_ptr &sec) { Uptane::EcuSerial serial = sec->getSerial(); @@ -33,6 +48,8 @@ void SotaUptaneClient::addSecondary(const std::shared_ptr &s } secondaries.emplace(serial, sec); + sec->init(secondary_provider_); + provisioner_.SecondariesWereChanged(); } std::vector SotaUptaneClient::findForEcu(const std::vector &targets, @@ -344,29 +361,30 @@ bool SotaUptaneClient::hasPendingUpdates() const { return storage->hasPendingIns void SotaUptaneClient::initialize() { LOG_DEBUG << "Checking if device is provisioned..."; - auto keys = std::make_shared(storage, config.keymanagerConfig()); - Initializer initializer(config.provision, storage, http, *keys, secondaries); + bool provisioned = false; + for (int i = 0; i < 3 && !provisioned; i++) { + provisioned = provisioner_.Attempt(); + } + + // This is temporary. For offline updates it will be necessary to + // run updates before provisioning has completed. + if (!provisioned) { + throw std::runtime_error("Initialization failed after 3 attempts"); + } EcuSerials serials; - /* unlikely, post-condition of Initializer::Initializer() */ + /* unlikely, post-condition of Provisioner::Attempt() returning true */ if (!storage->loadEcuSerials(&serials) || serials.empty()) { throw std::runtime_error("Unable to load ECU serials after device registration."); } - uptane_manifest = std::make_shared(keys, serials[0].first); + uptane_manifest = std::make_shared(key_manager_, serials[0].first); + // TODO: Move this debug logging out to somewhere else primary_ecu_serial_ = serials[0].first; primary_ecu_hw_id_ = serials[0].second; LOG_INFO << "Primary ECU serial: " << primary_ecu_serial_ << " with hardware ID: " << primary_ecu_hw_id_; - for (auto it = secondaries.begin(); it != secondaries.end(); ++it) { - try { - it->second->init(secondary_provider_); - } catch (const std::exception &ex) { - LOG_ERROR << "Failed to initialize Secondary with serial " << it->first << ": " << ex.what(); - } - } - std::string device_id; if (!storage->loadDeviceId(&device_id)) { throw std::runtime_error("Unable to load device ID after device registration."); @@ -378,7 +396,7 @@ void SotaUptaneClient::initialize() { std::string issuer; std::string not_before; std::string not_after; - keys->getCertInfo(&subject, &issuer, ¬_before, ¬_after); + key_manager_->getCertInfo(&subject, &issuer, ¬_before, ¬_after); LOG_INFO << "Certificate subject: " << subject; LOG_INFO << "Certificate issuer: " << issuer; LOG_INFO << "Certificate valid from: " << not_before << " until: " << not_after; diff --git a/src/libaktualizr/primary/sotauptaneclient.h b/src/libaktualizr/primary/sotauptaneclient.h index 156f43f95f..184036f027 100644 --- a/src/libaktualizr/primary/sotauptaneclient.h +++ b/src/libaktualizr/primary/sotauptaneclient.h @@ -22,6 +22,7 @@ #include "bootloader/bootloader.h" #include "http/httpclient.h" #include "primary/secondary_provider_builder.h" +#include "provisioner.h" #include "reportqueue.h" #include "uptane/directorrepository.h" #include "uptane/exceptions.h" @@ -37,18 +38,7 @@ class SotaUptaneClient { SotaUptaneClient(Config &config_in, std::shared_ptr storage_in, std::shared_ptr http_in, std::shared_ptr events_channel_in, const Uptane::EcuSerial &primary_serial = Uptane::EcuSerial::Unknown(), - const Uptane::HardwareIdentifier &hwid = Uptane::HardwareIdentifier::Unknown()) - : config(config_in), - storage(std::move(storage_in)), - http(std::move(http_in)), - package_manager_(PackageManagerFactory::makePackageManager(config.pacman, config.bootloader, storage, http)), - uptane_fetcher(new Uptane::Fetcher(config, http)), - events_channel(std::move(events_channel_in)), - primary_ecu_serial_(primary_serial), - primary_ecu_hw_id_(hwid) { - report_queue = std_::make_unique(config, http, storage); - secondary_provider_ = SecondaryProviderBuilder::Build(config, storage, package_manager_); - } + const Uptane::HardwareIdentifier &hwid = Uptane::HardwareIdentifier::Unknown()); SotaUptaneClient(Config &config_in, const std::shared_ptr &storage_in, std::shared_ptr http_in) @@ -178,6 +168,7 @@ class SotaUptaneClient { std::shared_ptr storage; std::shared_ptr http; std::shared_ptr package_manager_; + std::shared_ptr key_manager_; std::shared_ptr uptane_fetcher; std::unique_ptr report_queue; std::shared_ptr secondary_provider_; @@ -189,6 +180,7 @@ class SotaUptaneClient { std::mutex download_mutex; Uptane::EcuSerial primary_ecu_serial_; Uptane::HardwareIdentifier primary_ecu_hw_id_; + Provisioner provisioner_; }; class TargetCompare { diff --git a/src/libaktualizr/uptane/CMakeLists.txt b/src/libaktualizr/uptane/CMakeLists.txt index 92fcdcdc5a..85b915c6d4 100644 --- a/src/libaktualizr/uptane/CMakeLists.txt +++ b/src/libaktualizr/uptane/CMakeLists.txt @@ -35,10 +35,14 @@ else(BUILD_OSTREE AND SOTA_PACKED_CREDENTIALS) endif(BUILD_OSTREE AND SOTA_PACKED_CREDENTIALS) -add_aktualizr_test(NAME uptane SOURCES uptane_test.cc PROJECT_WORKING_DIRECTORY LIBRARIES uptane_generator_lib) -set_property(SOURCE uptane_test.cc PROPERTY COMPILE_DEFINITIONS TEST_PKCS11_MODULE_PATH="${TEST_PKCS11_MODULE_PATH}") +add_aktualizr_test(NAME uptane + SOURCES uptane_test.cc + PROJECT_WORKING_DIRECTORY + LIBRARIES uptane_generator_lib provisioner_test_utils virtual_secondary) +set_property(SOURCE uptane_test.cc + PROPERTY COMPILE_DEFINITIONS + TEST_PKCS11_MODULE_PATH="${TEST_PKCS11_MODULE_PATH}") set_tests_properties(test_uptane PROPERTIES LABELS "crypto") -target_link_libraries(t_uptane virtual_secondary) add_aktualizr_test(NAME uptane_delegation SOURCES uptane_delegation_test.cc PROJECT_WORKING_DIRECTORY ARGS "$" LIBRARIES uptane_generator_lib) diff --git a/src/libaktualizr/uptane/uptane_network_test.cc b/src/libaktualizr/uptane/uptane_network_test.cc index 2d7213e040..8ca3d10cad 100644 --- a/src/libaktualizr/uptane/uptane_network_test.cc +++ b/src/libaktualizr/uptane/uptane_network_test.cc @@ -17,7 +17,7 @@ #include "http/httpclient.h" #include "httpfake.h" #include "logging/logging.h" -#include "primary/initializer.h" +#include "primary/provisioner.h" #include "primary/sotauptaneclient.h" #include "storage/invstorage.h" #include "test_utils.h" @@ -45,13 +45,9 @@ bool doTestInit(const std::string &device_register_state, const std::string &ecu http->timeout(1000); auto store = INvStorage::newStorage(conf.storage); { - KeyManager keys(store, conf.keymanagerConfig()); - try { - Initializer initializer(conf.provision, store, http, keys, {}); - result = true; - } catch (const std::exception &e) { - result = false; - } + auto keys = std::make_shared(store, conf.keymanagerConfig()); + Provisioner provisioner(conf.provision, store, http, keys, {}); + result = provisioner.Attempt(); } if (device_register_state != "noerrors" || ecu_register_state != "noerrors") { EXPECT_FALSE(result); @@ -60,13 +56,9 @@ bool doTestInit(const std::string &device_register_state, const std::string &ecu conf.provision.expiry_days = "noerrors"; conf.provision.primary_ecu_serial = "noerrors"; - KeyManager keys(store, conf.keymanagerConfig()); - try { - Initializer initializer(conf.provision, store, http, keys, {}); - result = true; - } catch (const std::exception &e) { - result = false; - } + auto keys = std::make_shared(store, conf.keymanagerConfig()); + Provisioner provisioner(conf.provision, store, http, keys, {}); + result = provisioner.Attempt(); } return result; diff --git a/src/libaktualizr/uptane/uptane_test.cc b/src/libaktualizr/uptane/uptane_test.cc index e3008929d0..ff7043986c 100644 --- a/src/libaktualizr/uptane/uptane_test.cc +++ b/src/libaktualizr/uptane/uptane_test.cc @@ -13,11 +13,11 @@ #include #include "json/json.h" -#include "libaktualizr/secondaryinterface.h" - #include "crypto/p11engine.h" #include "httpfake.h" -#include "primary/initializer.h" +#include "libaktualizr/secondaryinterface.h" +#include "primary/provisioner.h" +#include "primary/provisioner_test_utils.h" #include "primary/sotauptaneclient.h" #include "storage/fsstorage_read.h" #include "storage/invstorage.h" @@ -1409,9 +1409,9 @@ TEST(Uptane, Pkcs11Provision) { auto storage = INvStorage::newStorage(config.storage); storage->importData(config.import); auto http = std::make_shared(temp_dir.Path(), "hasupdates"); - KeyManager keys(storage, config.keymanagerConfig()); + auto keys = std::make_shared(storage, config.keymanagerConfig()); - EXPECT_NO_THROW(Initializer(config.provision, storage, http, keys, {})); + ExpectProvisionOK(Provisioner(config.provision, storage, http, keys, {})); } #endif