diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fb64498b7..74554f97ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ Our versioning scheme is `YEAR.N` where `N` is incremented whenever a new releas ## [??? (unreleased)] +### Added + +- Ostree update on IP Secondaries: [PR](https://github.com/advancedtelematic/aktualizr/pull/1500) + ## [2019.11] - 2019-12-12 ### Added diff --git a/src/aktualizr_get/get.cc b/src/aktualizr_get/get.cc index 2a682383ba..5b471c9f17 100644 --- a/src/aktualizr_get/get.cc +++ b/src/aktualizr_get/get.cc @@ -1,6 +1,7 @@ #include "get.h" #include "crypto/keymanager.h" #include "http/httpclient.h" +#include "storage/invstorage.h" std::string aktualizrGet(Config &config, const std::string &url) { auto storage = INvStorage::newStorage(config.storage); diff --git a/src/aktualizr_primary/secondary.cc b/src/aktualizr_primary/secondary.cc index 248f9ec33f..8d3011046d 100644 --- a/src/aktualizr_primary/secondary.cc +++ b/src/aktualizr_primary/secondary.cc @@ -106,9 +106,9 @@ class SecondaryWaiter { LOG_INFO << "Accepted connection from a secondary: (" << sec_ip << ":" << sec_port << ")"; try { - auto sec_creation_res = Uptane::IpUptaneSecondary::create(sec_ip, sec_port, con_socket_.native_handle()); - if (sec_creation_res.first) { - connected_secondaries_.push_back(sec_creation_res.second); + auto sec = Uptane::IpUptaneSecondary::create(sec_ip, sec_port, con_socket_.native_handle()); + if (sec != nullptr) { + connected_secondaries_.push_back(sec); } } catch (const std::exception& exc) { LOG_ERROR << "Failed to initialize a secondary: " << exc.what(); @@ -146,9 +146,9 @@ static Secondaries createIPSecondaries(const IPSecondariesConfig& config) { SecondaryWaiter sec_waiter{config.secondaries_wait_port, config.secondaries_timeout_s, result}; for (auto& ip_sec_cfg : config.secondaries_cfg) { - auto sec_creation_res = Uptane::IpUptaneSecondary::connectAndCreate(ip_sec_cfg.ip, ip_sec_cfg.port); - if (sec_creation_res.first) { - result.push_back(sec_creation_res.second); + auto sec = Uptane::IpUptaneSecondary::connectAndCreate(ip_sec_cfg.ip, ip_sec_cfg.port); + if (sec != nullptr) { + result.push_back(sec); } else { sec_waiter.addSecondary(ip_sec_cfg.ip, ip_sec_cfg.port); } diff --git a/src/aktualizr_secondary/CMakeLists.txt b/src/aktualizr_secondary/CMakeLists.txt index be66d9fd2f..eabe09a7ec 100644 --- a/src/aktualizr_secondary/CMakeLists.txt +++ b/src/aktualizr_secondary/CMakeLists.txt @@ -1,12 +1,12 @@ set(AKTUALIZR_SECONDARY_SRC main.cc) set(AKTUALIZR_SECONDARY_LIB_SRC + update_agent_file.cc aktualizr_secondary.cc aktualizr_secondary_config.cc - aktualizr_secondary_common.cc aktualizr_secondary_metadata.cc - socket_server.cc - ) + secondary_tcp_server.cc + aktualizr_secondary_factory.cc) # do not link tests with libaktualizr list(REMOVE_ITEM TEST_LIBS aktualizr_lib) @@ -26,18 +26,19 @@ add_library(aktualizr_secondary_lib SHARED $ $ $ - $) + $ + $) target_link_libraries(aktualizr_secondary_lib ${AKTUALIZR_EXTERNAL_LIBS}) target_include_directories(aktualizr_secondary_lib PUBLIC $ - ${PROJECT_SOURCE_DIR}/src/libaktualizr-posix - ) + ${PROJECT_SOURCE_DIR}/src/libaktualizr-posix) if (BUILD_ISOTP) target_sources(aktualizr_secondary_lib PRIVATE $) endif (BUILD_ISOTP) add_executable(aktualizr-secondary ${AKTUALIZR_SECONDARY_SRC}) + target_link_libraries(aktualizr-secondary aktualizr_secondary_lib) install(TARGETS aktualizr_secondary_lib LIBRARY DESTINATION lib COMPONENT aktualizr) @@ -46,13 +47,13 @@ install(TARGETS aktualizr-secondary RUNTIME DESTINATION bin) set(ALL_AKTUALIZR_SECONDARY_HEADERS + update_agent.h + update_agent_file.h aktualizr_secondary.h - aktualizr_secondary_interface.h aktualizr_secondary_config.h - aktualizr_secondary_common.h aktualizr_secondary_metadata.h - socket_server.h - ) + secondary_tcp_server.h + aktualizr_secondary_factory.h) include(AddAktualizrTest) @@ -60,31 +61,33 @@ include(AddAktualizrTest) list(INSERT TEST_LIBS 0 aktualizr_secondary_lib) add_aktualizr_test(NAME aktualizr_secondary_config - SOURCES aktualizr_secondary_config_test.cc PROJECT_WORKING_DIRECTORY LIBRARIES aktualizr_secondary_lib) + SOURCES aktualizr_secondary_config_test.cc PROJECT_WORKING_DIRECTORY) -add_aktualizr_test(NAME aktualizr_secondary_update - SOURCES update_test.cc - ARGS ${PROJECT_BINARY_DIR}/ostree_repo - PROJECT_WORKING_DIRECTORY LIBRARIES aktualizr_secondary_lib) +add_aktualizr_test(NAME secondary_tcp_server + SOURCES secondary_tcp_server_test.cc PROJECT_WORKING_DIRECTORY) if(BUILD_OSTREE) - add_aktualizr_test(NAME aktualizr_secondary_uptane_verification - SOURCES uptane_verification_test.cc - ARGS ${PROJECT_BINARY_DIR}/ostree_repo - LIBRARIES aktualizr_secondary_lib uptane_generator_lib $ - PROJECT_WORKING_DIRECTORY ) - - set_target_properties(t_aktualizr_secondary_uptane_verification PROPERTIES LINK_FLAGS -Wl,--export-dynamic) - target_link_libraries(t_aktualizr_secondary_uptane_verification aktualizr_secondary_lib uptane_generator_lib) - - add_aktualizr_test(NAME aktualizr_secondary_uptane - SOURCES uptane_test.cc - LIBRARIES aktualizr_secondary_lib uptane_generator_lib virtual_secondary $ $ $ + target_sources(aktualizr_secondary_lib PRIVATE update_agent_ostree.cc) + list(APPEND AKTUALIZR_SECONDARY_LIB_SRC update_agent_ostree.cc) + list(APPEND ALL_AKTUALIZR_SECONDARY_HEADERS update_agent_ostree.h) + + add_aktualizr_test(NAME aktualizr_secondary_ostree SOURCES aktualizr_secondary_ostree_test.cc ARGS ${PROJECT_BINARY_DIR}/ostree_repo PROJECT_WORKING_DIRECTORY) + + set_target_properties(t_aktualizr_secondary_ostree PROPERTIES LINK_FLAGS -Wl,--export-dynamic) + target_link_libraries(t_aktualizr_secondary_ostree aktualizr_secondary_lib uptane_generator_lib) + else(BUILD_OSTREE) - list(APPEND TEST_SOURCES uptane_verification_test.cc uptane_test.cc) + list(APPEND TEST_SOURCES aktualizr_secondary_ostree_test.cc update_agent_ostree.cc) + list(APPEND ALL_AKTUALIZR_SECONDARY_HEADERS update_agent_ostree.h) endif(BUILD_OSTREE) +add_aktualizr_test(NAME aktualizr_secondary + SOURCES aktualizr_secondary_test.cc + PROJECT_WORKING_DIRECTORY) + +target_link_libraries(t_aktualizr_secondary aktualizr_secondary_lib uptane_generator_lib) + # test running the executable with command line option --help add_test(NAME aktualizr_secondary_cmdline--help COMMAND aktualizr-secondary --help) # test running the executable with command line option --something diff --git a/src/aktualizr_secondary/aktualizr_secondary.cc b/src/aktualizr_secondary/aktualizr_secondary.cc index fc9f718aa5..bc13a92e14 100644 --- a/src/aktualizr_secondary/aktualizr_secondary.cc +++ b/src/aktualizr_secondary/aktualizr_secondary.cc @@ -1,73 +1,68 @@ #include "aktualizr_secondary.h" -#include -#include - +#include "crypto/keymanager.h" #include "logging/logging.h" -#ifdef BUILD_OSTREE -#include "package_manager/ostreemanager.h" // TODO: Hide behind PackageManagerInterface -#endif -#include "socket_server.h" +#include "update_agent.h" +#include "uptane/manifest.h" #include "utilities/utils.h" -class SecondaryAdapter : public Uptane::SecondaryInterface { - public: - SecondaryAdapter(AktualizrSecondary& sec) : secondary(sec) {} - ~SecondaryAdapter() override = default; - - Uptane::EcuSerial getSerial() override { return secondary.getSerialResp(); } - Uptane::HardwareIdentifier getHwId() override { return secondary.getHwIdResp(); } - PublicKey getPublicKey() override { return secondary.getPublicKeyResp(); } - Json::Value getManifest() override { return secondary.getManifestResp(); } - bool putMetadata(const Uptane::RawMetaPack& meta_pack) override { - return secondary.putMetadataResp(Metadata(meta_pack)); - } - int32_t getRootVersion(bool director) override { return secondary.getRootVersionResp(director); } - bool putRoot(const std::string& root, bool director) override { return secondary.putRootResp(root, director); } - bool sendFirmware(const std::shared_ptr& data) override { - return secondary.AktualizrSecondary::sendFirmwareResp(data); - } +#include +#include - private: - AktualizrSecondary& secondary; -}; - -AktualizrSecondary::AktualizrSecondary(const AktualizrSecondaryConfig& config, - const std::shared_ptr& storage) - : AktualizrSecondaryCommon(config, storage), - socket_server_(std_::make_unique(*this), SocketFromPort(config.network.port)) { - // note: we don't use TlsConfig here and supply the default to - // KeyManagerConf. Maybe we should figure a cleaner way to do that - // (split KeyManager?) +AktualizrSecondary::AktualizrSecondary(AktualizrSecondaryConfig config, std::shared_ptr storage, + std::shared_ptr bootloader, std::shared_ptr key_mngr, + std::shared_ptr update_agent) + : config_(std::move(config)), + storage_(std::move(storage)), + bootloader_(std::move(bootloader)), + keys_(std::move(key_mngr)), + update_agent_(std::move(update_agent)) { if (!uptaneInitialize()) { LOG_ERROR << "Failed to initialize"; return; } -} -void AktualizrSecondary::run() { - connectToPrimary(); - socket_server_.Run(); -} - -void AktualizrSecondary::stop() { /* TODO? */ + manifest_issuer_ = std::make_shared(keys_, ecu_serial_); + + if (rebootDetected()) { + LOG_INFO << "Reboot has been detected, applying the new ostree revision: " << pending_target_.sha256Hash(); + // TODO: refactor this to make it simpler as we don't need to persist/store + // an installation status of each ECU but store it just for a given secondary ECU + std::vector installed_versions; + boost::optional pending_target; + storage_->loadInstalledVersions(ecu_serial_.ToString(), nullptr, &pending_target); + + if (!!pending_target) { + data::InstallationResult install_res = update_agent_->applyPendingInstall(*pending_target); + storage_->saveEcuInstallationResult(ecu_serial_, install_res); + if (install_res.success) { + storage_->saveInstalledVersion(ecu_serial_.ToString(), *pending_target, InstalledVersionUpdateMode::kCurrent); + } else { + storage_->saveInstalledVersion(ecu_serial_.ToString(), *pending_target, InstalledVersionUpdateMode::kNone); + director_repo_.dropTargets(*storage_); + } + } + bootloader_->rebootFlagClear(); + } } -Uptane::EcuSerial AktualizrSecondary::getSerialResp() const { return ecu_serial_; } +Uptane::EcuSerial AktualizrSecondary::getSerial() const { return ecu_serial_; } -Uptane::HardwareIdentifier AktualizrSecondary::getHwIdResp() const { return hardware_id_; } +Uptane::HardwareIdentifier AktualizrSecondary::getHwId() const { return hardware_id_; } -PublicKey AktualizrSecondary::getPublicKeyResp() const { return keys_.UptanePublicKey(); } +PublicKey AktualizrSecondary::getPublicKey() const { return keys_->UptanePublicKey(); } -Json::Value AktualizrSecondary::getManifestResp() const { - Json::Value manifest = pacman->getManifest(getSerialResp()); +Uptane::Manifest AktualizrSecondary::getManifest() const { + Uptane::InstalledImageInfo installed_image_info; + Uptane::Manifest manifest; + if (update_agent_->getInstalledImageInfo(installed_image_info)) { + manifest = manifest_issuer_->assembleAndSignManifest(installed_image_info); + } - return keys_.signTuf(manifest); + return manifest; } -bool AktualizrSecondary::putMetadataResp(const Metadata& metadata) { return doFullVerification(metadata); } - -int32_t AktualizrSecondary::getRootVersionResp(bool director) const { +int32_t AktualizrSecondary::getRootVersion(bool director) const { std::string root_meta; if (!storage_->loadLatestRoot(&root_meta, (director) ? Uptane::RepositoryType::Director() : Uptane::RepositoryType::Image())) { @@ -78,146 +73,61 @@ int32_t AktualizrSecondary::getRootVersionResp(bool director) const { return Uptane::extractVersionUntrusted(root_meta); } -bool AktualizrSecondary::putRootResp(const std::string& root, bool director) { +bool AktualizrSecondary::putRoot(const std::string& root, bool director) { (void)root; (void)director; LOG_ERROR << "putRootResp is not implemented yet"; return false; } -bool AktualizrSecondary::sendFirmwareResp(const std::shared_ptr& firmware) { - auto targetsForThisEcu = director_repo_.getTargets(getSerial(), getHardwareID()); +bool AktualizrSecondary::putMetadata(const Metadata& metadata) { return doFullVerification(metadata); } - if (targetsForThisEcu.size() != 1) { - LOG_ERROR << "Invalid number of targets (should be one): " << targetsForThisEcu.size(); +bool AktualizrSecondary::sendFirmware(const std::string& firmware) { + // TODO: how to handle the case when secondary is rebooted after metadata are received + if (!pending_target_.IsValid()) { + LOG_ERROR << "No any pending target to receive update data/image for"; return false; } - auto target_to_apply = targetsForThisEcu[0]; - - std::string treehub_server; - std::size_t firmware_size = firmware->length(); - - if (target_to_apply.IsOstree()) { - // this is the ostree specific case - try { - std::string ca, cert, pkey, server_url; - extractCredentialsArchive(*firmware, &ca, &cert, &pkey, &server_url); - keys_.loadKeys(&ca, &cert, &pkey); - boost::trim(server_url); - treehub_server = server_url; - firmware_size = 0; - } catch (std::runtime_error& exc) { - LOG_ERROR << exc.what(); - - return false; - } - } - - data::InstallationResult install_res; - - if (target_to_apply.IsOstree()) { -#ifdef BUILD_OSTREE - install_res = OstreeManager::pull(config_.pacman.sysroot, treehub_server, keys_, target_to_apply); - if (install_res.result_code.num_code != data::ResultCode::Numeric::kOk) { - LOG_ERROR << "Could not pull from OSTree (" << install_res.result_code.toString() - << "): " << install_res.description; - return false; - } -#else - LOG_ERROR << "Could not pull from OSTree. Aktualizr was built without OSTree support!"; - return false; -#endif - } else if (pacman->name() == "debian") { - // TODO save debian package here. - LOG_ERROR << "Installation of debian images is not suppotrted yet."; - return false; - } - - if (target_to_apply.length() != firmware_size) { - LOG_ERROR << "The target image size specified in metadata " << target_to_apply.length() - << " does not match actual size " << firmware->length(); + if (!update_agent_->download(pending_target_, firmware)) { + LOG_ERROR << "Failed to pull/store an update data"; + pending_target_ = Uptane::Target::Unknown(); return false; } - if (!target_to_apply.IsOstree()) { - auto target_hashes = target_to_apply.hashes(); - if (target_hashes.size() == 0) { - LOG_ERROR << "No hash found in the target metadata: " << target_to_apply.filename(); - return false; - } - - try { - auto received_image_data_hash = Uptane::Hash::generate(target_hashes[0].type(), *firmware); - - if (!target_to_apply.MatchHash(received_image_data_hash)) { - LOG_ERROR << "The received image data hash doesn't match the hash specified in the target metadata," - " hash type: " - << target_hashes[0].TypeString(); - return false; - } - - } catch (const std::exception& exc) { - LOG_ERROR << "Failed to generate a hash of the received image data: " << exc.what(); - return false; - } - } - - install_res = pacman->install(target_to_apply); - if (install_res.result_code.num_code != data::ResultCode::Numeric::kOk && - install_res.result_code.num_code != data::ResultCode::Numeric::kNeedCompletion) { - LOG_ERROR << "Could not install target (" << install_res.result_code.toString() << "): " << install_res.description; - return false; - } - - if (install_res.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) { - storage_->saveInstalledVersion(getSerialResp().ToString(), target_to_apply, InstalledVersionUpdateMode::kPending); - } else { - storage_->saveInstalledVersion(getSerialResp().ToString(), target_to_apply, InstalledVersionUpdateMode::kCurrent); - } - - // TODO: https://saeljira.it.here.com/browse/OTA-4174 - // - return data::InstallationResult to Primary - // - add finaliztion support, pacman->finalizeInstall() must be called at secondary startup if there is pending - // version - return true; } -void AktualizrSecondary::extractCredentialsArchive(const std::string& archive, std::string* ca, std::string* cert, - std::string* pkey, std::string* treehub_server) { - { - std::stringstream as(archive); - *ca = Utils::readFileFromArchive(as, "ca.pem"); - } - { - std::stringstream as(archive); - *cert = Utils::readFileFromArchive(as, "client.pem"); - } - { - std::stringstream as(archive); - *pkey = Utils::readFileFromArchive(as, "pkey.pem"); +data::ResultCode::Numeric AktualizrSecondary::install(const std::string& target_name) { + // TODO: how to handle the case when secondary is rebooted after metadata are received + if (!pending_target_.IsValid()) { + LOG_ERROR << "No any pending target to receive update data/image for"; + return data::ResultCode::Numeric::kInternalError; } - { - std::stringstream as(archive); - *treehub_server = Utils::readFileFromArchive(as, "server.url", true); + + if (pending_target_.filename() != target_name) { + LOG_ERROR << "name of the target to install and a name of the pending target do not match"; + return data::ResultCode::Numeric::kInternalError; } -} -void AktualizrSecondary::connectToPrimary() { - Socket socket(config_.network.primary_ip, config_.network.primary_port); + auto install_result = update_agent_->install(pending_target_); - if (socket.bind(config_.network.port) != 0) { - LOG_ERROR << "Failed to bind a connection socket to the secondary's port"; - return; + switch (install_result) { + case data::ResultCode::Numeric::kOk: { + storage_->saveInstalledVersion(ecu_serial_.ToString(), pending_target_, InstalledVersionUpdateMode::kCurrent); + pending_target_ = Uptane::Target::Unknown(); + LOG_INFO << "The target has been successfully installed: " << target_name; + break; + } + case data::ResultCode::Numeric::kNeedCompletion: { + storage_->saveInstalledVersion(ecu_serial_.ToString(), pending_target_, InstalledVersionUpdateMode::kPending); + LOG_INFO << "The target has been successfully installed, but a reboot is required to be applied: " << target_name; + break; + } + default: { LOG_INFO << "Failed to install the target: " << target_name; } } - if (socket.connect() == 0) { - LOG_INFO << "Connected to Primary, sending info about this secondary..."; - socket_server_.HandleOneConnection(socket.getFD()); - } else { - LOG_INFO << "Failed to connect to Primary"; - } + return install_result; } bool AktualizrSecondary::doFullVerification(const Metadata& metadata) { @@ -249,13 +159,6 @@ bool AktualizrSecondary::doFullVerification(const Metadata& metadata) { return false; } - auto targetsForThisEcu = director_repo_.getTargets(getSerial(), getHardwareID()); - - if (targetsForThisEcu.size() != 1) { - LOG_ERROR << "Invalid number of targets (should be 1): " << targetsForThisEcu.size(); - return false; - } - // 6. Download and check the Root metadata file from the Image repository, following the procedure in Section 5.4.4.3. // 7. Download and check the Timestamp metadata file from the Image repository, following the procedure in // Section 5.4.4.4. @@ -269,10 +172,61 @@ bool AktualizrSecondary::doFullVerification(const Metadata& metadata) { } // 10. Verify that Targets metadata from the Director and Image repositories match. - if (!(director_repo_.getTargets() == *image_repo_.getTargets())) { + if (!director_repo_.matchTargetsWithImageTargets(*(image_repo_.getTargets()))) { LOG_ERROR << "Targets metadata from the Director and Image repositories DOES NOT match "; return false; } + auto targetsForThisEcu = director_repo_.getTargets(getSerial(), getHwId()); + + if (targetsForThisEcu.size() != 1) { + LOG_ERROR << "Invalid number of targets (should be 1): " << targetsForThisEcu.size(); + return false; + } + + if (!update_agent_->isTargetSupported(targetsForThisEcu[0])) { + LOG_ERROR << "The given target type is not supported: " << targetsForThisEcu[0].type(); + return false; + } + + pending_target_ = targetsForThisEcu[0]; + + return true; +} + +bool AktualizrSecondary::uptaneInitialize() { + if (keys_->generateUptaneKeyPair().size() == 0) { + LOG_ERROR << "Failed to generate uptane key pair"; + return false; + } + + // from uptane/initialize.cc but we only take care of our own serial/hwid + EcuSerials ecu_serials; + + if (storage_->loadEcuSerials(&ecu_serials)) { + ecu_serial_ = ecu_serials[0].first; + hardware_id_ = ecu_serials[0].second; + + return true; + } + + std::string ecu_serial_local = config_.uptane.ecu_serial; + if (ecu_serial_local.empty()) { + ecu_serial_local = keys_->UptanePublicKey().KeyId(); + } + + std::string ecu_hardware_id = config_.uptane.ecu_hardware_id; + if (ecu_hardware_id.empty()) { + ecu_hardware_id = Utils::getHostname(); + if (ecu_hardware_id == "") { + return false; + } + } + + ecu_serials.emplace_back(Uptane::EcuSerial(ecu_serial_local), Uptane::HardwareIdentifier(ecu_hardware_id)); + storage_->storeEcuSerials(ecu_serials); + ecu_serial_ = ecu_serials[0].first; + hardware_id_ = ecu_serials[0].second; + return true; } diff --git a/src/aktualizr_secondary/aktualizr_secondary.h b/src/aktualizr_secondary/aktualizr_secondary.h index 4d62cb068f..8d4174f2e2 100644 --- a/src/aktualizr_secondary/aktualizr_secondary.h +++ b/src/aktualizr_secondary/aktualizr_secondary.h @@ -1,50 +1,66 @@ #ifndef AKTUALIZR_SECONDARY_H #define AKTUALIZR_SECONDARY_H -#include - -#include "aktualizr_secondary_common.h" #include "aktualizr_secondary_config.h" -#include "aktualizr_secondary_interface.h" -#include "crypto/keymanager.h" -#include "socket_server.h" -#include "storage/invstorage.h" -#include "utilities/types.h" -#include "utilities/utils.h" +#include "aktualizr_secondary_metadata.h" +#include "uptane/secondaryinterface.h" #include "uptane/directorrepository.h" #include "uptane/imagesrepository.h" -#include "uptane/tuf.h" +#include "uptane/manifest.h" -#include "aktualizr_secondary_metadata.h" +class UpdateAgent; +class INvStorage; +class Bootloader; +class KeyManager; -class AktualizrSecondary : public AktualizrSecondaryInterface, private AktualizrSecondaryCommon { +class AktualizrSecondary : public Uptane::SecondaryInterface { public: - AktualizrSecondary(const AktualizrSecondaryConfig& config, const std::shared_ptr& storage); - void run() override; - void stop() override; - - // implementation of primary's SecondaryInterface - Uptane::EcuSerial getSerialResp() const; - Uptane::HardwareIdentifier getHwIdResp() const; - PublicKey getPublicKeyResp() const; - Json::Value getManifestResp() const; - bool putMetadataResp(const Metadata& metadata); - int32_t getRootVersionResp(bool director) const; - bool putRootResp(const std::string& root, bool director); - bool sendFirmwareResp(const std::shared_ptr& firmware); - - static void extractCredentialsArchive(const std::string& archive, std::string* ca, std::string* cert, - std::string* pkey, std::string* treehub_server); + using Ptr = std::shared_ptr; + + public: + // TODO: free AktualizrSecondary from dependencies as much as possible, e.g. bootloader + AktualizrSecondary(AktualizrSecondaryConfig config, std::shared_ptr storage, + std::shared_ptr bootloader, std::shared_ptr key_mngr, + std::shared_ptr update_agent); + + Uptane::EcuSerial getSerial() const override; + Uptane::HardwareIdentifier getHwId() const override; + PublicKey getPublicKey() const override; + + Uptane::Manifest getManifest() const override; + bool putMetadata(const Uptane::RawMetaPack& meta_pack) override { return putMetadata(Metadata(meta_pack)); } + int32_t getRootVersion(bool director) const override; + bool putRoot(const std::string& root, bool director) override; + bool sendFirmware(const std::string& firmware) override; + data::ResultCode::Numeric install(const std::string& target_name) override; + + bool putMetadata(const Metadata& metadata); private: - void connectToPrimary(); + bool rebootDetected() { return bootloader_->rebootDetected() && storage_->hasPendingInstall(); } bool doFullVerification(const Metadata& metadata); + bool uptaneInitialize(); private: - SocketServer socket_server_; + // Uptane verification Uptane::DirectorRepository director_repo_; Uptane::ImagesRepository image_repo_; + + // installation + Uptane::Target pending_target_{Uptane::Target::Unknown()}; + + AktualizrSecondaryConfig config_; + std::shared_ptr storage_; + std::shared_ptr bootloader_; + + std::shared_ptr keys_; + Uptane::ManifestIssuer::Ptr manifest_issuer_; + + Uptane::EcuSerial ecu_serial_{Uptane::EcuSerial::Unknown()}; + Uptane::HardwareIdentifier hardware_id_{Uptane::HardwareIdentifier::Unknown()}; + + std::shared_ptr update_agent_; }; #endif // AKTUALIZR_SECONDARY_H diff --git a/src/aktualizr_secondary/aktualizr_secondary_common.cc b/src/aktualizr_secondary/aktualizr_secondary_common.cc deleted file mode 100644 index 1bcde1cc81..0000000000 --- a/src/aktualizr_secondary/aktualizr_secondary_common.cc +++ /dev/null @@ -1,63 +0,0 @@ -#include "aktualizr_secondary_common.h" -#include "package_manager/packagemanagerfactory.h" -#include "utilities/utils.h" - -AktualizrSecondaryCommon::AktualizrSecondaryCommon(const AktualizrSecondaryConfig &config, - std::shared_ptr storage) - : config_(config), - storage_(std::move(storage)), - keys_(storage_, config.keymanagerConfig()), - ecu_serial_(Uptane::EcuSerial::Unknown()), - hardware_id_(Uptane::HardwareIdentifier::Unknown()) { - pacman = PackageManagerFactory::makePackageManager(config_.pacman, config_.bootloader, storage_, nullptr); - - // Load Root keys from storage - std::string root; - storage_->loadLatestRoot(&root, Uptane::RepositoryType::Director()); - if (root.size() > 0) { - LOG_DEBUG << "Loading root.json:" << root; - root_ = Uptane::Root(Uptane::RepositoryType::Director(), Utils::parseJSON(root)); - } else { - LOG_INFO << "No root.json in local storage, defaulting will accept the first root.json provided"; - root_ = Uptane::Root(Uptane::Root::Policy::kAcceptAll); - } -} - -bool AktualizrSecondaryCommon::uptaneInitialize() { - if (keys_.generateUptaneKeyPair().size() == 0) { - LOG_ERROR << "Failed to generate uptane key pair"; - return false; - } - - // from uptane/initialize.cc but we only take care of our own serial/hwid - EcuSerials ecu_serials; - - if (storage_->loadEcuSerials(&ecu_serials)) { - ecu_serial_ = ecu_serials[0].first; - hardware_id_ = ecu_serials[0].second; - - return true; - } - - std::string ecu_serial_local = config_.uptane.ecu_serial; - if (ecu_serial_local.empty()) { - ecu_serial_local = keys_.UptanePublicKey().KeyId(); - } - - std::string ecu_hardware_id = config_.uptane.ecu_hardware_id; - if (ecu_hardware_id.empty()) { - ecu_hardware_id = Utils::getHostname(); - if (ecu_hardware_id == "") { - return false; - } - } - - ecu_serials.emplace_back(Uptane::EcuSerial(ecu_serial_local), Uptane::HardwareIdentifier(ecu_hardware_id)); - storage_->storeEcuSerials(ecu_serials); - ecu_serial_ = ecu_serials[0].first; - hardware_id_ = ecu_serials[0].second; - - storage_->importInstalledVersions(config_.import.base_path); - - return true; -} diff --git a/src/aktualizr_secondary/aktualizr_secondary_common.h b/src/aktualizr_secondary/aktualizr_secondary_common.h deleted file mode 100644 index 79d82200c2..0000000000 --- a/src/aktualizr_secondary/aktualizr_secondary_common.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef AKTUALIZR_SECONDARY_COMMON_H_ -#define AKTUALIZR_SECONDARY_COMMON_H_ - -#include -#include -#include - -#include "aktualizr_secondary_config.h" -#include "crypto/keymanager.h" -#include "package_manager/packagemanagerinterface.h" -#include "storage/invstorage.h" - -class AktualizrSecondaryCommon { - public: - AktualizrSecondaryCommon(const AktualizrSecondaryConfig& /*config*/, std::shared_ptr /*storage*/); - - bool uptaneInitialize(); - - const Uptane::EcuSerial& getSerial() const { return ecu_serial_; } - const Uptane::HardwareIdentifier& getHardwareID() const { return hardware_id_; } - - protected: - AktualizrSecondaryConfig config_; - std::shared_ptr storage_; - KeyManager keys_; - Uptane::EcuSerial ecu_serial_; - Uptane::HardwareIdentifier hardware_id_; - std::shared_ptr pacman; - Uptane::Root root_; -}; - -#endif // AKTUALIZR_SECONDARY_COMMON_H_ diff --git a/src/aktualizr_secondary/aktualizr_secondary_config.cc b/src/aktualizr_secondary/aktualizr_secondary_config.cc index d6988af919..8f33ff182a 100644 --- a/src/aktualizr_secondary/aktualizr_secondary_config.cc +++ b/src/aktualizr_secondary/aktualizr_secondary_config.cc @@ -96,6 +96,7 @@ void AktualizrSecondaryConfig::updateFromPropertyTree(const boost::property_tree CopySubtreeFromConfig(pacman, "pacman", pt); CopySubtreeFromConfig(storage, "storage", pt); CopySubtreeFromConfig(import, "import", pt); + CopySubtreeFromConfig(bootloader, "bootloader", pt); } void AktualizrSecondaryConfig::writeToStream(std::ostream& sink) const { @@ -109,6 +110,7 @@ void AktualizrSecondaryConfig::writeToStream(std::ostream& sink) const { WriteSectionToStream(pacman, "pacman", sink); WriteSectionToStream(storage, "storage", sink); WriteSectionToStream(import, "import", sink); + WriteSectionToStream(bootloader, "bootloader", sink); } std::ostream& operator<<(std::ostream& os, const AktualizrSecondaryConfig& cfg) { diff --git a/src/aktualizr_secondary/aktualizr_secondary_config_test.cc b/src/aktualizr_secondary/aktualizr_secondary_config_test.cc index 2ac654148f..4acdfe28d8 100644 --- a/src/aktualizr_secondary/aktualizr_secondary_config_test.cc +++ b/src/aktualizr_secondary/aktualizr_secondary_config_test.cc @@ -13,8 +13,11 @@ TEST(aktualizr_secondary_config, config_toml_parsing) { AktualizrSecondaryConfig conf("tests/config/aktualizr_secondary.toml"); EXPECT_EQ(conf.network.port, 9031); - +#ifdef BUILD_OSTREE EXPECT_EQ(conf.pacman.type, PackageManager::kOstree); +#else + EXPECT_EQ(conf.pacman.type, PackageManager::kNone); +#endif EXPECT_EQ(conf.pacman.os, std::string("testos")); EXPECT_EQ(conf.pacman.sysroot, boost::filesystem::path("testsysroot")); EXPECT_EQ(conf.pacman.ostree_server, std::string("test_server")); diff --git a/src/aktualizr_secondary/aktualizr_secondary_factory.cc b/src/aktualizr_secondary/aktualizr_secondary_factory.cc new file mode 100644 index 0000000000..db175ac929 --- /dev/null +++ b/src/aktualizr_secondary/aktualizr_secondary_factory.cc @@ -0,0 +1,47 @@ +#include "aktualizr_secondary_factory.h" + +#include "crypto/keymanager.h" +#include "update_agent_file.h" + +#ifdef BUILD_OSTREE +#include "package_manager/ostreemanager.h" +#include "update_agent_ostree.h" +#endif + +// TODO: consider implementation of a proper registry/builder/factory +AktualizrSecondary::Ptr AktualizrSecondaryFactory::create(const AktualizrSecondaryConfig& config) { + auto storage = INvStorage::newStorage(config.storage); + return AktualizrSecondaryFactory::create(config, storage); +} + +AktualizrSecondary::Ptr AktualizrSecondaryFactory::create(const AktualizrSecondaryConfig& config, + const std::shared_ptr& storage) { + auto bootloader = std::make_shared(config.bootloader, storage); + auto key_mngr = std::make_shared(storage, config.keymanagerConfig()); + + UpdateAgent::Ptr update_agent; + + if (config.pacman.type != PackageManager::kOstree) { + std::string current_target_name{""}; + + boost::optional current_version; + boost::optional pending_version; + auto installed_version_res = storage->loadInstalledVersions("", ¤t_version, &pending_version); + + if (installed_version_res && !!current_version) { + current_target_name = current_version->filename(); + } + + update_agent = std::make_shared(config.storage.path / "firmware.txt", current_target_name); + } +#ifdef BUILD_OSTREE + else { + std::shared_ptr pack_man = + std::make_shared(config.pacman, bootloader, storage, nullptr); + update_agent = + std::make_shared(config.pacman.sysroot, key_mngr, pack_man, config.uptane.ecu_hardware_id); + } +#endif + + return std::make_shared(config, storage, bootloader, key_mngr, update_agent); +} diff --git a/src/aktualizr_secondary/aktualizr_secondary_factory.h b/src/aktualizr_secondary/aktualizr_secondary_factory.h new file mode 100644 index 0000000000..d1b16882d1 --- /dev/null +++ b/src/aktualizr_secondary/aktualizr_secondary_factory.h @@ -0,0 +1,13 @@ +#ifndef AKTUALIZR_SECONDARY_FACTORY_H +#define AKTUALIZR_SECONDARY_FACTORY_H + +#include "aktualizr_secondary.h" + +class AktualizrSecondaryFactory { + public: + static AktualizrSecondary::Ptr create(const AktualizrSecondaryConfig& config); + static AktualizrSecondary::Ptr create(const AktualizrSecondaryConfig& config, + const std::shared_ptr& storage); +}; + +#endif // AKTUALIZR_SECONDARY_FACTORY_H diff --git a/src/aktualizr_secondary/aktualizr_secondary_interface.h b/src/aktualizr_secondary/aktualizr_secondary_interface.h deleted file mode 100644 index 68329ac4d0..0000000000 --- a/src/aktualizr_secondary/aktualizr_secondary_interface.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef AKTUALIZR_SECONDARY_INTERFACE_H_ -#define AKTUALIZR_SECONDARY_INTERFACE_H_ - -class AktualizrSecondaryInterface { - public: - virtual ~AktualizrSecondaryInterface() = default; - virtual void run() = 0; - virtual void stop() = 0; -}; - -#endif // AKTUALIZR_SECONDARY_INTERFACE_H_ diff --git a/src/aktualizr_secondary/aktualizr_secondary_ostree_test.cc b/src/aktualizr_secondary/aktualizr_secondary_ostree_test.cc new file mode 100644 index 0000000000..880c9c67f9 --- /dev/null +++ b/src/aktualizr_secondary/aktualizr_secondary_ostree_test.cc @@ -0,0 +1,335 @@ +#include + +#include + +#include "boost/algorithm/string/trim.hpp" +#include "boost/process.hpp" + +#include "logging/logging.h" +#include "test_utils.h" + +#include "aktualizr_secondary.h" +#include "aktualizr_secondary_factory.h" +#include "package_manager/ostreemanager.h" +#include "update_agent_ostree.h" +#include "uptane_repo.h" + +class Treehub { + public: + Treehub(const std::string& server_path) + : _port(TestUtils::getFreePort()), + _url("http://127.0.0.1:" + _port), + _process(server_path, "-p", _port, "-d", _root_dir.PathString(), "-s0.5", "--create") { + TestUtils::waitForServer(url() + "/"); + auto rev_process = Process("ostree").run({"rev-parse", "--repo", _root_dir.PathString(), "master"}); + EXPECT_EQ(std::get<0>(rev_process), 0) << std::get<2>(rev_process); + _cur_rev = std::get<1>(rev_process); + boost::trim_right_if(_cur_rev, boost::is_any_of(" \t\r\n")); + + LOG_INFO << "Treehub is running on: " << _port << " current revision: " << _cur_rev; + } + + ~Treehub() { + _process.terminate(); + _process.wait_for(std::chrono::seconds(10)); + if (_process.running()) { + LOG_ERROR << "Failed to stop Treehub server"; + } else { + LOG_INFO << "Treehub server has been stopped"; + } + } + + public: + const std::string& url() const { return _url; } + const std::string& curRev() const { return _cur_rev; } + + private: + TemporaryDirectory _root_dir; + const std::string _port; + const std::string _url; + boost::process::child _process; + std::string _cur_rev; +}; + +class OstreeRootfs { + public: + OstreeRootfs(const std::string& rootfs_template) { + auto sysroot_copy = Process("cp").run({"-r", rootfs_template, getPath().c_str()}); + EXPECT_EQ(std::get<0>(sysroot_copy), 0) << std::get<1>(sysroot_copy); + + auto deployment_rev = Process("ostree").run( + {"rev-parse", std::string("--repo"), getPath().string() + "/ostree/repo", "generate-remote/generated"}); + + EXPECT_EQ(std::get<0>(deployment_rev), 0) << std::get<2>(deployment_rev); + + _rev = std::get<1>(deployment_rev); + boost::trim_right_if(_rev, boost::is_any_of(" \t\r\n")); + + _deployment.reset(ostree_deployment_new(0, getOSName(), getDeploymentRev(), getDeploymentSerial(), + getDeploymentRev(), getDeploymentSerial())); + } + + const boost::filesystem::path& getPath() const { return _sysroot_dir; } + const char* getDeploymentRev() const { return _rev.c_str(); } + int getDeploymentSerial() const { return 0; } + const char* getOSName() const { return _os_name.c_str(); } + + OstreeDeployment* getDeployment() const { return _deployment.get(); } + void setNewDeploymentRev(const std::string& new_rev) { _rev = new_rev; } + + private: + const std::string _os_name{"dummy-os"}; + TemporaryDirectory _tmp_dir; + boost::filesystem::path _sysroot_dir{_tmp_dir / "ostree-rootfs"}; + std::string _rev; + GObjectUniquePtr _deployment; +}; + +class AktualizrSecondaryWrapper { + public: + AktualizrSecondaryWrapper(const OstreeRootfs& sysroot, const Treehub& treehub) { + // ostree update + + _config.pacman.type = PackageManager::kOstree; + _config.pacman.os = sysroot.getOSName(); + _config.pacman.sysroot = sysroot.getPath(); + _config.pacman.ostree_server = treehub.url(); + + _config.bootloader.reboot_sentinel_dir = _storage_dir.Path(); + _config.bootloader.reboot_sentinel_name = "need_reboot"; + + _config.storage.path = _storage_dir.Path(); + _config.storage.type = StorageType::kSqlite; + + _storage = INvStorage::newStorage(_config.storage); + _secondary = AktualizrSecondaryFactory::create(_config, _storage); + } + + public: + Uptane::SecondaryInterface::Ptr& operator->() { return _secondary; } + + Uptane::Target getPendingVersion() const { return getVersion().first; } + + Uptane::Target getCurrentVersion() const { return getVersion().second; } + + std::pair getVersion() const { + boost::optional current_target; + boost::optional pending_target; + + _storage->loadInstalledVersions(_secondary->getSerial().ToString(), ¤t_target, &pending_target); + + return std::make_pair(!pending_target ? Uptane::Target::Unknown() : *pending_target, + !current_target ? Uptane::Target::Unknown() : *current_target); + } + + std::string hardwareID() const { return _secondary->getHwId().ToString(); } + + std::string serial() const { return _secondary->getSerial().ToString(); } + + void reboot() { + boost::filesystem::remove(_storage_dir / _config.bootloader.reboot_sentinel_name); + _secondary = AktualizrSecondaryFactory::create(_config, _storage); + } + + private: + TemporaryDirectory _storage_dir; + AktualizrSecondaryConfig _config; + std::shared_ptr _storage; + Uptane::SecondaryInterface::Ptr _secondary; +}; + +class UptaneRepoWrapper { + public: + UptaneRepoWrapper() { _uptane_repo.generateRepo(KeyType::kED25519); } + + Metadata addOstreeRev(const std::string& rev, const std::string& hardware_id, const std::string& serial) { + // it makes sense to add 'addOstreeImage' to UptaneRepo interface/class uptane_repo.h + auto custom = Json::Value(); + custom["targetFormat"] = "OSTREE"; + _uptane_repo.addCustomImage(rev, Uptane::Hash(Uptane::Hash::Type::kSha256, rev), 0, hardware_id, "", Delegation(), + custom); + + _uptane_repo.addTarget(rev, hardware_id, serial, ""); + _uptane_repo.signTargets(); + + return getCurrentMetadata(); + } + + Uptane::RawMetaPack getCurrentMetadata() const { + Uptane::RawMetaPack metadata; + + boost::filesystem::load_string_file(_director_dir / "root.json", metadata.director_root); + boost::filesystem::load_string_file(_director_dir / "targets.json", metadata.director_targets); + + boost::filesystem::load_string_file(_imagerepo_dir / "root.json", metadata.image_root); + boost::filesystem::load_string_file(_imagerepo_dir / "timestamp.json", metadata.image_timestamp); + boost::filesystem::load_string_file(_imagerepo_dir / "snapshot.json", metadata.image_snapshot); + boost::filesystem::load_string_file(_imagerepo_dir / "targets.json", metadata.image_targets); + + return metadata; + } + + std::shared_ptr getImageData(const std::string& targetname) const { + auto image_data = std::make_shared(); + boost::filesystem::load_string_file(_root_dir / targetname, *image_data); + return image_data; + } + + private: + TemporaryDirectory _root_dir; + boost::filesystem::path _director_dir{_root_dir / "repo/director"}; + boost::filesystem::path _imagerepo_dir{_root_dir / "repo/repo"}; + UptaneRepo _uptane_repo{_root_dir.Path(), "", ""}; +}; + +class SecondaryOstreeTest : public ::testing::Test { + public: + static const char* curOstreeRootfsRev(OstreeDeployment* ostree_depl) { + (void)ostree_depl; + return _sysroot->getDeploymentRev(); + } + + static OstreeDeployment* curOstreeDeployment(OstreeSysroot* ostree_sysroot) { + (void)ostree_sysroot; + return _sysroot->getDeployment(); + } + + static void setOstreeRootfsTemplate(const std::string& ostree_rootfs_template) { + _ostree_rootfs_template = ostree_rootfs_template; + } + + protected: + static void SetUpTestSuite() { + _treehub = std::make_shared("tests/sota_tools/treehub_server.py"); + _sysroot = std::make_shared(_ostree_rootfs_template); + } + + static void TearDownTestSuite() { + _treehub.reset(); + _sysroot.reset(); + } + + protected: + SecondaryOstreeTest() {} + + Uptane::RawMetaPack addDefaultTarget() { return addTarget(_treehub->curRev()); } + + Uptane::RawMetaPack addTarget(const std::string& rev = "", const std::string& hardware_id = "", + const std::string& serial = "") { + auto rev_to_apply = rev.empty() ? _treehub->curRev() : rev; + auto hw_id = hardware_id.empty() ? _secondary.hardwareID() : hardware_id; + auto serial_id = serial.empty() ? _secondary.serial() : serial; + + _uptane_repo.addOstreeRev(rev, hw_id, serial_id); + + return currentMetadata(); + } + + Uptane::RawMetaPack currentMetadata() const { return _uptane_repo.getCurrentMetadata(); } + + std::string getCredsToSend() const { + std::map creds_map = { + {"ca.pem", ""}, {"client.pem", ""}, {"pkey.pem", ""}, {"server.url", _treehub->url()}}; + + std::stringstream creads_strstream; + Utils::writeArchive(creds_map, creads_strstream); + + return creads_strstream.str(); + } + + Uptane::Hash treehubCurRevHash() const { return Uptane::Hash(Uptane::Hash::Type::kSha256, _treehub->curRev()); } + Uptane::Hash sysrootCurRevHash() const { + return Uptane::Hash(Uptane::Hash::Type::kSha256, _sysroot->getDeploymentRev()); + } + const std::string& treehubCurRev() const { return _treehub->curRev(); } + + protected: + static std::shared_ptr _treehub; + static std::string _ostree_rootfs_template; + static std::shared_ptr _sysroot; + + AktualizrSecondaryWrapper _secondary{*_sysroot, *_treehub}; + UptaneRepoWrapper _uptane_repo; +}; + +std::shared_ptr SecondaryOstreeTest::_treehub{nullptr}; +std::string SecondaryOstreeTest::_ostree_rootfs_template{"./build/ostree_repo"}; +std::shared_ptr SecondaryOstreeTest::_sysroot{nullptr}; + +TEST_F(SecondaryOstreeTest, fullUptaneVerificationInvalidRevision) { + EXPECT_TRUE(_secondary->putMetadata(addTarget("invalid-revision"))); + EXPECT_FALSE(_secondary->sendFirmware(getCredsToSend())); +} + +TEST_F(SecondaryOstreeTest, fullUptaneVerificationInvalidHwID) { + EXPECT_FALSE(_secondary->putMetadata(addTarget("", "invalid-hardware-id", ""))); +} + +TEST_F(SecondaryOstreeTest, fullUptaneVerificationInvalidSerial) { + EXPECT_FALSE(_secondary->putMetadata(addTarget("", "", "invalid-serial-id"))); +} + +TEST_F(SecondaryOstreeTest, verifyUpdatePositive) { + // check the version reported in the manifest just after an initial boot + Uptane::Manifest manifest = _secondary->getManifest(); + EXPECT_TRUE(manifest.verifySignature(_secondary->getPublicKey())); + EXPECT_EQ(manifest.installedImageHash(), sysrootCurRevHash()); + + // do update + EXPECT_TRUE(_secondary->putMetadata(addDefaultTarget())); + EXPECT_TRUE(_secondary->sendFirmware(getCredsToSend())); + EXPECT_EQ(_secondary->install(treehubCurRev()), data::ResultCode::Numeric::kNeedCompletion); + + // check if the update was installed and pending + EXPECT_TRUE(_secondary.getPendingVersion().MatchHash(treehubCurRevHash())); + // manifest should still report the old version + manifest = _secondary->getManifest(); + EXPECT_TRUE(manifest.verifySignature(_secondary->getPublicKey())); + EXPECT_EQ(manifest.installedImageHash(), sysrootCurRevHash()); + + // emulate reboot + _sysroot->setNewDeploymentRev(treehubCurRev()); + _secondary.reboot(); + + // check if the version in the DB and reported in the manifest matches with the installed and applied one + EXPECT_FALSE(_secondary.getPendingVersion().IsValid()); + EXPECT_TRUE(_secondary.getCurrentVersion().MatchHash(treehubCurRevHash())); + manifest = _secondary->getManifest(); + EXPECT_TRUE(manifest.verifySignature(_secondary->getPublicKey())); + EXPECT_EQ(manifest.installedImageHash(), treehubCurRevHash()); + + // emulate reboot + // check if the installed version persists after a reboot + _secondary.reboot(); + EXPECT_FALSE(_secondary.getPendingVersion().IsValid()); + EXPECT_TRUE(_secondary.getCurrentVersion().MatchHash(treehubCurRevHash())); + manifest = _secondary->getManifest(); + EXPECT_TRUE(manifest.verifySignature(_secondary->getPublicKey())); + EXPECT_EQ(manifest.installedImageHash(), treehubCurRevHash()); +} + +#ifndef __NO_MAIN__ +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + + if (argc != 2) { + std::cerr << "Error: " << argv[0] << " \n"; + return EXIT_FAILURE; + } + + SecondaryOstreeTest::setOstreeRootfsTemplate(argv[1]); + + logger_init(); + logger_set_threshold(boost::log::trivial::info); + + return RUN_ALL_TESTS(); +} +#endif + +extern "C" OstreeDeployment* ostree_sysroot_get_booted_deployment(OstreeSysroot* ostree_sysroot) { + return SecondaryOstreeTest::curOstreeDeployment(ostree_sysroot); +} + +extern "C" const char* ostree_deployment_get_csum(OstreeDeployment* ostree_deployment) { + return SecondaryOstreeTest::curOstreeRootfsRev(ostree_deployment); +} diff --git a/src/aktualizr_secondary/aktualizr_secondary_test.cc b/src/aktualizr_secondary/aktualizr_secondary_test.cc new file mode 100644 index 0000000000..f93734170d --- /dev/null +++ b/src/aktualizr_secondary/aktualizr_secondary_test.cc @@ -0,0 +1,218 @@ +#include + +#include + +#include "aktualizr_secondary.h" +#include "aktualizr_secondary_factory.h" +#include "test_utils.h" +#include "uptane_repo.h" + +class AktualizrSecondaryWrapper { + public: + AktualizrSecondaryWrapper() { + AktualizrSecondaryConfig config; + config.pacman.type = PackageManager::kNone; + + config.storage.path = _storage_dir.Path(); + config.storage.type = StorageType::kSqlite; + + _storage = INvStorage::newStorage(config.storage); + _secondary = AktualizrSecondaryFactory::create(config, _storage); + } + + public: + std::shared_ptr& operator->() { return _secondary; } + + Uptane::Target getPendingVersion() const { + boost::optional pending_target; + + _storage->loadInstalledVersions(_secondary->getSerial().ToString(), nullptr, &pending_target); + return *pending_target; + } + + std::string hardwareID() const { return _secondary->getHwId().ToString(); } + + std::string serial() const { return _secondary->getSerial().ToString(); } + + private: + TemporaryDirectory _storage_dir; + AktualizrSecondary::Ptr _secondary; + std::shared_ptr _storage; +}; + +class UptaneRepoWrapper { + public: + UptaneRepoWrapper() { _uptane_repo.generateRepo(KeyType::kED25519); } + + Metadata addImageFile(const std::string& targetname, const std::string& hardware_id, const std::string& serial, + bool add_and_sign_target = true) { + const auto image_file_path = _root_dir / targetname; + boost::filesystem::ofstream(image_file_path) << "some data"; + + _uptane_repo.addImage(image_file_path, targetname, hardware_id, "", Delegation()); + if (add_and_sign_target) { + _uptane_repo.addTarget(targetname, hardware_id, serial, ""); + _uptane_repo.signTargets(); + } + + return getCurrentMetadata(); + } + + Uptane::RawMetaPack getCurrentMetadata() const { + Uptane::RawMetaPack metadata; + + boost::filesystem::load_string_file(_director_dir / "root.json", metadata.director_root); + boost::filesystem::load_string_file(_director_dir / "targets.json", metadata.director_targets); + + boost::filesystem::load_string_file(_imagerepo_dir / "root.json", metadata.image_root); + boost::filesystem::load_string_file(_imagerepo_dir / "timestamp.json", metadata.image_timestamp); + boost::filesystem::load_string_file(_imagerepo_dir / "snapshot.json", metadata.image_snapshot); + boost::filesystem::load_string_file(_imagerepo_dir / "targets.json", metadata.image_targets); + + return metadata; + } + + std::string getImageData(const std::string& targetname) const { + std::string image_data; + boost::filesystem::load_string_file(_root_dir / targetname, image_data); + return image_data; + } + + private: + TemporaryDirectory _root_dir; + boost::filesystem::path _director_dir{_root_dir / "repo/director"}; + boost::filesystem::path _imagerepo_dir{_root_dir / "repo/repo"}; + UptaneRepo _uptane_repo{_root_dir.Path(), "", ""}; +}; + +class SecondaryTest : public ::testing::Test { + protected: + SecondaryTest() { + _uptane_repo.addImageFile(_default_target, _secondary->getHwId().ToString(), _secondary->getSerial().ToString()); + } + + std::string getImageData(const std::string& targetname = _default_target) const { + return _uptane_repo.getImageData(targetname); + } + + protected: + static constexpr const char* const _default_target{"defaulttarget"}; + AktualizrSecondaryWrapper _secondary; + UptaneRepoWrapper _uptane_repo; +}; + +class SecondaryTestNegative : public SecondaryTest, + public ::testing::WithParamInterface> { + protected: + class MetadataInvalidator : public Metadata { + public: + MetadataInvalidator(const Uptane::RawMetaPack& valid_metadata, const Uptane::RepositoryType& repo, + const Uptane::Role& role) + : Metadata(valid_metadata), _repo_type(repo), _role(role) {} + + bool getRoleMetadata(std::string* result, const Uptane::RepositoryType& repo, const Uptane::Role& role, + Uptane::Version version) const override { + auto return_val = Metadata::getRoleMetadata(result, repo, role, version); + if (!(_repo_type == repo && _role == role)) { + return return_val; + } + (*result)[10] = 'f'; + return true; + } + + private: + Uptane::RepositoryType _repo_type; + Uptane::Role _role; + }; + + protected: + MetadataInvalidator currentMetadata() const { + return MetadataInvalidator(_uptane_repo.getCurrentMetadata(), GetParam().first, GetParam().second); + } +}; + +/** + * Parameterized test, + * The parameter is std::pair to indicate which metadata to malform + * + * see INSTANTIATE_TEST_SUITE_P for the test instantiations with concrete parameter values + */ +TEST_P(SecondaryTestNegative, MalformedMetadaJson) { EXPECT_FALSE(_secondary->putMetadata(currentMetadata())); } + +/** + * Instantiates the parameterized test for each specified value of std::pair + * the parameter value indicates which metadata to malform + */ +INSTANTIATE_TEST_SUITE_P(SecondaryTestMalformedMetadata, SecondaryTestNegative, + ::testing::Values(std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Root()), + std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Targets()), + std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Root()), + std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Timestamp()), + std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Snapshot()), + std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Targets()))); + +TEST_F(SecondaryTest, fullUptaneVerificationPositive) { + EXPECT_TRUE(_secondary->putMetadata(_uptane_repo.getCurrentMetadata())); + EXPECT_TRUE(_secondary->sendFirmware(getImageData())); + EXPECT_EQ(_secondary->install(_default_target), data::ResultCode::Numeric::kOk); +} + +TEST_F(SecondaryTest, TwoImagesAndOneTarget) { + // two images for the same ECU, just one of them is added as a target and signed + // default image and corresponding target has been already added, just add another image + _uptane_repo.addImageFile("second_image_00", _secondary->getHwId().ToString(), _secondary->getSerial().ToString(), + false); + EXPECT_TRUE(_secondary->putMetadata(_uptane_repo.getCurrentMetadata())); +} + +TEST_F(SecondaryTest, IncorrectTargetQuantity) { + { + // two targets for the same ECU + _uptane_repo.addImageFile("second_target", _secondary->getHwId().ToString(), _secondary->getSerial().ToString()); + + EXPECT_FALSE(_secondary->putMetadata(_uptane_repo.getCurrentMetadata())); + } + + { + // zero targets for the ECU being tested + auto metadata = + UptaneRepoWrapper().addImageFile("mytarget", _secondary->getHwId().ToString(), "non-existing-serial"); + + EXPECT_FALSE(_secondary->putMetadata(metadata)); + } + + { + // zero targets for the ECU being tested + auto metadata = + UptaneRepoWrapper().addImageFile("mytarget", "non-existig-hwid", _secondary->getSerial().ToString()); + + EXPECT_FALSE(_secondary->putMetadata(metadata)); + } +} + +TEST_F(SecondaryTest, InvalidImageFileSize) { + EXPECT_TRUE(_secondary->putMetadata(_uptane_repo.getCurrentMetadata())); + auto image_data = getImageData(); + image_data.append("\n"); + EXPECT_FALSE(_secondary->sendFirmware(image_data)); +} + +TEST_F(SecondaryTest, InvalidImageData) { + EXPECT_TRUE(_secondary->putMetadata(_uptane_repo.getCurrentMetadata())); + auto image_data = getImageData(); + image_data.operator[](3) = '0'; + EXPECT_FALSE(_secondary->sendFirmware(image_data)); +} + +// TODO: add more tests in case of file/binary based updates + +#ifndef __NO_MAIN__ +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + + logger_init(); + logger_set_threshold(boost::log::trivial::info); + + return RUN_ALL_TESTS(); +} +#endif diff --git a/src/aktualizr_secondary/main.cc b/src/aktualizr_secondary/main.cc index 957e6660d3..dd859c1f00 100644 --- a/src/aktualizr_secondary/main.cc +++ b/src/aktualizr_secondary/main.cc @@ -6,11 +6,12 @@ #include "aktualizr_secondary.h" #include "aktualizr_secondary_config.h" +#include "aktualizr_secondary_factory.h" +#include "logging/logging.h" +#include "secondary_tcp_server.h" #include "utilities/aktualizr_version.h" #include "utilities/utils.h" -#include "logging/logging.h" - namespace bpo = boost::program_options; void check_secondary_options(const bpo::options_description &description, const bpo::variables_map &vm) { @@ -85,11 +86,8 @@ int main(int argc, char *argv[]) { AktualizrSecondaryConfig config(commandline_map); LOG_DEBUG << "Current directory: " << boost::filesystem::current_path().string(); - // storage (share class with primary) - std::shared_ptr storage = INvStorage::newStorage(config.storage); - std::unique_ptr secondary; - secondary = std_::make_unique(config, storage); - secondary->run(); + auto secondary = AktualizrSecondaryFactory::create(config); + SecondaryTcpServer(*secondary, config.network.primary_ip, config.network.primary_port, config.network.port).run(); } catch (std::runtime_error &exc) { LOG_ERROR << "Error: " << exc.what(); diff --git a/src/aktualizr_secondary/socket_server.cc b/src/aktualizr_secondary/secondary_tcp_server.cc similarity index 71% rename from src/aktualizr_secondary/socket_server.cc rename to src/aktualizr_secondary/secondary_tcp_server.cc index 0b05bf4577..46bc24193f 100644 --- a/src/aktualizr_secondary/socket_server.cc +++ b/src/aktualizr_secondary/secondary_tcp_server.cc @@ -1,30 +1,38 @@ -#include "socket_server.h" - -#include +#include "secondary_tcp_server.h" #include "AKIpUptaneMes.h" #include "asn1/asn1_message.h" #include "logging/logging.h" +#include "uptane/secondaryinterface.h" #include "utilities/dequeue_buffer.h" -#include "utilities/sockaddr_io.h" #include -#include -#include -void SocketServer::Run() { - if (listen(*socket_, SOMAXCONN) < 0) { +SecondaryTcpServer::SecondaryTcpServer(Uptane::SecondaryInterface &secondary, const std::string &primary_ip, + in_port_t primary_port, in_port_t port) + : SecondaryTcpServer(secondary, port) { + ConnectionSocket conn_socket(primary_ip, primary_port, listen_socket_.port()); + if (conn_socket.connect() == 0) { + LOG_INFO << "Connected to Primary, sending info about this secondary..."; + HandleOneConnection(*conn_socket); + } else { + LOG_INFO << "Failed to connect to Primary"; + } +} + +void SecondaryTcpServer::run() { + if (listen(*listen_socket_, SOMAXCONN) < 0) { throw std::system_error(errno, std::system_category(), "listen"); } - LOG_INFO << "Listening on " << Utils::ipGetSockaddr(*socket_); + LOG_INFO << "Secondary TCP server listens on " << listen_socket_.toString(); - while (true) { + while (keep_running_.load()) { int con_fd; sockaddr_storage peer_sa{}; socklen_t peer_sa_size = sizeof(sockaddr_storage); LOG_DEBUG << "Waiting for connection from client..."; - if ((con_fd = accept(*socket_, reinterpret_cast(&peer_sa), &peer_sa_size)) == -1) { + if ((con_fd = accept(*listen_socket_, reinterpret_cast(&peer_sa), &peer_sa_size)) == -1) { LOG_INFO << "Socket accept failed. aborting"; break; } @@ -32,9 +40,18 @@ void SocketServer::Run() { HandleOneConnection(con_fd); LOG_DEBUG << "Client disconnected"; } + LOG_INFO << "Secondary TCP server exit"; +} + +void SecondaryTcpServer::stop() { + keep_running_ = false; + // unblock accept + ConnectionSocket("localhost", listen_socket_.port()).connect(); } -void SocketServer::HandleOneConnection(int socket) { +in_port_t SecondaryTcpServer::port() const { return listen_socket_.port(); } + +void SecondaryTcpServer::HandleOneConnection(int socket) { // Outside the message loop, because one recv() may have parts of 2 messages // Note that one recv() call returning 2+ messages doesn't work at the // moment. This shouldn't be a problem until we have messages that aren't @@ -66,9 +83,9 @@ void SocketServer::HandleOneConnection(int socket) { Asn1Message::Ptr resp = Asn1Message::Empty(); switch (msg->present()) { case AKIpUptaneMes_PR_getInfoReq: { - Uptane::EcuSerial serial = impl_->getSerial(); - Uptane::HardwareIdentifier hw_id = impl_->getHwId(); - PublicKey pk = impl_->getPublicKey(); + Uptane::EcuSerial serial = impl_.getSerial(); + Uptane::HardwareIdentifier hw_id = impl_.getHwId(); + PublicKey pk = impl_.getPublicKey(); resp->present(AKIpUptaneMes_PR_getInfoResp); auto r = resp->getInfoResp(); SetString(&r->ecuSerial, serial.ToString()); @@ -77,7 +94,7 @@ void SocketServer::HandleOneConnection(int socket) { SetString(&r->key, pk.Value()); } break; case AKIpUptaneMes_PR_manifestReq: { - std::string manifest = Utils::jsonToStr(impl_->getManifest()); + std::string manifest = Utils::jsonToStr(impl_.getManifest()); resp->present(AKIpUptaneMes_PR_manifestResp); auto r = resp->manifestResp(); r->manifest.present = manifest_PR_json; @@ -103,7 +120,7 @@ void SocketServer::HandleOneConnection(int socket) { } bool ok; try { - ok = impl_->putMetadata(meta_pack); + ok = impl_.putMetadata(meta_pack); } catch (Uptane::SecurityException &e) { LOG_WARNING << "Rejected metadata push because of security failure" << e.what(); ok = false; @@ -114,11 +131,19 @@ void SocketServer::HandleOneConnection(int socket) { } break; case AKIpUptaneMes_PR_sendFirmwareReq: { auto fw = msg->sendFirmwareReq(); - auto fw_data = std::make_shared(ToString(fw->firmware)); - auto fut = std::async(std::launch::async, &Uptane::SecondaryInterface::sendFirmware, impl_, fw_data); + auto send_firmware_result = impl_.sendFirmware(ToString(fw->firmware)); resp->present(AKIpUptaneMes_PR_sendFirmwareResp); auto r = resp->sendFirmwareResp(); - r->result = fut.get() ? AKInstallationResult_success : AKInstallationResult_failure; + r->result = send_firmware_result ? AKInstallationResult_success : AKInstallationResult_failure; + } break; + case AKIpUptaneMes_PR_installReq: { + auto request = msg->installReq(); + + auto install_result = impl_.install(ToString(request->hash)); + + resp->present(AKIpUptaneMes_PR_installResp); + auto response_message = resp->installResp(); + response_message->result = static_cast(install_result); } break; default: LOG_ERROR << "Unrecognised message type:" << msg->present(); @@ -145,34 +170,3 @@ void SocketServer::HandleOneConnection(int socket) { // write error => Shutdown the socket // Timeout on write => shutdown } - -SocketHandle SocketFromPort(in_port_t port) { - // manual socket creation - int socket_fd = socket(AF_INET6, SOCK_STREAM, 0); - if (socket_fd < 0) { - throw std::runtime_error("socket creation failed"); - } - SocketHandle hdl(new int(socket_fd)); - sockaddr_in6 sa{}; - - memset(&sa, 0, sizeof(sa)); - sa.sin6_family = AF_INET6; - sa.sin6_port = htons(port); - sa.sin6_addr = IN6ADDR_ANY_INIT; - - int v6only = 0; - if (setsockopt(*hdl, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) < 0) { - throw std::system_error(errno, std::system_category(), "setsockopt(IPV6_V6ONLY)"); - } - - int reuseaddr = 1; - if (setsockopt(*hdl, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) < 0) { - throw std::system_error(errno, std::system_category(), "setsockopt(SO_REUSEADDR)"); - } - - if (bind(*hdl, reinterpret_cast(&sa), sizeof(sa)) < 0) { - throw std::system_error(errno, std::system_category(), "bind"); - } - - return hdl; -} diff --git a/src/aktualizr_secondary/secondary_tcp_server.h b/src/aktualizr_secondary/secondary_tcp_server.h new file mode 100644 index 0000000000..aa2244c2ae --- /dev/null +++ b/src/aktualizr_secondary/secondary_tcp_server.h @@ -0,0 +1,44 @@ +#ifndef AKTUALIZR_SECONDARY_TCP_SERVER_H_ +#define AKTUALIZR_SECONDARY_TCP_SERVER_H_ + +#include "utilities/utils.h" + +#include + +namespace Uptane { +class SecondaryInterface; +} // namespace Uptane +/** + * Listens on a socket, decodes calls (ASN.1) and forwards them to an Uptane Secondary + * implementation + */ +class SecondaryTcpServer { + public: + SecondaryTcpServer(Uptane::SecondaryInterface& secondary, const std::string& primary_ip, in_port_t primary_port, + in_port_t port = 0); + + SecondaryTcpServer(Uptane::SecondaryInterface& secondary, in_port_t port = 0) + : keep_running_(true), impl_(secondary), listen_socket_(port) {} + + SecondaryTcpServer(const SecondaryTcpServer&) = delete; + SecondaryTcpServer& operator=(const SecondaryTcpServer&) = delete; + + public: + /** + * Accept connections on the socket, decode requests and respond using the secondary implementation + */ + void run(); + void stop(); + + in_port_t port() const; + + private: + void HandleOneConnection(int socket); + + private: + std::atomic keep_running_; + Uptane::SecondaryInterface& impl_; + ListenSocket listen_socket_; +}; + +#endif // AKTUALIZR_SECONDARY_TCP_SERVER_H_ diff --git a/src/aktualizr_secondary/secondary_tcp_server_test.cc b/src/aktualizr_secondary/secondary_tcp_server_test.cc new file mode 100644 index 0000000000..6b6619bce0 --- /dev/null +++ b/src/aktualizr_secondary/secondary_tcp_server_test.cc @@ -0,0 +1,131 @@ +#include + +#include "ipuptanesecondary.h" +#include "logging/logging.h" +#include "secondary_tcp_server.h" +#include "test_utils.h" +#include "uptane/secondaryinterface.h" + +class SecondaryMock : public Uptane::SecondaryInterface { + public: + SecondaryMock(const Uptane::EcuSerial& serial, const Uptane::HardwareIdentifier& hdw_id, const PublicKey& pub_key, + const Uptane::Manifest& manifest) + : _serial(serial), _hdw_id(hdw_id), _pub_key(pub_key), _manifest(manifest) {} + + public: + virtual Uptane::EcuSerial getSerial() const { return _serial; } + virtual Uptane::HardwareIdentifier getHwId() const { return _hdw_id; } + virtual PublicKey getPublicKey() const { return _pub_key; } + virtual Uptane::Manifest getManifest() const { return _manifest; } + virtual bool putMetadata(const Uptane::RawMetaPack& meta_pack) { + _metapack = meta_pack; + return true; + } + virtual int32_t getRootVersion(bool director) const { + (void)director; + return 0; + } + virtual bool putRoot(const std::string& root, bool director) { + (void)root; + (void)director; + return true; + } + + virtual bool sendFirmware(const std::string& data) { + _data = data; + return true; + } + + virtual data::ResultCode::Numeric install(const std::string& target_name) { + (void)target_name; + return data::ResultCode::Numeric::kOk; + } + + public: + const Uptane::EcuSerial _serial; + const Uptane::HardwareIdentifier _hdw_id; + const PublicKey _pub_key; + const Uptane::Manifest _manifest; + + Uptane::RawMetaPack _metapack; + std::string _data; +}; + +bool operator==(const Uptane::RawMetaPack& lhs, const Uptane::RawMetaPack& rhs) { + return (lhs.director_root == rhs.director_root) && (lhs.image_root == rhs.image_root) && + (lhs.director_targets == rhs.director_targets) && (lhs.image_snapshot == rhs.image_snapshot) && + (lhs.image_timestamp == rhs.image_timestamp) && (lhs.image_targets == rhs.image_targets); +} + +// Test the serialization/deserialization and the TCP/IP communication implementation +// that occurs during communication between Primary and IP Secondary +TEST(SecondaryTcpServer, TestIpSecondaryRPC) { + // secondary object on Secondary ECU + SecondaryMock secondary(Uptane::EcuSerial("serial"), Uptane::HardwareIdentifier("hardware-id"), + PublicKey("pub-key", KeyType::kED25519), Uptane::Manifest()); + + // create Secondary on Secondary ECU, and run it in a dedicated thread + SecondaryTcpServer secondary_server{secondary}; + std::thread secondary_server_thread{[&secondary_server]() { secondary_server.run(); }}; + + // create Secondary on Primary ECU, try it a few times since the secondary thread + // might not be ready at the moment of the first try + const int max_try = 5; + Uptane::SecondaryInterface::Ptr ip_secondary; + for (int ii = 0; ii < max_try && ip_secondary == nullptr; ++ii) { + ip_secondary = Uptane::IpUptaneSecondary::connectAndCreate("localhost", secondary_server.port()); + } + + ASSERT_TRUE(ip_secondary != nullptr) << "Failed to create IP Secondary"; + EXPECT_EQ(ip_secondary->getSerial(), secondary.getSerial()); + EXPECT_EQ(ip_secondary->getHwId(), secondary.getHwId()); + EXPECT_EQ(ip_secondary->getPublicKey(), secondary.getPublicKey()); + EXPECT_EQ(ip_secondary->getManifest(), secondary.getManifest()); + + Uptane::RawMetaPack meta_pack{"director-root", "director-target", "image_root", + "image_targets", "image_timestamp", "image_snapshot"}; + + EXPECT_TRUE(ip_secondary->putMetadata(meta_pack)); + EXPECT_TRUE(meta_pack == secondary._metapack); + + std::string firmware = "firmware"; + EXPECT_TRUE(ip_secondary->sendFirmware(firmware)); + EXPECT_EQ(firmware, secondary._data); + + EXPECT_EQ(ip_secondary->install(""), data::ResultCode::Numeric::kOk); + + secondary_server.stop(); + secondary_server_thread.join(); +} + +TEST(SecondaryTcpServer, TestIpSecondaryIfSecondaryIsNotRunning) { + in_port_t secondary_port = TestUtils::getFreePortAsInt(); + Uptane::SecondaryInterface::Ptr ip_secondary; + + // trying to connect to a non-running Secondary and create a corresponding instance on Primary + ip_secondary = Uptane::IpUptaneSecondary::connectAndCreate("localhost", secondary_port); + EXPECT_EQ(ip_secondary, nullptr); + + // create Primary's secondary without connecting to Secondary + ip_secondary = std::make_shared("localhost", secondary_port, Uptane::EcuSerial("serial"), + Uptane::HardwareIdentifier("hwid"), + PublicKey("key", KeyType::kED25519)); + + Uptane::RawMetaPack meta_pack{"director-root", "director-target", "image_root", + "image_targets", "image_timestamp", "image_snapshot"}; + + // expect failures since the secondary is not running + EXPECT_EQ(ip_secondary->getManifest(), Json::Value()); + EXPECT_FALSE(ip_secondary->sendFirmware("firmware")); + EXPECT_FALSE(ip_secondary->putMetadata(meta_pack)); + EXPECT_EQ(ip_secondary->install(""), data::ResultCode::Numeric::kInternalError); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + + logger_init(); + logger_set_threshold(boost::log::trivial::info); + + return RUN_ALL_TESTS(); +} diff --git a/src/aktualizr_secondary/socket_server.h b/src/aktualizr_secondary/socket_server.h deleted file mode 100644 index cba016ddda..0000000000 --- a/src/aktualizr_secondary/socket_server.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef AKTUALIZR_SECONDARY_SOCKET_SERVER_H_ -#define AKTUALIZR_SECONDARY_SOCKET_SERVER_H_ - -#include "uptane/secondaryinterface.h" -#include "utilities/utils.h" - -/** - * Listens on a socket, decodes calls and forwards them to an Uptane Secondary - * implementation - */ -class SocketServer { - public: - SocketServer(std::shared_ptr implementation, SocketHandle socket) - : impl_(std::move(implementation)), socket_(std::move(socket)) {} - - /** - * Accept connections on the socket, decode requests and respond using the - * wrapped secondary - */ - void Run(); - - void HandleOneConnection(int socket); - - private: - std::shared_ptr impl_; - SocketHandle socket_; -}; - -/** - * Create and return a server socket on the given port number - */ -SocketHandle SocketFromPort(in_port_t port); - -#endif // AKTUALIZR_SECONDARY_SOCKET_SERVER_H_ diff --git a/src/aktualizr_secondary/update_agent.h b/src/aktualizr_secondary/update_agent.h new file mode 100644 index 0000000000..5c35034a43 --- /dev/null +++ b/src/aktualizr_secondary/update_agent.h @@ -0,0 +1,28 @@ +#ifndef AKTUALIZR_SECONDARY_UPDATE_AGENT_H +#define AKTUALIZR_SECONDARY_UPDATE_AGENT_H + +#include "uptane/tuf.h" + +class UpdateAgent { + public: + using Ptr = std::shared_ptr; + + public: + virtual bool isTargetSupported(const Uptane::Target& target) const = 0; + virtual bool getInstalledImageInfo(Uptane::InstalledImageInfo& installed_image_info) const = 0; + + virtual bool download(const Uptane::Target& target, const std::string& data) = 0; + virtual data::ResultCode::Numeric install(const Uptane::Target& target) = 0; + virtual data::InstallationResult applyPendingInstall(const Uptane::Target& target) = 0; + + virtual ~UpdateAgent() = default; + + public: + UpdateAgent(const UpdateAgent&) = delete; + UpdateAgent& operator=(const UpdateAgent&) = delete; + + protected: + UpdateAgent() = default; +}; + +#endif // AKTUALIZR_SECONDARY_UPDATE_AGENT_H diff --git a/src/aktualizr_secondary/update_agent_file.cc b/src/aktualizr_secondary/update_agent_file.cc new file mode 100644 index 0000000000..75cd513fd1 --- /dev/null +++ b/src/aktualizr_secondary/update_agent_file.cc @@ -0,0 +1,62 @@ +#include "update_agent_file.h" +#include "logging/logging.h" +#include "uptane/manifest.h" + +// TODO: this is an initial version of a file update on Secondary +bool FileUpdateAgent::isTargetSupported(const Uptane::Target& target) const { return target.type() != "OSTREE"; } + +bool FileUpdateAgent::getInstalledImageInfo(Uptane::InstalledImageInfo& installed_image_info) const { + if (boost::filesystem::exists(target_filepath_)) { + auto file_content = Utils::readFile(target_filepath_); + + installed_image_info.name = current_target_name_; + installed_image_info.len = file_content.size(); + installed_image_info.hash = Uptane::ManifestIssuer::generateVersionHashStr(file_content); + } else { + // mimic the Primary's fake package manager behavior + auto unknown_target = Uptane::Target::Unknown(); + installed_image_info.name = unknown_target.filename(); + installed_image_info.len = unknown_target.length(); + installed_image_info.hash = unknown_target.sha256Hash(); + } + + return true; +} + +bool FileUpdateAgent::download(const Uptane::Target& target, const std::string& data) { + auto target_hashes = target.hashes(); + if (target_hashes.size() == 0) { + LOG_ERROR << "No hash found in the target metadata: " << target.filename(); + return false; + } + + try { + auto received_image_data_hash = Uptane::ManifestIssuer::generateVersionHash(data); + + if (!target.MatchHash(received_image_data_hash)) { + LOG_ERROR << "The received image data hash doesn't match the hash specified in the target metadata," + " hash type: " + << target_hashes[0].TypeString(); + return false; + } + + Utils::writeFile(target_filepath_, data); + current_target_name_ = target.filename(); + + } catch (const std::exception& exc) { + LOG_ERROR << "Failed to generate a hash of the received image data: " << exc.what(); + return false; + } + return true; +} + +data::ResultCode::Numeric FileUpdateAgent::install(const Uptane::Target& target) { + (void)target; + return data::ResultCode::Numeric::kOk; +} + +data::InstallationResult FileUpdateAgent::applyPendingInstall(const Uptane::Target& target) { + (void)target; + return data::InstallationResult(data::ResultCode::Numeric::kInternalError, + "Applying of the pending updates are not supported by the file update agent"); +} diff --git a/src/aktualizr_secondary/update_agent_file.h b/src/aktualizr_secondary/update_agent_file.h new file mode 100644 index 0000000000..dcbeb3a6e0 --- /dev/null +++ b/src/aktualizr_secondary/update_agent_file.h @@ -0,0 +1,23 @@ +#ifndef AKTUALIZR_SECONDARY_UPDATE_AGENT_FILE_H +#define AKTUALIZR_SECONDARY_UPDATE_AGENT_FILE_H + +#include "update_agent.h" + +class FileUpdateAgent : public UpdateAgent { + public: + FileUpdateAgent(const boost::filesystem::path& target_filepath, std::string target_name) + : target_filepath_{target_filepath}, current_target_name_{target_name} {} + + public: + bool isTargetSupported(const Uptane::Target& target) const override; + bool getInstalledImageInfo(Uptane::InstalledImageInfo& installed_image_info) const override; + bool download(const Uptane::Target& target, const std::string& data) override; + data::ResultCode::Numeric install(const Uptane::Target& target) override; + data::InstallationResult applyPendingInstall(const Uptane::Target& target) override; + + private: + const boost::filesystem::path target_filepath_; + std::string current_target_name_; +}; + +#endif // AKTUALIZR_SECONDARY_UPDATE_AGENT_FILE_H diff --git a/src/aktualizr_secondary/update_agent_ostree.cc b/src/aktualizr_secondary/update_agent_ostree.cc new file mode 100644 index 0000000000..566398b30f --- /dev/null +++ b/src/aktualizr_secondary/update_agent_ostree.cc @@ -0,0 +1,91 @@ +#include "update_agent_ostree.h" + +#include "package_manager/ostreemanager.h" + +// TODO: consider moving this and SotaUptaneClient::secondaryTreehubCredentials() to encapsulate them in one place that +// is shared between IP Secondary's component +static void extractCredentialsArchive(const std::string& archive, std::string* ca, std::string* cert, std::string* pkey, + std::string* treehub_server); + +bool OstreeUpdateAgent::isTargetSupported(const Uptane::Target& target) const { return target.IsOstree(); } + +bool OstreeUpdateAgent::getInstalledImageInfo(Uptane::InstalledImageInfo& installed_image_info) const { + bool result = false; + try { + installed_image_info.hash = _ostreePackMan->getCurrentHash(); + // This is the policy on a target image name in case of ostree + // The policy in followed and implied in meta-updater (garage-sign/push) and the backend + installed_image_info.name = _targetname_prefix + "-" + installed_image_info.hash; + installed_image_info.len = 0; + result = true; + } catch (const std::exception& exc) { + LOG_ERROR << "Failed to get the currently installed revision: " << exc.what(); + } + return result; +} + +bool OstreeUpdateAgent::download(const Uptane::Target& target, const std::string& data) { + std::string treehub_server; + bool download_result = false; + + try { + std::string ca, cert, pkey, server_url; + extractCredentialsArchive(data, &ca, &cert, &pkey, &server_url); + // TODO: why are qe loading this credentials at all ? + _keyMngr->loadKeys(&pkey, &cert, &ca); + boost::trim(server_url); + treehub_server = server_url; + } catch (std::runtime_error& exc) { + LOG_ERROR << exc.what(); + return false; + } + + auto install_res = OstreeManager::pull(_sysrootPath, treehub_server, *_keyMngr, target); + + switch (install_res.result_code.num_code) { + case data::ResultCode::Numeric::kOk: { + LOG_INFO << "The target revision has been successfully downloaded: " << target.sha256Hash(); + download_result = true; + break; + } + case data::ResultCode::Numeric::kAlreadyProcessed: { + LOG_INFO << "The target revision is already present on the local ostree repo: " << target.sha256Hash(); + download_result = true; + break; + } + default: { + LOG_ERROR << "Failed to download the target revision: " << target.sha256Hash() << " ( " + << install_res.result_code.toString() << " ): " << install_res.description; + } + } + + return download_result; +} + +data::ResultCode::Numeric OstreeUpdateAgent::install(const Uptane::Target& target) { + return (_ostreePackMan->install(target)).result_code.num_code; +} + +data::InstallationResult OstreeUpdateAgent::applyPendingInstall(const Uptane::Target& target) { + return _ostreePackMan->finalizeInstall(target); +} + +void extractCredentialsArchive(const std::string& archive, std::string* ca, std::string* cert, std::string* pkey, + std::string* treehub_server) { + { + std::stringstream as(archive); + *ca = Utils::readFileFromArchive(as, "ca.pem"); + } + { + std::stringstream as(archive); + *cert = Utils::readFileFromArchive(as, "client.pem"); + } + { + std::stringstream as(archive); + *pkey = Utils::readFileFromArchive(as, "pkey.pem"); + } + { + std::stringstream as(archive); + *treehub_server = Utils::readFileFromArchive(as, "server.url", true); + } +} diff --git a/src/aktualizr_secondary/update_agent_ostree.h b/src/aktualizr_secondary/update_agent_ostree.h new file mode 100644 index 0000000000..19771f64b1 --- /dev/null +++ b/src/aktualizr_secondary/update_agent_ostree.h @@ -0,0 +1,32 @@ +#ifndef AKTUALIZR_SECONDARY_UPDATE_AGENT_OSTREE_H +#define AKTUALIZR_SECONDARY_UPDATE_AGENT_OSTREE_H + +#include "update_agent.h" + +class OstreeManager; +class KeyManager; + +class OstreeUpdateAgent : public UpdateAgent { + public: + OstreeUpdateAgent(const boost::filesystem::path& sysroot_path, std::shared_ptr& key_mngr, + std::shared_ptr& ostree_pack_man, std::string targetname_prefix) + : _sysrootPath(sysroot_path), + _keyMngr(key_mngr), + _ostreePackMan(ostree_pack_man), + _targetname_prefix(std::move(targetname_prefix)) {} + + public: + bool isTargetSupported(const Uptane::Target& target) const override; + bool getInstalledImageInfo(Uptane::InstalledImageInfo& installed_image_info) const override; + bool download(const Uptane::Target& target, const std::string& data) override; + data::ResultCode::Numeric install(const Uptane::Target& target) override; + data::InstallationResult applyPendingInstall(const Uptane::Target& target) override; + + private: + const boost::filesystem::path& _sysrootPath; + std::shared_ptr _keyMngr; + std::shared_ptr _ostreePackMan; + const ::std::string _targetname_prefix; +}; + +#endif // AKTUALIZR_SECONDARY_UPDATE_AGENT_OSTREE_H diff --git a/src/aktualizr_secondary/update_test.cc b/src/aktualizr_secondary/update_test.cc deleted file mode 100644 index 56b63b9b9e..0000000000 --- a/src/aktualizr_secondary/update_test.cc +++ /dev/null @@ -1,84 +0,0 @@ -#include - -#include "aktualizr_secondary.h" -#include "uptane/secondaryinterface.h" - -#include -#include - -#include "config/config.h" -#include "storage/invstorage.h" -#include "utilities/utils.h" -std::string sysroot; - -class ShortCircuitSecondary : public Uptane::SecondaryInterface { - public: - ShortCircuitSecondary(AktualizrSecondary& sec) : secondary(sec) {} - ~ShortCircuitSecondary() override = default; - - Uptane::EcuSerial getSerial() override { return secondary.getSerialResp(); } - Uptane::HardwareIdentifier getHwId() override { return secondary.getHwIdResp(); } - PublicKey getPublicKey() override { return secondary.getPublicKeyResp(); } - Json::Value getManifest() override { return secondary.getManifestResp(); } - bool putMetadata(const Uptane::RawMetaPack& meta_pack) override { return secondary.putMetadataResp(meta_pack); } - int32_t getRootVersion(bool director) override { return secondary.getRootVersionResp(director); } - bool putRoot(const std::string& root, bool director) override { return secondary.putRootResp(root, director); } - bool sendFirmware(const std::shared_ptr& data) override { return secondary.sendFirmwareResp(data); } - - private: - AktualizrSecondary& secondary; -}; - -TEST(aktualizr_secondary_protocol, DISABLED_manual_update) { - // secondary - TemporaryDirectory temp_dir_sec; - AktualizrSecondaryConfig config; - config.network.port = 0; - config.storage.type = StorageType::kSqlite; - config.pacman.sysroot = sysroot; - auto storage = INvStorage::newStorage(config.storage); - - AktualizrSecondary as(config, storage); - - // secondary interface - ShortCircuitSecondary sec_iface{as}; - - // storage - TemporaryDirectory temp_dir; - Utils::copyDir("tests/test_data/secondary_meta", temp_dir.Path()); - StorageConfig storage2_config; - storage2_config.path = temp_dir.Path(); - auto storage2 = INvStorage::newStorage(storage2_config); - - Uptane::RawMetaPack metadata; - EXPECT_TRUE(storage2->loadLatestRoot(&metadata.director_root, Uptane::RepositoryType::Director())); - EXPECT_TRUE( - storage2->loadNonRoot(&metadata.director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets())); - EXPECT_TRUE(storage2->loadLatestRoot(&metadata.image_root, Uptane::RepositoryType::Image())); - EXPECT_TRUE(storage2->loadNonRoot(&metadata.image_targets, Uptane::RepositoryType::Image(), Uptane::Role::Targets())); - EXPECT_TRUE( - storage2->loadNonRoot(&metadata.image_timestamp, Uptane::RepositoryType::Image(), Uptane::Role::Timestamp())); - EXPECT_TRUE( - storage2->loadNonRoot(&metadata.image_snapshot, Uptane::RepositoryType::Image(), Uptane::Role::Snapshot())); - - std::string firmware = Utils::readFile(temp_dir.Path() / "firmware.bin"); - - EXPECT_TRUE(sec_iface.putMetadata(metadata)); - EXPECT_TRUE(sec_iface.sendFirmware(std::make_shared(firmware))); - Json::Value manifest = sec_iface.getManifest(); - - EXPECT_EQ(manifest["signed"]["installed_image"]["fileinfo"]["hashes"]["sha256"], - boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(firmware)))); -} - -#ifndef __NO_MAIN__ -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - if (argc != 2) { - std::cerr << "Error: " << argv[0] << " requires the path to an OSTree sysroot as an input argument.\n"; - return EXIT_FAILURE; - } - sysroot = argv[1]; - return RUN_ALL_TESTS(); -} -#endif diff --git a/src/aktualizr_secondary/uptane_test.cc b/src/aktualizr_secondary/uptane_test.cc deleted file mode 100644 index 1fe18bf185..0000000000 --- a/src/aktualizr_secondary/uptane_test.cc +++ /dev/null @@ -1,77 +0,0 @@ -#include - -#include "aktualizr_secondary.h" -#include "primary/reportqueue.h" -#include "primary/sotauptaneclient.h" - -#include "config/config.h" -#include "httpfake.h" -#include "uptane_test_common.h" - -std::shared_ptr test_storage; -AktualizrSecondaryConfig test_config; -std::string test_sysroot; - -TEST(aktualizr_secondary_uptane, getSerial) { - test_config.pacman.sysroot = test_sysroot; - AktualizrSecondary as(test_config, test_storage); - - EXPECT_NE(as.getSerialResp(), Uptane::EcuSerial("hw")); -} - -TEST(aktualizr_secondary_uptane, getHwId) { - test_config.pacman.sysroot = test_sysroot; - AktualizrSecondary as(test_config, test_storage); - - EXPECT_NE(as.getHwIdResp(), Uptane::HardwareIdentifier("")); -} - -TEST(aktualizr_secondary_uptane, getPublicKey) { - test_config.pacman.sysroot = test_sysroot; - AktualizrSecondary as(test_config, test_storage); - - EXPECT_NO_THROW(as.getPublicKeyResp()); -} - -TEST(aktualizr_secondary_uptane, credentialsPassing) { - TemporaryDirectory temp_dir; - auto http = std::make_shared(temp_dir.Path()); - Config config; - config.storage.path = temp_dir.Path(); - boost::filesystem::copy_file("tests/test_data/cred.zip", (temp_dir / "cred.zip").string()); - config.provision.provision_path = temp_dir / "cred.zip"; - config.provision.mode = ProvisionMode::kSharedCred; - config.provision.primary_ecu_serial = "testecuserial"; - config.uptane.director_server = http->tls_server + "/director"; - config.uptane.repo_server = http->tls_server + "/repo"; - config.pacman.type = PackageManager::kNone; - - auto storage = INvStorage::newStorage(config.storage); - - auto sota_client = std_::make_unique(config, storage, http); - EXPECT_NO_THROW(sota_client->initialize()); - - std::string arch = sota_client->secondaryTreehubCredentials(); - std::string ca, cert, pkey, server_url; - EXPECT_NO_THROW(AktualizrSecondary::extractCredentialsArchive(arch, &ca, &cert, &pkey, &server_url)); -} - -#ifndef __NO_MAIN__ -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - - TemporaryDirectory temp_dir; - test_config.network.port = 0; // random port - test_config.storage.path = temp_dir.Path(); - test_config.storage.type = StorageType::kSqlite; - - test_storage = INvStorage::newStorage(test_config.storage); - - if (argc != 2) { - std::cerr << "Error: " << argv[0] << " requires the path to an OSTree sysroot as an input argument.\n"; - return EXIT_FAILURE; - } - test_sysroot = argv[1]; - return RUN_ALL_TESTS(); -} -#endif diff --git a/src/aktualizr_secondary/uptane_verification_test.cc b/src/aktualizr_secondary/uptane_verification_test.cc deleted file mode 100644 index 915bf1ed56..0000000000 --- a/src/aktualizr_secondary/uptane_verification_test.cc +++ /dev/null @@ -1,413 +0,0 @@ -#include - -#include -#include -#include "aktualizr_secondary.h" -#include "package_manager/ostreemanager.h" -#include "test_utils.h" -#include "uptane_repo.h" - -class Treehub { - public: - Treehub(const std::string& server_path) - : _port(TestUtils::getFreePort()), - _url("http://127.0.0.1:" + _port), - _process(server_path, "-p", _port, "-d", _root_dir.PathString(), "-s0.5", "--create") { - TestUtils::waitForServer(url() + "/"); - auto rev_process = Process("ostree").run({"rev-parse", "--repo", _root_dir.PathString(), "master"}); - EXPECT_EQ(std::get<0>(rev_process), 0) << std::get<2>(rev_process); - _cur_rev = std::get<1>(rev_process); - boost::trim_right_if(_cur_rev, boost::is_any_of(" \t\r\n")); - - LOG_INFO << "Treehub is running on: " << _port << " current revision: " << _cur_rev; - } - - ~Treehub() { - _process.terminate(); - _process.wait_for(std::chrono::seconds(10)); - if (_process.running()) { - LOG_ERROR << "Failed to stop Treehub server"; - } else { - LOG_INFO << "Treehub server has been stopped"; - } - } - - public: - const std::string& url() const { return _url; } - const std::string curRev() const { return _cur_rev; } - - private: - TemporaryDirectory _root_dir; - const std::string _port; - const std::string _url; - boost::process::child _process; - std::string _cur_rev; -}; - -class OstreeRootfs { - public: - OstreeRootfs(const std::string& rootfs_template) { - auto sysroot_copy = Process("cp").run({"-r", rootfs_template, getPath()}); - EXPECT_EQ(std::get<0>(sysroot_copy), 0) << std::get<1>(sysroot_copy); - - auto deployment_rev = Process("ostree").run( - {"rev-parse", std::string("--repo"), getPath() + "/ostree/repo", "generate-remote/generated"}); - - EXPECT_EQ(std::get<0>(deployment_rev), 0) << std::get<2>(deployment_rev); - - _rev = std::get<1>(deployment_rev); - boost::trim_right_if(_rev, boost::is_any_of(" \t\r\n")); - - _deployment.reset(ostree_deployment_new(0, getOSName(), getDeploymentRev(), getDeploymentSerial(), - getDeploymentRev(), getDeploymentSerial())); - } - - const std::string getPath() const { return _sysroot_dir; } - const char* getDeploymentRev() const { return _rev.c_str(); } - int getDeploymentSerial() const { return 0; } - const char* getOSName() const { return _os_name.c_str(); } - - OstreeDeployment* getDeployment() const { return _deployment.get(); } - - private: - const std::string _os_name{"dummy-os"}; - TemporaryDirectory _tmp_dir; - std::string _sysroot_dir{(_tmp_dir / "ostree-rootfs").c_str()}; - std::string _rev; - GObjectUniquePtr _deployment; -}; - -class AktualizrSecondaryWrapper { - public: - AktualizrSecondaryWrapper(const OstreeRootfs& sysroot, const Treehub& treehub) { - // ostree update - AktualizrSecondaryConfig config; - - config.pacman.type = PackageManager::kOstree; - config.pacman.os = sysroot.getOSName(); - config.pacman.sysroot = sysroot.getPath(); - config.pacman.ostree_server = treehub.url(); - - config.storage.path = _storage_dir.Path(); - config.storage.type = StorageType::kSqlite; - - _storage = INvStorage::newStorage(config.storage); - _secondary = std::make_shared(config, _storage); - } - - AktualizrSecondaryWrapper() { - // binary update - AktualizrSecondaryConfig config; - config.pacman.type = PackageManager::kNone; - - config.storage.path = _storage_dir.Path(); - config.storage.type = StorageType::kSqlite; - - auto storage = INvStorage::newStorage(config.storage); - _secondary = std::make_shared(config, storage); - } - - public: - std::shared_ptr& operator->() { return _secondary; } - - Uptane::Target getPendingVersion() const { - boost::optional pending_target; - - _storage->loadInstalledVersions(_secondary->getSerialResp().ToString(), nullptr, &pending_target); - return *pending_target; - } - - std::string hardwareID() const { return _secondary->getHwIdResp().ToString(); } - - std::string serial() const { return _secondary->getSerialResp().ToString(); } - - private: - TemporaryDirectory _storage_dir; - std::shared_ptr _secondary; - std::shared_ptr _storage; -}; - -class UptaneRepoWrapper { - public: - UptaneRepoWrapper() { _uptane_repo.generateRepo(KeyType::kED25519); } - - Metadata addImageFile(const std::string& targetname, const std::string& hardware_id, const std::string& serial) { - const auto image_file_path = _root_dir / targetname; - boost::filesystem::ofstream(image_file_path) << "some data"; - - _uptane_repo.addImage(image_file_path, targetname, hardware_id, "", Delegation()); - _uptane_repo.addTarget(targetname, hardware_id, serial, ""); - _uptane_repo.signTargets(); - - return getCurrentMetadata(); - } - - Metadata addOstreeRev(const std::string& rev, const std::string& hardware_id, const std::string& serial) { - // it makes sense to add 'addOstreeImage' to UptaneRepo interface/class uptane_repo.h - auto custom = Json::Value(); - custom["targetFormat"] = "OSTREE"; - _uptane_repo.addCustomImage(rev, Uptane::Hash(Uptane::Hash::Type::kSha256, rev), 0, hardware_id, "", Delegation(), - custom); - - _uptane_repo.addTarget(rev, hardware_id, serial, ""); - _uptane_repo.signTargets(); - - return getCurrentMetadata(); - } - - Uptane::RawMetaPack getCurrentMetadata() const { - Uptane::RawMetaPack metadata; - - boost::filesystem::load_string_file(_director_dir / "root.json", metadata.director_root); - boost::filesystem::load_string_file(_director_dir / "targets.json", metadata.director_targets); - - boost::filesystem::load_string_file(_imagerepo_dir / "root.json", metadata.image_root); - boost::filesystem::load_string_file(_imagerepo_dir / "timestamp.json", metadata.image_timestamp); - boost::filesystem::load_string_file(_imagerepo_dir / "snapshot.json", metadata.image_snapshot); - boost::filesystem::load_string_file(_imagerepo_dir / "targets.json", metadata.image_targets); - - return metadata; - } - - std::shared_ptr getImageData(const std::string& targetname) const { - auto image_data = std::make_shared(); - boost::filesystem::load_string_file(_root_dir / targetname, *image_data); - return image_data; - } - - private: - TemporaryDirectory _root_dir; - boost::filesystem::path _director_dir{_root_dir / "repo/director"}; - boost::filesystem::path _imagerepo_dir{_root_dir / "repo/repo"}; - UptaneRepo _uptane_repo{_root_dir.Path(), "", ""}; -}; - -class SecondaryUptaneVerificationTest : public ::testing::Test { - protected: - SecondaryUptaneVerificationTest() { - _uptane_repo.addImageFile(_default_target, _secondary->getHwIdResp().ToString(), - _secondary->getSerialResp().ToString()); - } - - std::shared_ptr getImageData(const std::string& targetname = _default_target) const { - return _uptane_repo.getImageData(targetname); - } - - protected: - static constexpr const char* const _default_target{"defaulttarget"}; - AktualizrSecondaryWrapper _secondary; - UptaneRepoWrapper _uptane_repo; -}; - -class OstreeSecondaryUptaneVerificationTest : public ::testing::Test { - public: - static const char* curOstreeRootfsRev(OstreeDeployment* ostree_depl) { - (void)ostree_depl; - return _sysroot->getDeploymentRev(); - } - - static OstreeDeployment* curOstreeDeployment(OstreeSysroot* ostree_sysroot) { - (void)ostree_sysroot; - return _sysroot->getDeployment(); - } - - static void setOstreeRootfsTemplate(const std::string& ostree_rootfs_template) { - _ostree_rootfs_template = ostree_rootfs_template; - } - - protected: - static void SetUpTestSuite() { - _treehub = std::make_shared("tests/sota_tools/treehub_server.py"); - _sysroot = std::make_shared(_ostree_rootfs_template); - } - - static void TearDownTestSuite() { - _treehub.reset(); - _sysroot.reset(); - } - - protected: - OstreeSecondaryUptaneVerificationTest() {} - - Metadata addDefaultTarget() { return addTarget(_treehub->curRev()); } - - Metadata addTarget(const std::string& rev = "", const std::string& hardware_id = "", const std::string& serial = "") { - auto rev_to_apply = rev.empty() ? _treehub->curRev() : rev; - auto hw_id = hardware_id.empty() ? _secondary.hardwareID() : hardware_id; - auto serial_id = serial.empty() ? _secondary.serial() : serial; - - _uptane_repo.addOstreeRev(rev, hw_id, serial_id); - - return currentMetadata(); - } - - Metadata currentMetadata() const { return _uptane_repo.getCurrentMetadata(); } - - std::shared_ptr getCredsToSend() const { - std::map creds_map = { - {"ca.pem", ""}, {"client.pem", ""}, {"pkey.pem", ""}, {"server.url", _treehub->url()}}; - - std::stringstream creads_strstream; - Utils::writeArchive(creds_map, creads_strstream); - - return std::make_shared(creads_strstream.str()); - } - - Uptane::Hash treehubCurRev() const { return Uptane::Hash(Uptane::Hash::Type::kSha256, _treehub->curRev()); } - - protected: - static std::shared_ptr _treehub; - static std::string _ostree_rootfs_template; - static std::shared_ptr _sysroot; - - AktualizrSecondaryWrapper _secondary{*_sysroot, *_treehub}; - UptaneRepoWrapper _uptane_repo; -}; - -std::shared_ptr OstreeSecondaryUptaneVerificationTest::_treehub{nullptr}; -std::string OstreeSecondaryUptaneVerificationTest::_ostree_rootfs_template{"./build/ostree_repo"}; -std::shared_ptr OstreeSecondaryUptaneVerificationTest::_sysroot{nullptr}; - -class SecondaryUptaneVerificationTestNegative - : public SecondaryUptaneVerificationTest, - public ::testing::WithParamInterface> { - protected: - class MetadataInvalidator : public Metadata { - public: - MetadataInvalidator(const Uptane::RawMetaPack& valid_metadata, const Uptane::RepositoryType& repo, - const Uptane::Role& role) - : Metadata(valid_metadata), _repo_type(repo), _role(role) {} - - bool getRoleMetadata(std::string* result, const Uptane::RepositoryType& repo, const Uptane::Role& role, - Uptane::Version version) const override { - auto return_val = Metadata::getRoleMetadata(result, repo, role, version); - if (!(_repo_type == repo && _role == role)) { - return return_val; - } - (*result)[10] = 'f'; - return true; - } - - private: - Uptane::RepositoryType _repo_type; - Uptane::Role _role; - }; - - protected: - MetadataInvalidator currentMetadata() const { - return MetadataInvalidator(_uptane_repo.getCurrentMetadata(), GetParam().first, GetParam().second); - } -}; - -/** - * Parameterized test, - * The parameter is std::pair to indicate which metadata to malform - * - * see INSTANTIATE_TEST_SUITE_P for the test instantiations with concrete parameter values - */ -TEST_P(SecondaryUptaneVerificationTestNegative, MalformedMetadaJson) { - EXPECT_FALSE(_secondary->putMetadataResp(currentMetadata())); -} - -/** - * Instantiates the parameterized test for each specified value of std::pair - * the parameter value indicates which metadata to malform - */ -INSTANTIATE_TEST_SUITE_P(SecondaryUptaneVerificationMalformedMetadata, SecondaryUptaneVerificationTestNegative, - ::testing::Values(std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Root()), - std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Targets()), - std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Root()), - std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Timestamp()), - std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Snapshot()), - std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Targets()))); - -TEST_F(OstreeSecondaryUptaneVerificationTest, fullUptaneVerificationPositive) { - EXPECT_TRUE(_secondary->putMetadataResp(addDefaultTarget())); - EXPECT_TRUE(_secondary->sendFirmwareResp(getCredsToSend())); - EXPECT_TRUE(_secondary.getPendingVersion().MatchHash(treehubCurRev())); - // TODO: emulate reboot and check installed version once ostree update finalization is supported by secondary -} - -TEST_F(OstreeSecondaryUptaneVerificationTest, fullUptaneVerificationInvalidRevision) { - EXPECT_TRUE(_secondary->putMetadataResp(addTarget("invalid-revision"))); - EXPECT_FALSE(_secondary->sendFirmwareResp(getCredsToSend())); -} - -TEST_F(OstreeSecondaryUptaneVerificationTest, fullUptaneVerificationInvalidHwID) { - EXPECT_FALSE(_secondary->putMetadataResp(addTarget("", "invalid-hardware-id", ""))); -} - -TEST_F(OstreeSecondaryUptaneVerificationTest, fullUptaneVerificationInvalidSerial) { - EXPECT_FALSE(_secondary->putMetadataResp(addTarget("", "", "invalid-serial-id"))); -} - -TEST_F(SecondaryUptaneVerificationTest, fullUptaneVerificationPositive) { - EXPECT_TRUE(_secondary->putMetadataResp(_uptane_repo.getCurrentMetadata())); - EXPECT_TRUE(_secondary->sendFirmwareResp(getImageData())); -} - -TEST_F(SecondaryUptaneVerificationTest, IncorrectTargetQuantity) { - { - // two targets for the same ECU - _uptane_repo.addImageFile("second_target", _secondary->getHwIdResp().ToString(), - _secondary->getSerialResp().ToString()); - - EXPECT_FALSE(_secondary->putMetadataResp(_uptane_repo.getCurrentMetadata())); - } - - { - // zero targets for the ECU being tested - auto metadata = - UptaneRepoWrapper().addImageFile("mytarget", _secondary->getHwIdResp().ToString(), "non-existing-serial"); - - EXPECT_FALSE(_secondary->putMetadataResp(metadata)); - } - - { - // zero targets for the ECU being tested - auto metadata = - UptaneRepoWrapper().addImageFile("mytarget", "non-existig-hwid", _secondary->getSerialResp().ToString()); - - EXPECT_FALSE(_secondary->putMetadataResp(metadata)); - } -} - -TEST_F(SecondaryUptaneVerificationTest, InvalidImageFileSize) { - EXPECT_TRUE(_secondary->putMetadataResp(_uptane_repo.getCurrentMetadata())); - auto image_data = getImageData(); - image_data->append("\n"); - EXPECT_FALSE(_secondary->sendFirmwareResp(image_data)); -} - -TEST_F(SecondaryUptaneVerificationTest, InvalidImageData) { - EXPECT_TRUE(_secondary->putMetadataResp(_uptane_repo.getCurrentMetadata())); - auto image_data = getImageData(); - image_data->operator[](3) = '0'; - EXPECT_FALSE(_secondary->sendFirmwareResp(image_data)); -} - -#ifndef __NO_MAIN__ -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - - if (argc != 2) { - std::cerr << "Error: " << argv[0] << " \n"; - return EXIT_FAILURE; - } - - OstreeSecondaryUptaneVerificationTest::setOstreeRootfsTemplate(argv[1]); - - logger_init(); - logger_set_threshold(boost::log::trivial::info); - - return RUN_ALL_TESTS(); -} -#endif - -extern "C" OstreeDeployment* ostree_sysroot_get_booted_deployment(OstreeSysroot* ostree_sysroot) { - return OstreeSecondaryUptaneVerificationTest::curOstreeDeployment(ostree_sysroot); -} - -extern "C" const char* ostree_deployment_get_csum(OstreeDeployment* ostree_deployment) { - return OstreeSecondaryUptaneVerificationTest::curOstreeRootfsRev(ostree_deployment); -} diff --git a/src/cert_provider/cert_provider_test.cc b/src/cert_provider/cert_provider_test.cc index 720580fe2a..a5cd7be5d8 100644 --- a/src/cert_provider/cert_provider_test.cc +++ b/src/cert_provider/cert_provider_test.cc @@ -4,6 +4,7 @@ #include "cert_provider_test.h" #include "config/config.h" +#include "crypto/crypto.h" #include "utilities/utils.h" static boost::filesystem::path CERT_PROVIDER_PATH; diff --git a/src/libaktualizr-posix/asn1/asn1_message.cc b/src/libaktualizr-posix/asn1/asn1_message.cc index 855a85d9fb..3419ce324a 100644 --- a/src/libaktualizr-posix/asn1/asn1_message.cc +++ b/src/libaktualizr-posix/asn1/asn1_message.cc @@ -1,12 +1,10 @@ #include "asn1_message.h" +#include +#include #include "logging/logging.h" #include "utilities/dequeue_buffer.h" -#include "utilities/sockaddr_io.h" #include "utilities/utils.h" -#include -#include - #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif @@ -87,11 +85,12 @@ Asn1Message::Ptr Asn1Rpc(const Asn1Message::Ptr& tx, int con_fd) { } Asn1Message::Ptr Asn1Rpc(const Asn1Message::Ptr& tx, const std::pair& addr) { - Socket connection(addr.first, addr.second); + ConnectionSocket connection(addr.first, addr.second); if (connection.connect() < 0) { - LOG_ERROR << "Failed to connect to the secondary: " << std::strerror(errno); + LOG_ERROR << "Failed to connect to the secondary ( " << addr.first << ":" << addr.second + << "): " << std::strerror(errno); return Asn1Message::Empty(); } - return Asn1Rpc(tx, connection.getFD()); + return Asn1Rpc(tx, *connection); } diff --git a/src/libaktualizr-posix/asn1/asn1_message.h b/src/libaktualizr-posix/asn1/asn1_message.h index b0cabf67a8..77ae14ad45 100644 --- a/src/libaktualizr-posix/asn1/asn1_message.h +++ b/src/libaktualizr-posix/asn1/asn1_message.h @@ -76,6 +76,8 @@ class Asn1Message { ASN1_MESSAGE_DEFINE_ACCESSOR(AKPutMetaRespMes_t, putMetaResp); ASN1_MESSAGE_DEFINE_ACCESSOR(AKSendFirmwareReqMes_t, sendFirmwareReq); ASN1_MESSAGE_DEFINE_ACCESSOR(AKSendFirmwareRespMes_t, sendFirmwareResp); + ASN1_MESSAGE_DEFINE_ACCESSOR(AKInstallReqMes_t, installReq); + ASN1_MESSAGE_DEFINE_ACCESSOR(AKInstallRespMes_t, installResp); /** * The underlying message structure. This is public to simplify calls to diff --git a/src/libaktualizr-posix/asn1/messages/ipuptane_message.asn1 b/src/libaktualizr-posix/asn1/messages/ipuptane_message.asn1 index 39c8cb0fc1..ddbcf8140d 100644 --- a/src/libaktualizr-posix/asn1/messages/ipuptane_message.asn1 +++ b/src/libaktualizr-posix/asn1/messages/ipuptane_message.asn1 @@ -1,95 +1,121 @@ IpUptane DEFINITIONS ::= BEGIN -- Keep these in Sync with Uptane::KeyType AKIpUptaneKeyType ::= ENUMERATED { - ed25519(0), - rsa2048(1), - rsa4096(2), - unknownKey(255), - ... - } - - AKInstallationResult ::= ENUMERATED { - success, - failure, - ... - } - - - -- Json format image repository metadata - AKImageMetaJson ::= SEQUENCE { - root OCTET STRING, - timestamp OCTET STRING, - snapshot OCTET STRING, - targets OCTET STRING, - ... - } - - -- Json format director metadata - AKDirectorMetaJson ::= SEQUENCE { - root OCTET STRING, - targets OCTET STRING, - ... - } - - AKGetInfoReqMes ::= SEQUENCE { - ... - } - - AKGetInfoRespMes ::= SEQUENCE { - ecuSerial UTF8String, - hwId UTF8String, - keyType AKIpUptaneKeyType, - key OCTET STRING, - ... - } - - AKManifestReqMes ::= SEQUENCE { - ... - } - - AKManifestRespMes ::= SEQUENCE { - manifest CHOICE { - json OCTET STRING - }, - ... - } - - - AKPutMetaReqMes ::= SEQUENCE { - image CHOICE { - json AKImageMetaJson - }, - director CHOICE { - json AKDirectorMetaJson - }, - ... - } - - AKPutMetaRespMes ::= SEQUENCE { - result AKInstallationResult, - ... - } - - AKSendFirmwareReqMes ::= SEQUENCE { - firmware OCTET STRING, - ... - } - - AKSendFirmwareRespMes ::= SEQUENCE { - result AKInstallationResult, - ... - } - - AKIpUptaneMes ::= CHOICE { - getInfoReq [0] AKGetInfoReqMes, - getInfoResp [1] AKGetInfoRespMes, - manifestReq [2] AKManifestReqMes, - manifestResp [3] AKManifestRespMes, - putMetaReq [4] AKPutMetaReqMes, - putMetaResp [5] AKPutMetaRespMes, - sendFirmwareReq [6] AKSendFirmwareReqMes, - sendFirmwareResp [7] AKSendFirmwareRespMes, - ... - } + ed25519(0), + rsa2048(1), + rsa4096(2), + unknownKey(255), + ... + } + + AKInstallationResult ::= ENUMERATED { + success, + failure, + ... + } + + -- Keep these in Sync with data::ResultCode::Numeric + AKInstallationResultCode ::= ENUMERATED { + ok(0), + alreadyProcessed(1), + validationFailed(3), + installFailed(4), + downloadFailed(5), + internalError(18), + generalError(19), + needCompletion(21), + customError(22), + unknown(-1), + ... + } + + -- Json format image repository metadata + AKImageMetaJson ::= SEQUENCE { + root OCTET STRING, + timestamp OCTET STRING, + snapshot OCTET STRING, + targets OCTET STRING, + ... + } + + -- Json format director metadata + AKDirectorMetaJson ::= SEQUENCE { + root OCTET STRING, + targets OCTET STRING, + ... + } + + AKGetInfoReqMes ::= SEQUENCE { + ... + } + + AKGetInfoRespMes ::= SEQUENCE { + ecuSerial UTF8String, + hwId UTF8String, + keyType AKIpUptaneKeyType, + key OCTET STRING, + ... + } + + AKManifestReqMes ::= SEQUENCE { + ... + } + + AKManifestRespMes ::= SEQUENCE { + manifest CHOICE { + json OCTET STRING + }, + ... + } + + + AKPutMetaReqMes ::= SEQUENCE { + image CHOICE { + json AKImageMetaJson + }, + director CHOICE { + json AKDirectorMetaJson + }, + ... + } + + AKPutMetaRespMes ::= SEQUENCE { + result AKInstallationResult, + ... + } + + AKSendFirmwareReqMes ::= SEQUENCE { + firmware OCTET STRING, + ... + } + + AKSendFirmwareRespMes ::= SEQUENCE { + result AKInstallationResult, + ... + } + + AKInstallReqMes ::= SEQUENCE { + hash OCTET STRING, + ... + } + + AKInstallRespMes ::= SEQUENCE { + result AKInstallationResultCode, + ... + } + + AKIpUptaneMes ::= CHOICE { + getInfoReq [0] AKGetInfoReqMes, + getInfoResp [1] AKGetInfoRespMes, + manifestReq [2] AKManifestReqMes, + manifestResp [3] AKManifestRespMes, + putMetaReq [4] AKPutMetaReqMes, + putMetaResp [5] AKPutMetaRespMes, + sendFirmwareReq [6] AKSendFirmwareReqMes, + sendFirmwareResp [7] AKSendFirmwareRespMes, + installReq [8] AKInstallReqMes, + installResp [9] AKInstallRespMes, + ... + } END diff --git a/src/libaktualizr-posix/ipuptanesecondary.cc b/src/libaktualizr-posix/ipuptanesecondary.cc index 82d5ca1fad..dcf67351c0 100644 --- a/src/libaktualizr-posix/ipuptanesecondary.cc +++ b/src/libaktualizr-posix/ipuptanesecondary.cc @@ -10,29 +10,28 @@ namespace Uptane { -std::pair> IpUptaneSecondary::connectAndCreate( - const std::string& address, unsigned short port) { +Uptane::SecondaryInterface::Ptr IpUptaneSecondary::connectAndCreate(const std::string& address, unsigned short port) { LOG_INFO << "Connecting to and getting info about IP Secondary: " << address << ":" << port << "..."; - Socket con_sock{address, port}; + ConnectionSocket con_sock{address, port}; if (con_sock.connect() != 0) { LOG_ERROR << "Failed to connect to a secondary: " << std::strerror(errno); - return {false, std::shared_ptr()}; + return std::shared_ptr(); } LOG_INFO << "Connected to IP Secondary: " << "(" << address << ":" << port << ")"; - return create(address, port, con_sock.getFD()); + return create(address, port, *con_sock); } -std::pair> IpUptaneSecondary::create(const std::string& address, - unsigned short port, - int con_fd) { +Uptane::SecondaryInterface::Ptr IpUptaneSecondary::create(const std::string& address, unsigned short port, int con_fd) { Asn1Message::Ptr req(Asn1Message::Empty()); req->present(AKIpUptaneMes_PR_getInfoReq); + auto m = req->getInfoReq(); + auto resp = Asn1Rpc(req, con_fd); if (resp->present() != AKIpUptaneMes_PR_getInfoResp) { @@ -50,7 +49,7 @@ std::pair> IpUptaneSecondary:: LOG_INFO << "Got info on IP Secondary: " << "hw-ID: " << hw_id << " serial: " << serial; - return {true, std::make_shared(address, port, serial, hw_id, pub_key)}; + return std::make_shared(address, port, serial, hw_id, pub_key); } IpUptaneSecondary::IpUptaneSecondary(const std::string& address, unsigned short port, EcuSerial serial, @@ -84,14 +83,14 @@ bool IpUptaneSecondary::putMetadata(const RawMetaPack& meta_pack) { return r->result == AKInstallationResult_success; } -bool IpUptaneSecondary::sendFirmware(const std::shared_ptr& data) { +bool IpUptaneSecondary::sendFirmware(const std::string& data) { std::lock_guard l(install_mutex); LOG_INFO << "Sending firmware to the secondary"; Asn1Message::Ptr req(Asn1Message::Empty()); req->present(AKIpUptaneMes_PR_sendFirmwareReq); auto m = req->sendFirmwareReq(); - SetString(&m->firmware, *data); + SetString(&m->firmware, data); auto resp = Asn1Rpc(req, getAddr()); if (resp->present() != AKIpUptaneMes_PR_sendFirmwareResp) { @@ -103,7 +102,31 @@ bool IpUptaneSecondary::sendFirmware(const std::shared_ptr& data) { return r->result == AKInstallationResult_success; } -Json::Value IpUptaneSecondary::getManifest() { +data::ResultCode::Numeric IpUptaneSecondary::install(const std::string& target_name) { + LOG_INFO << "Invoking an installation of the target on the secondary: " << target_name; + + Asn1Message::Ptr req(Asn1Message::Empty()); + req->present(AKIpUptaneMes_PR_installReq); + + // prepare request message + auto req_mes = req->installReq(); + SetString(&req_mes->hash, target_name); + // send request and receive response, a request-response type of RPC + auto resp = Asn1Rpc(req, getAddr()); + + // invalid type of an response message + if (resp->present() != AKIpUptaneMes_PR_installResp) { + LOG_ERROR << "Failed to get response to an installation request to secondary"; + return data::ResultCode::Numeric::kInternalError; + } + + // deserialize the response message + auto r = resp->installResp(); + + return static_cast(r->result); +} + +Manifest IpUptaneSecondary::getManifest() const { LOG_INFO << "Getting the manifest key of a secondary"; Asn1Message::Ptr req(Asn1Message::Empty()); diff --git a/src/libaktualizr-posix/ipuptanesecondary.h b/src/libaktualizr-posix/ipuptanesecondary.h index 68e423d0cb..6b1e717d47 100644 --- a/src/libaktualizr-posix/ipuptanesecondary.h +++ b/src/libaktualizr-posix/ipuptanesecondary.h @@ -10,26 +10,24 @@ namespace Uptane { class IpUptaneSecondary : public SecondaryInterface { public: - static std::pair> connectAndCreate(const std::string& address, - unsigned short port); + static SecondaryInterface::Ptr connectAndCreate(const std::string& address, unsigned short port); - static std::pair> create(const std::string& address, - unsigned short port, int con_fd); + static SecondaryInterface::Ptr create(const std::string& address, unsigned short port, int con_fd); explicit IpUptaneSecondary(const std::string& address, unsigned short port, EcuSerial serial, HardwareIdentifier hw_id, PublicKey pub_key); // It looks more natural to return const EcuSerial& and const Uptane::HardwareIdentifier& - // and they should be 'const' methods - EcuSerial getSerial() /*const*/ override { return serial_; }; - Uptane::HardwareIdentifier getHwId() /*const*/ override { return hw_id_; } - PublicKey getPublicKey() /*const*/ override { return pub_key_; } + EcuSerial getSerial() const override { return serial_; }; + Uptane::HardwareIdentifier getHwId() const override { return hw_id_; } + PublicKey getPublicKey() const override { return pub_key_; } bool putMetadata(const RawMetaPack& meta_pack) override; - int32_t getRootVersion(bool /* director */) override { return 0; } + int32_t getRootVersion(bool /* director */) const override { return 0; } bool putRoot(const std::string& /* root */, bool /* director */) override { return true; } - bool sendFirmware(const std::shared_ptr& data) override; - Json::Value getManifest() override; + bool sendFirmware(const std::string& data) override; + data::ResultCode::Numeric install(const std::string& target_name) override; + Manifest getManifest() const override; private: const std::pair& getAddr() const { return addr_; } diff --git a/src/libaktualizr/bootloader/bootloader.cc b/src/libaktualizr/bootloader/bootloader.cc index 94b9a46e93..4578a4d732 100644 --- a/src/libaktualizr/bootloader/bootloader.cc +++ b/src/libaktualizr/bootloader/bootloader.cc @@ -7,10 +7,12 @@ #include #include "bootloader.h" +#include "storage/invstorage.h" #include "utilities/exceptions.h" #include "utilities/utils.h" -Bootloader::Bootloader(BootloaderConfig config, INvStorage& storage) : config_(std::move(config)), storage_(storage) { +Bootloader::Bootloader(BootloaderConfig config, std::shared_ptr storage) + : config_(std::move(config)), storage_(std::move(storage)) { reboot_sentinel_ = config_.reboot_sentinel_dir / config_.reboot_sentinel_name; reboot_command_ = config_.reboot_command; @@ -87,7 +89,7 @@ bool Bootloader::rebootDetected() const { bool sentinel_exists = boost::filesystem::exists(reboot_sentinel_); bool need_reboot = false; - storage_.loadNeedReboot(&need_reboot); + storage_->loadNeedReboot(&need_reboot); return need_reboot && !sentinel_exists; } @@ -100,7 +102,7 @@ void Bootloader::rebootFlagSet() { // set in storage + volatile flag Utils::writeFile(reboot_sentinel_, std::string(), false); // empty file - storage_.storeNeedReboot(); + storage_->storeNeedReboot(); } void Bootloader::rebootFlagClear() { @@ -110,7 +112,7 @@ void Bootloader::rebootFlagClear() { // clear in storage + volatile flag - storage_.clearNeedReboot(); + storage_->clearNeedReboot(); boost::filesystem::remove(reboot_sentinel_); } diff --git a/src/libaktualizr/bootloader/bootloader.h b/src/libaktualizr/bootloader/bootloader.h index 4a795958a4..2342ec8b49 100644 --- a/src/libaktualizr/bootloader/bootloader.h +++ b/src/libaktualizr/bootloader/bootloader.h @@ -3,11 +3,15 @@ #include "bootloader_config.h" -#include "storage/invstorage.h" +class INvStorage; class Bootloader { public: - Bootloader(BootloaderConfig config, INvStorage& storage); + Bootloader(BootloaderConfig config, std::shared_ptr storage); + Bootloader(const Bootloader&) = delete; + Bootloader& operator=(const Bootloader&) = delete; + + public: void setBootOK() const; void updateNotify() const; @@ -27,7 +31,7 @@ class Bootloader { private: const BootloaderConfig config_; - INvStorage& storage_; + std::shared_ptr storage_; boost::filesystem::path reboot_sentinel_; std::string reboot_command_; bool reboot_detect_supported_{false}; diff --git a/src/libaktualizr/bootloader/bootloader_test.cc b/src/libaktualizr/bootloader/bootloader_test.cc index a29b9a274a..d0f9124118 100644 --- a/src/libaktualizr/bootloader/bootloader_test.cc +++ b/src/libaktualizr/bootloader/bootloader_test.cc @@ -1,6 +1,8 @@ #include #include "bootloader.h" +#include "storage/invstorage.h" +#include "utilities/utils.h" /* Check that the reboot detection feature works */ TEST(bootloader, detectReboot) { @@ -11,7 +13,7 @@ TEST(bootloader, detectReboot) { BootloaderConfig boot_config; boot_config.reboot_sentinel_dir = temp_dir.Path(); - Bootloader bootloader(boot_config, *storage); + Bootloader bootloader(boot_config, storage); ASSERT_TRUE(bootloader.supportRebootDetection()); diff --git a/src/libaktualizr/campaign/campaign.cc b/src/libaktualizr/campaign/campaign.cc index 1341e6b96b..7e0b8443ec 100644 --- a/src/libaktualizr/campaign/campaign.cc +++ b/src/libaktualizr/campaign/campaign.cc @@ -68,7 +68,7 @@ void Campaign::getJson(Json::Value &out) const { out["metadata"][2]["value"] = std::to_string(estPreparationDuration); } -std::vector campaignsFromJson(const Json::Value &json) { +std::vector Campaign::campaignsFromJson(const Json::Value &json) { std::vector campaigns; Json::Value campaigns_array; @@ -95,7 +95,7 @@ std::vector campaignsFromJson(const Json::Value &json) { return campaigns; } -void JsonFromCampaigns(const std::vector &in, Json::Value &out) { +void Campaign::JsonFromCampaigns(const std::vector &in, Json::Value &out) { out.clear(); auto i = 0; Json::Value json; @@ -106,7 +106,7 @@ void JsonFromCampaigns(const std::vector &in, Json::Value &out) { } } -std::vector fetchAvailableCampaigns(HttpInterface &http_client, const std::string &tls_server) { +std::vector Campaign::fetchAvailableCampaigns(HttpInterface &http_client, const std::string &tls_server) { HttpResponse response = http_client.get(tls_server + "/campaigner/campaigns", kMaxCampaignsMetaSize); if (!response.isOk()) { LOG_ERROR << "Failed to fetch list of available campaigns"; diff --git a/src/libaktualizr/campaign/campaign.h b/src/libaktualizr/campaign/campaign.h index cb91f1d9a6..5d5331f885 100644 --- a/src/libaktualizr/campaign/campaign.h +++ b/src/libaktualizr/campaign/campaign.h @@ -25,10 +25,15 @@ static inline Cmd cmdFromName(const std::string &name) { return std::map{ {"campaign_accept", Cmd::Accept}, {"campaign_decline", Cmd::Decline}, {"campaign_postpone", Cmd::Postpone}} .at(name); -} +}; // Out of uptane concept: update campaign for a device class Campaign { + public: + static std::vector campaignsFromJson(const Json::Value &json); + static void JsonFromCampaigns(const std::vector &in, Json::Value &out); + static std::vector fetchAvailableCampaigns(HttpInterface &http_client, const std::string &tls_server); + public: Campaign() = default; Campaign(const Json::Value &json); @@ -43,9 +48,6 @@ class Campaign { int estPreparationDuration{0}; }; -std::vector campaignsFromJson(const Json::Value &json); -void JsonFromCampaigns(const std::vector &in, Json::Value &out); -std::vector fetchAvailableCampaigns(HttpInterface &http_client, const std::string &tls_server); } // namespace campaign #endif diff --git a/src/libaktualizr/campaign/campaign_test.cc b/src/libaktualizr/campaign/campaign_test.cc index e1a3dfd5a7..21be1c7153 100644 --- a/src/libaktualizr/campaign/campaign_test.cc +++ b/src/libaktualizr/campaign/campaign_test.cc @@ -10,7 +10,7 @@ boost::filesystem::path test_data_dir; TEST(campaign, Campaigns_from_json) { auto json = Utils::parseJSONFile(test_data_dir / "campaigns_sample.json"); - auto campaigns = campaign::campaignsFromJson(json); + auto campaigns = campaign::Campaign::campaignsFromJson(json); EXPECT_EQ(campaigns.size(), 1); EXPECT_EQ(campaigns.at(0).name, "campaign1"); @@ -27,7 +27,7 @@ TEST(campaign, Campaigns_from_json) { bad4["campaigns"][0] = Json::Value(); bad4["campaigns"][0]["name"] = "a"; bad4["campaigns"][0]["id"] = "a"; - auto campaignsNoAutoAccept = campaign::campaignsFromJson(bad4); + auto campaignsNoAutoAccept = campaign::Campaign::campaignsFromJson(bad4); EXPECT_FALSE(campaignsNoAutoAccept.at(0).autoAccept); } @@ -35,9 +35,9 @@ TEST(campaign, Campaigns_from_json) { TEST(campaign, Campaigns_to_json) { auto json = Utils::parseJSONFile(test_data_dir / "campaigns_sample.json"); - auto campaigns = campaign::campaignsFromJson(json); + auto campaigns = campaign::Campaign::campaignsFromJson(json); Json::Value res; - JsonFromCampaigns(campaigns, res); + campaign::Campaign::JsonFromCampaigns(campaigns, res); EXPECT_EQ(res["campaigns"][0]["name"], "campaign1"); EXPECT_EQ(res["campaigns"][0]["id"], "c2eb7e8d-8aa0-429d-883f-5ed8fdb2a493"); @@ -53,27 +53,27 @@ TEST(campaign, Campaigns_to_json) { TEST(campaign, Campaigns_from_invalid_json) { // empty object - EXPECT_EQ(campaign::campaignsFromJson(Json::Value()).size(), 0); + EXPECT_EQ(campaign::Campaign::campaignsFromJson(Json::Value()).size(), 0); // naked array - EXPECT_EQ(campaign::campaignsFromJson(Json::Value(Json::arrayValue)).size(), 0); + EXPECT_EQ(campaign::Campaign::campaignsFromJson(Json::Value(Json::arrayValue)).size(), 0); // object in object Json::Value bad1; bad1["campaigns"] = Json::Value(); - EXPECT_EQ(campaign::campaignsFromJson(bad1).size(), 0); + EXPECT_EQ(campaign::Campaign::campaignsFromJson(bad1).size(), 0); // array in array in object Json::Value bad2; bad2["campaigns"] = Json::Value(Json::arrayValue); bad2["campaigns"][0] = Json::Value(Json::arrayValue); - EXPECT_EQ(campaign::campaignsFromJson(bad2).size(), 0); + EXPECT_EQ(campaign::Campaign::campaignsFromJson(bad2).size(), 0); // no name Json::Value bad3; bad3["campaigns"] = Json::Value(Json::arrayValue); bad3["campaigns"][0] = Json::Value(); - EXPECT_EQ(campaign::campaignsFromJson(bad3).size(), 0); + EXPECT_EQ(campaign::Campaign::campaignsFromJson(bad3).size(), 0); } #ifndef __NO_MAIN__ diff --git a/src/libaktualizr/crypto/keymanager.cc b/src/libaktualizr/crypto/keymanager.cc index e42b75a3e6..2aaeb0bd20 100644 --- a/src/libaktualizr/crypto/keymanager.cc +++ b/src/libaktualizr/crypto/keymanager.cc @@ -1,4 +1,5 @@ #include "keymanager.h" +#include "storage/invstorage.h" #include "utilities/types.h" #include @@ -231,6 +232,7 @@ Json::Value KeyManager::signTuf(const Json::Value &in_data) const { } Json::Value signature; + // TODO: FIX the hardcoded value of a signature method/algorithm signature["method"] = "rsassa-pss"; signature["sig"] = b64sig; diff --git a/src/libaktualizr/crypto/keymanager.h b/src/libaktualizr/crypto/keymanager.h index d7c43f884d..c067e197a2 100644 --- a/src/libaktualizr/crypto/keymanager.h +++ b/src/libaktualizr/crypto/keymanager.h @@ -1,19 +1,22 @@ #ifndef KEYMANAGER_H_ #define KEYMANAGER_H_ -#include "keymanager_config.h" - +#include "crypto.h" #include "http/httpinterface.h" +#include "keymanager_config.h" #include "p11engine.h" -#include "storage/invstorage.h" #include "utilities/utils.h" +class INvStorage; + class KeyManager { public: + KeyManager(std::shared_ptr backend, KeyManagerConfig config); + KeyManager(const KeyManager &) = delete; + KeyManager &operator=(const KeyManager &) = delete; // std::string RSAPSSSign(const std::string &message); // Contains the logic from HttpClient::setCerts() void copyCertsToCurl(HttpInterface &http); - KeyManager(std::shared_ptr backend, KeyManagerConfig config); void loadKeys(const std::string *pkey_content = nullptr, const std::string *cert_content = nullptr, const std::string *ca_content = nullptr); std::string getPkeyFile() const; diff --git a/src/libaktualizr/package_manager/debianmanager.h b/src/libaktualizr/package_manager/debianmanager.h index fa6ca0a76e..51699b9693 100644 --- a/src/libaktualizr/package_manager/debianmanager.h +++ b/src/libaktualizr/package_manager/debianmanager.h @@ -9,9 +9,9 @@ class DebianManager : public PackageManagerInterface { public: - DebianManager(PackageConfig pconfig, BootloaderConfig bconfig, std::shared_ptr storage, - std::shared_ptr http) - : PackageManagerInterface(std::move(pconfig), std::move(bconfig), std::move(storage), std::move(http)) {} + DebianManager(const PackageConfig &pconfig, const BootloaderConfig &bconfig, + const std::shared_ptr &storage, const std::shared_ptr &http) + : PackageManagerInterface(pconfig, bconfig, storage, http) {} ~DebianManager() override = default; std::string name() const override { return "debian"; } Json::Value getInstalledPackages() const override; diff --git a/src/libaktualizr/package_manager/dockerappmanager.h b/src/libaktualizr/package_manager/dockerappmanager.h index 3a66bfe831..79eed29225 100644 --- a/src/libaktualizr/package_manager/dockerappmanager.h +++ b/src/libaktualizr/package_manager/dockerappmanager.h @@ -8,9 +8,9 @@ using DockerAppCb = std::function storage, - std::shared_ptr http) - : OstreeManager(std::move(pconfig), std::move(bconfig), std::move(storage), std::move(http)) { + DockerAppManager(const PackageConfig &pconfig, const BootloaderConfig &bconfig, + const std::shared_ptr &storage, const std::shared_ptr &http) + : OstreeManager(pconfig, bconfig, storage, http) { fake_fetcher_ = std::make_shared("", "", http_); } bool fetchTarget(const Uptane::Target &target, Uptane::Fetcher &fetcher, const KeyManager &keys, diff --git a/src/libaktualizr/package_manager/ostreemanager.cc b/src/libaktualizr/package_manager/ostreemanager.cc index c32f7b4c75..a255300d66 100644 --- a/src/libaktualizr/package_manager/ostreemanager.cc +++ b/src/libaktualizr/package_manager/ostreemanager.cc @@ -196,6 +196,8 @@ data::InstallationResult OstreeManager::install(const Uptane::Target &target) co // set reboot flag to be notified later if (bootloader_ != nullptr) { + LOG_INFO << "Setting the reboot sentinel/flag"; + bootloader_->rebootFlagSet(); } @@ -222,9 +224,9 @@ data::InstallationResult OstreeManager::finalizeInstall(const Uptane::Target &ta return data::InstallationResult(data::ResultCode::Numeric::kOk, "Successfully booted on new version"); } -OstreeManager::OstreeManager(PackageConfig pconfig, BootloaderConfig bconfig, std::shared_ptr storage, - std::shared_ptr http) - : PackageManagerInterface(std::move(pconfig), std::move(bconfig), std::move(storage), std::move(http)) { +OstreeManager::OstreeManager(PackageConfig pconfig, std::shared_ptr bootloader, + std::shared_ptr storage, std::shared_ptr http) + : PackageManagerInterface(std::move(pconfig), std::move(bootloader), std::move(storage), std::move(http)) { GObjectUniquePtr sysroot_smart = OstreeManager::LoadSysroot(config.sysroot); if (sysroot_smart == nullptr) { throw std::runtime_error("Could not find OSTree sysroot at: " + config.sysroot.string()); diff --git a/src/libaktualizr/package_manager/ostreemanager.h b/src/libaktualizr/package_manager/ostreemanager.h index b86a0a5eb1..4e3d907346 100644 --- a/src/libaktualizr/package_manager/ostreemanager.h +++ b/src/libaktualizr/package_manager/ostreemanager.h @@ -39,7 +39,11 @@ struct PullMetaStruct { class OstreeManager : public PackageManagerInterface { public: - OstreeManager(PackageConfig pconfig, BootloaderConfig bconfig, std::shared_ptr storage, + OstreeManager(const PackageConfig &pconfig, const BootloaderConfig &bconfig, + const std::shared_ptr &storage, const std::shared_ptr &http) + : OstreeManager(pconfig, std::make_shared(bconfig, storage), storage, http) {} + + OstreeManager(PackageConfig pconfig, std::shared_ptr bootloader, std::shared_ptr storage, std::shared_ptr http); ~OstreeManager() override = default; std::string name() const override { return "ostree"; } diff --git a/src/libaktualizr/package_manager/packagemanagerconfig.h b/src/libaktualizr/package_manager/packagemanagerconfig.h index cb6db7401e..ee08ee7257 100644 --- a/src/libaktualizr/package_manager/packagemanagerconfig.h +++ b/src/libaktualizr/package_manager/packagemanagerconfig.h @@ -16,6 +16,10 @@ struct PackageConfig { #else PackageManager type{PackageManager::kNone}; #endif + // // optional, if not specified nothing is updated, just image data are verified and delivered + // boost::filesystem::path filepath; + // // optional, if not specified any target can be applied + // std::string target_name; std::string os; boost::filesystem::path sysroot; std::string ostree_server; diff --git a/src/libaktualizr/package_manager/packagemanagerfake.h b/src/libaktualizr/package_manager/packagemanagerfake.h index 9245e73453..3b42514a05 100644 --- a/src/libaktualizr/package_manager/packagemanagerfake.h +++ b/src/libaktualizr/package_manager/packagemanagerfake.h @@ -8,9 +8,9 @@ class PackageManagerFake : public PackageManagerInterface { public: - PackageManagerFake(PackageConfig pconfig, BootloaderConfig bconfig, std::shared_ptr storage, - std::shared_ptr http) - : PackageManagerInterface(std::move(pconfig), std::move(bconfig), std::move(storage), std::move(http)) {} + PackageManagerFake(const PackageConfig &pconfig, const BootloaderConfig &bconfig, + const std::shared_ptr &storage, const std::shared_ptr &http) + : PackageManagerInterface(pconfig, bconfig, storage, http) {} ~PackageManagerFake() override = default; std::string name() const override { return "fake"; } Json::Value getInstalledPackages() const override; diff --git a/src/libaktualizr/package_manager/packagemanagerfake_test.cc b/src/libaktualizr/package_manager/packagemanagerfake_test.cc index d085a9ed57..9c606ff8dc 100644 --- a/src/libaktualizr/package_manager/packagemanagerfake_test.cc +++ b/src/libaktualizr/package_manager/packagemanagerfake_test.cc @@ -77,7 +77,7 @@ TEST(PackageManagerFake, FinalizeAfterReboot) { config.bootloader.reboot_sentinel_dir = temp_dir.Path(); config.storage.path = temp_dir.Path(); std::shared_ptr storage = INvStorage::newStorage(config.storage); - std::shared_ptr bootloader = std::make_shared(config.bootloader, *storage); + std::shared_ptr bootloader = std::make_shared(config.bootloader, storage); PackageManagerFake fakepm(config.pacman, config.bootloader, storage, nullptr); diff --git a/src/libaktualizr/package_manager/packagemanagerinterface.h b/src/libaktualizr/package_manager/packagemanagerinterface.h index e8ce4b5f0d..4aab9882da 100644 --- a/src/libaktualizr/package_manager/packagemanagerinterface.h +++ b/src/libaktualizr/package_manager/packagemanagerinterface.h @@ -35,12 +35,16 @@ enum class TargetStatus { class PackageManagerInterface { public: - PackageManagerInterface(PackageConfig pconfig, BootloaderConfig bconfig, std::shared_ptr storage, - std::shared_ptr http) + PackageManagerInterface(const PackageConfig& pconfig, const BootloaderConfig& bconfig, + const std::shared_ptr& storage, const std::shared_ptr& http) + : PackageManagerInterface(pconfig, std::make_shared(bconfig, storage), storage, http) {} + + PackageManagerInterface(PackageConfig pconfig, std::shared_ptr bootloader, + std::shared_ptr storage, std::shared_ptr http) : config(std::move(pconfig)), storage_(std::move(storage)), http_(std::move(http)), - bootloader_{new Bootloader(std::move(bconfig), *storage_)} {} + bootloader_(std::move(bootloader)) {} virtual ~PackageManagerInterface() = default; virtual std::string name() const = 0; virtual Json::Value getInstalledPackages() const = 0; @@ -55,27 +59,10 @@ class PackageManagerInterface { FetcherProgressCb progress_cb, const api::FlowControlToken* token); virtual TargetStatus verifyTarget(const Uptane::Target& target) const; - // only returns the version - Json::Value getManifest(const Uptane::EcuSerial& ecu_serial) const { - Uptane::Target installed_target = getCurrent(); - Json::Value installed_image; - installed_image["filepath"] = installed_target.filename(); - installed_image["fileinfo"]["length"] = Json::UInt64(installed_target.length()); - installed_image["fileinfo"]["hashes"]["sha256"] = installed_target.sha256Hash(); - - Json::Value unsigned_ecu_version; - unsigned_ecu_version["attacks_detected"] = ""; - unsigned_ecu_version["installed_image"] = installed_image; - unsigned_ecu_version["ecu_serial"] = ecu_serial.ToString(); - unsigned_ecu_version["previous_timeserver_time"] = "1970-01-01T00:00:00Z"; - unsigned_ecu_version["timeserver_time"] = "1970-01-01T00:00:00Z"; - return unsigned_ecu_version; - } - protected: PackageConfig config; std::shared_ptr storage_; std::shared_ptr http_; - std::unique_ptr bootloader_; + std::shared_ptr bootloader_; }; #endif // PACKAGEMANAGERINTERFACE_H_ diff --git a/src/libaktualizr/primary/initializer.cc b/src/libaktualizr/primary/initializer.cc index eac47c1c21..d8449ed457 100644 --- a/src/libaktualizr/primary/initializer.cc +++ b/src/libaktualizr/primary/initializer.cc @@ -8,6 +8,7 @@ #include "bootstrap/bootstrap.h" #include "crypto/keymanager.h" #include "logging/logging.h" +#include "storage/invstorage.h" // Postcondition: device_id is in the storage bool Initializer::initDeviceId() { diff --git a/src/libaktualizr/primary/sotauptaneclient.cc b/src/libaktualizr/primary/sotauptaneclient.cc index c842b2d366..8c4f47d48d 100644 --- a/src/libaktualizr/primary/sotauptaneclient.cc +++ b/src/libaktualizr/primary/sotauptaneclient.cc @@ -49,7 +49,7 @@ void SotaUptaneClient::addSecondary(const std::shared_ptrgetCurrent()); } return false; @@ -86,7 +86,7 @@ void SotaUptaneClient::finalizeAfterReboot() { std::vector updates; unsigned int ecus_count = 0; if (uptaneOfflineIteration(&updates, &ecus_count)) { - const Uptane::EcuSerial &ecu_serial = uptane_manifest.getPrimaryEcuSerial(); + const Uptane::EcuSerial &ecu_serial = primaryEcuSerial(); std::vector installed_versions; boost::optional pending_target; @@ -124,7 +124,7 @@ void SotaUptaneClient::finalizeAfterReboot() { data::InstallationResult SotaUptaneClient::PackageInstallSetResult(const Uptane::Target &target) { data::InstallationResult result; - Uptane::EcuSerial ecu_serial = uptane_manifest.getPrimaryEcuSerial(); + Uptane::EcuSerial ecu_serial = primaryEcuSerial(); // This is to recover more gracefully if the install process was interrupted // but ends up booting the new version anyway (e.g: ostree finished @@ -195,36 +195,32 @@ void SotaUptaneClient::reportAktualizrConfiguration() { Json::Value SotaUptaneClient::AssembleManifest() { Json::Value manifest; // signed top-level - Uptane::EcuSerial primary_ecu_serial = uptane_manifest.getPrimaryEcuSerial(); + Uptane::EcuSerial primary_ecu_serial = primaryEcuSerial(); manifest["primary_ecu_serial"] = primary_ecu_serial.ToString(); // first part: report current version/state of all ecus Json::Value version_manifest; - Json::Value primary_ecu_version = package_manager_->getManifest(primary_ecu_serial); + Json::Value primary_manifest = uptane_manifest->assembleManifest(package_manager_->getCurrent()); std::vector> ecu_cnt; + std::string report_counter; if (!storage->loadEcuReportCounter(&ecu_cnt) || (ecu_cnt.size() == 0)) { LOG_ERROR << "No ECU version report counter, please check the database!"; + // TODO: consider not sending manifest at all in this case, or maybe retry } else { - primary_ecu_version["report_counter"] = std::to_string(ecu_cnt[0].second + 1); + report_counter = std::to_string(ecu_cnt[0].second + 1); storage->saveEcuReportCounter(ecu_cnt[0].first, ecu_cnt[0].second + 1); } - version_manifest[primary_ecu_serial.ToString()] = uptane_manifest.signManifest(primary_ecu_version); + version_manifest[primary_ecu_serial.ToString()] = uptane_manifest->sign(primary_manifest, report_counter); for (auto it = secondaries.begin(); it != secondaries.end(); it++) { - Json::Value secmanifest = it->second->getManifest(); - if (secmanifest.isMember("signatures") && secmanifest.isMember("signed")) { - const auto public_key = it->second->getPublicKey(); - const std::string canonical = Utils::jsonToCanonicalStr(secmanifest["signed"]); - const bool verified = public_key.VerifySignature(secmanifest["signatures"][0]["sig"].asString(), canonical); - - if (verified) { - version_manifest[it->first.ToString()] = secmanifest; - } else { - LOG_ERROR << "Secondary manifest verification failed, manifest: " << secmanifest; - } + Uptane::Manifest secmanifest = it->second->getManifest(); + + if (secmanifest.verifySignature(it->second->getPublicKey())) { + version_manifest[it->first.ToString()] = secmanifest; } else { - LOG_ERROR << "Secondary manifest is corrupted or not signed, manifest: " << secmanifest; + // TODO: send a corresponding event/report in this case, https://saeljira.it.here.com/browse/OTA-4305 + LOG_ERROR << "Secondary manifest is corrupted or not signed, or signature is invalid manifest: " << secmanifest; } } manifest["ecu_version_manifests"] = version_manifest; @@ -268,8 +264,8 @@ bool SotaUptaneClient::hasPendingUpdates() const { return storage->hasPendingIns void SotaUptaneClient::initialize() { LOG_DEBUG << "Checking if device is provisioned..."; - KeyManager keys(storage, config.keymanagerConfig()); - Initializer initializer(config.provision, storage, http, keys, secondaries); + auto keys = std::make_shared(storage, config.keymanagerConfig()); + Initializer initializer(config.provision, storage, http, *keys, secondaries); if (!initializer.isSuccessful()) { throw std::runtime_error("Fatal error during provisioning or ECU device registration."); @@ -280,7 +276,8 @@ void SotaUptaneClient::initialize() { throw std::runtime_error("Unable to load ECU serials after device registration."); } - uptane_manifest.setPrimaryEcuSerialHwId(serials[0]); + uptane_manifest = std::make_shared(keys, serials[0].first); + primary_ecu_serial_ = serials[0].first; hw_ids.insert(serials[0]); verifySecondaries(); @@ -395,7 +392,7 @@ void SotaUptaneClient::computeDeviceInstallationResult(data::InstallationResult bool SotaUptaneClient::getNewTargets(std::vector *new_targets, unsigned int *ecus_count) { std::vector targets = director_repo.getTargets().targets; - Uptane::EcuSerial primary_ecu_serial = uptane_manifest.getPrimaryEcuSerial(); + const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial(); if (ecus_count != nullptr) { *ecus_count = 0; } @@ -583,28 +580,36 @@ std::pair SotaUptaneClient::downloadImage(const Uptane::Ta report_queue->enqueue(std_::make_unique(ecu.first, correlation_id)); } - KeyManager keys(storage, config.keymanagerConfig()); - keys.loadKeys(); - auto prog_cb = [this](const Uptane::Target &t, const std::string description, unsigned int progress) { - report_progress_cb(events_channel.get(), t, description, progress); - }; + const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial(); bool success = false; - const int max_tries = 3; - int tries = 0; - std::chrono::milliseconds wait(500); - - for (; tries < max_tries; tries++) { - success = package_manager_->fetchTarget(target, *uptane_fetcher, keys, prog_cb, token); - if (success) { - break; - } else if (tries < max_tries - 1) { - std::this_thread::sleep_for(wait); - wait *= 2; + if (target.IsForEcu(primary_ecu_serial) || !target.IsOstree()) { + // TODO: download should be the logical ECU and packman specific + KeyManager keys(storage, config.keymanagerConfig()); + keys.loadKeys(); + auto prog_cb = [this](const Uptane::Target &t, const std::string description, unsigned int progress) { + report_progress_cb(events_channel.get(), t, description, progress); + }; + + const int max_tries = 3; + int tries = 0; + std::chrono::milliseconds wait(500); + + for (; tries < max_tries; tries++) { + success = package_manager_->fetchTarget(target, *uptane_fetcher, keys, prog_cb, token); + if (success) { + break; + } else if (tries < max_tries - 1) { + std::this_thread::sleep_for(wait); + wait *= 2; + } } - } - if (!success) { - LOG_ERROR << "Download unsuccessful after " << tries << " attempts."; + if (!success) { + LOG_ERROR << "Download unsuccessful after " << tries << " attempts."; + } + } else { + // we emulate successfull download in case of the secondary ostree update + success = true; } // send this asynchronously before `sendEvent`, so that the report timestamp @@ -702,6 +707,13 @@ result::UpdateCheck SotaUptaneClient::fetchMeta() { reportNetworkInfo(); if (hasPendingUpdates()) { + // if there are some pending updates check if the secondaries' pending updates have been applied + LOG_INFO << "The current update is pending. Check if pending ECUs has been already updated"; + checkAndUpdatePendingSecondaries(); + } + + if (hasPendingUpdates()) { + // if there are still some pending updates just return, don't check for new updates // no need in update checking if there are some pending updates LOG_INFO << "An update is pending. Skipping check for update until installation is complete."; return result::UpdateCheck({}, 0, result::UpdateStatus::kError, Json::nullValue, @@ -837,18 +849,27 @@ result::Install SotaUptaneClient::uptaneInstall(const std::vectorverifyTarget(update) != TargetStatus::kGood) { - result.dev_report = {false, data::ResultCode::Numeric::kInternalError, ""}; - storage->storeDeviceInstallationResult(result.dev_report, "Downloaded target is invalid", correlation_id); - sendEvent(result); - return result; + if (update.IsForEcu(primary_ecu_serial) || !update.IsOstree()) { + // download binary images for any target, for both Primary and Secondary + // download an ostree revision just for Primary, Secondary will do it by itself + // Primary cannot verify downloaded OSTree targets for Secondaries, + // Downloading of Secondary's ostree repo revision to the Primary's can fail + // if they differ signficantly as ostree has a certain cap/limit of the diff it pulls + if (package_manager_->verifyTarget(update) != TargetStatus::kGood) { + result.dev_report = {false, data::ResultCode::Numeric::kInternalError, ""}; + storage->storeDeviceInstallationResult(result.dev_report, "Downloaded target is invalid", correlation_id); + sendEvent(result); + return result; + } } } // Uptane step 5 (send time to all ECUs) is not implemented yet. - Uptane::EcuSerial primary_ecu_serial = uptane_manifest.getPrimaryEcuSerial(); + std::vector primary_updates = findForEcu(updates, primary_ecu_serial); // 6 - send metadata to all the ECUs sendMetadataToEcus(updates); @@ -889,12 +910,11 @@ result::Install SotaUptaneClient::uptaneInstall(const std::vectorsaveEcuInstallationResult(primary_ecu_serial, install_res); // TODO: distinguish this case from regular failure for local and remote // event reporting - report_queue->enqueue(std_::make_unique(uptane_manifest.getPrimaryEcuSerial(), - correlation_id, false)); - sendEvent(uptane_manifest.getPrimaryEcuSerial(), false); + report_queue->enqueue( + std_::make_unique(primaryEcuSerial(), correlation_id, false)); + sendEvent(primaryEcuSerial(), false); } - result.ecu_reports.emplace(result.ecu_reports.begin(), primary_update, uptane_manifest.getPrimaryEcuSerial(), - install_res); + result.ecu_reports.emplace(result.ecu_reports.begin(), primary_update, primaryEcuSerial(), install_res); // TODO: other updates for primary } else { LOG_INFO << "No update to install on primary"; @@ -913,7 +933,7 @@ result::Install SotaUptaneClient::uptaneInstall(const std::vectorsign(manifest); HttpResponse response = http->put(config.uptane.director_server + "/manifest", signed_manifest); if (response.isOk()) { if (!connected) { @@ -1000,13 +1020,12 @@ void SotaUptaneClient::verifySecondaries() { std::vector misconfigured_ecus; std::vector found(serials.size(), false); - SerialCompare primary_comp(uptane_manifest.getPrimaryEcuSerial()); + SerialCompare primary_comp(primaryEcuSerial()); EcuSerials::const_iterator store_it; store_it = std::find_if(serials.cbegin(), serials.cend(), primary_comp); if (store_it == serials.cend()) { - LOG_ERROR << "Primary ECU serial " << uptane_manifest.getPrimaryEcuSerial() << " not found in storage!"; - misconfigured_ecus.emplace_back(uptane_manifest.getPrimaryEcuSerial(), Uptane::HardwareIdentifier(""), - EcuState::kOld); + LOG_ERROR << "Primary ECU serial " << primaryEcuSerial() << " not found in storage!"; + misconfigured_ecus.emplace_back(primaryEcuSerial(), Uptane::HardwareIdentifier(""), EcuState::kOld); } else { found[static_cast(std::distance(serials.cbegin(), store_it))] = true; } @@ -1125,18 +1144,46 @@ void SotaUptaneClient::sendMetadataToEcus(const std::vector &tar } } -std::future SotaUptaneClient::sendFirmwareAsync(Uptane::SecondaryInterface &secondary, - const std::shared_ptr &data) { - auto f = [this, &secondary, data]() { +std::future SotaUptaneClient::sendFirmwareAsync(Uptane::SecondaryInterface &secondary, + const Uptane::Target &target) { + auto f = [this, &secondary, target]() { const std::string &correlation_id = director_repo.getCorrelationId(); + sendEvent(secondary.getSerial()); report_queue->enqueue(std_::make_unique(secondary.getSerial(), correlation_id)); - bool ret = secondary.sendFirmware(data); - report_queue->enqueue( - std_::make_unique(secondary.getSerial(), correlation_id, ret)); - sendEvent(secondary.getSerial(), ret); - return ret; + std::string data_to_send; + bool send_firmware_result = false; + + if (target.IsOstree()) { + // empty firmware means OSTree secondaries: pack credentials instead + data_to_send = secondaryTreehubCredentials(); + } else { + std::stringstream sstr; + sstr << *storage->openTargetFile(target); + data_to_send = sstr.str(); + } + + if (!data_to_send.empty()) { + send_firmware_result = secondary.sendFirmware(data_to_send); + } + + data::ResultCode::Numeric result = + send_firmware_result ? data::ResultCode::Numeric::kOk : data::ResultCode::Numeric::kInstallFailed; + + if (send_firmware_result) { + result = secondary.install(target.filename()); + } + + if (result == data::ResultCode::Numeric::kNeedCompletion) { + report_queue->enqueue(std_::make_unique(secondary.getSerial(), correlation_id)); + } else { + report_queue->enqueue(std_::make_unique( + secondary.getSerial(), correlation_id, result == data::ResultCode::Numeric::kOk)); + } + + sendEvent(secondary.getSerial(), result == data::ResultCode::Numeric::kOk); + return result; }; return std::async(std::launch::async, f); @@ -1144,9 +1191,9 @@ std::future SotaUptaneClient::sendFirmwareAsync(Uptane::SecondaryInterface std::vector SotaUptaneClient::sendImagesToEcus(const std::vector &targets) { std::vector reports; - std::vector>> firmwareFutures; + std::vector>> firmwareFutures; - Uptane::EcuSerial primary_ecu_serial = uptane_manifest.getPrimaryEcuSerial(); + const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial(); // target images should already have been downloaded to metadata_path/targets/ for (auto targets_it = targets.cbegin(); targets_it != targets.cend(); ++targets_it) { for (auto ecus_it = targets_it->ecus().cbegin(); ecus_it != targets_it->ecus().cend(); ++ecus_it) { @@ -1162,43 +1209,39 @@ std::vector SotaUptaneClient::sendImagesToEcus(const last_exception = Uptane::BadEcuId(targets_it->filename()); continue; } - Uptane::SecondaryInterface &sec = *f->second; - if (targets_it->IsOstree()) { - // empty firmware means OSTree secondaries: pack credentials instead - const std::string creds_archive = secondaryTreehubCredentials(); - if (creds_archive.empty()) { - continue; - } - firmwareFutures.emplace_back(result::Install::EcuReport(*targets_it, ecu_serial, data::InstallationResult()), - sendFirmwareAsync(sec, std::make_shared(creds_archive))); - } else { - std::stringstream sstr; - sstr << *storage->openTargetFile(*targets_it); - const std::string fw = sstr.str(); - firmwareFutures.emplace_back(result::Install::EcuReport(*targets_it, ecu_serial, data::InstallationResult()), - sendFirmwareAsync(sec, std::make_shared(fw))); - } + Uptane::SecondaryInterface &sec = *f->second; + firmwareFutures.emplace_back(result::Install::EcuReport(*targets_it, ecu_serial, data::InstallationResult()), + sendFirmwareAsync(sec, *targets_it)); } } for (auto &f : firmwareFutures) { - // failure + data::ResultCode::Numeric fut_result = data::ResultCode::Numeric::kUnknown; + + fut_result = f.second.get(); + f.first.install_res = data::InstallationResult(fut_result, ""); + if (fiu_fail((std::string("secondary_install_") + f.first.serial.ToString()).c_str()) != 0) { - f.first.install_res = data::InstallationResult( - data::ResultCode(data::ResultCode::Numeric::kInstallFailed, fault_injection_last_info()), ""); - storage->saveEcuInstallationResult(f.first.serial, f.first.install_res); - reports.push_back(f.first); - continue; + // consider changing this approach of the fault injection, since the current approach impacts the non-test code + // flow here as well as it doesn't test the installation failure on secondary from an end-to-end perspective as it + // injects an error on the middle of the control flow that would have happened if an installation error had + // happened in case of the virtual or the ip-secondary or any other secondary, e.g. add a mock secondary that + // returns an error to sendFirmware/install request we might consider passing the installation description message + // from Secondary, not just bool and/or data::ResultCode::Numeric + + // fault injection + fut_result = data::ResultCode::Numeric::kInstallFailed; + f.first.install_res = data::InstallationResult(data::ResultCode(fut_result, fault_injection_last_info()), ""); } - bool fut_result = f.second.get(); - if (fut_result) { - f.first.install_res = data::InstallationResult(data::ResultCode::Numeric::kOk, ""); + if (fut_result == data::ResultCode::Numeric::kOk) { storage->saveInstalledVersion(f.first.serial.ToString(), f.first.update, InstalledVersionUpdateMode::kCurrent); - } else { - f.first.install_res = data::InstallationResult(data::ResultCode::Numeric::kInstallFailed, ""); + } else if (fut_result == data::ResultCode::Numeric::kNeedCompletion) { + // ostree case: need reboot + storage->saveInstalledVersion(f.first.serial.ToString(), f.first.update, InstalledVersionUpdateMode::kPending); } + storage->saveEcuInstallationResult(f.first.serial, f.first.install_res); reports.push_back(f.first); } @@ -1235,3 +1278,39 @@ std::string SotaUptaneClient::secondaryTreehubCredentials() const { Uptane::LazyTargetsList SotaUptaneClient::allTargets() const { return Uptane::LazyTargetsList(images_repo, storage, uptane_fetcher); } + +void SotaUptaneClient::checkAndUpdatePendingSecondaries() { + // TODO: think of another alternatives to inform Primary about installation result on Secondary ECUs (reboot case) + std::vector> pending_ecus; + storage->getPendingEcus(&pending_ecus); + + for (const auto &pending_ecu : pending_ecus) { + if (primaryEcuSerial() == pending_ecu.first) { + continue; + } + auto &sec = secondaries[pending_ecu.first]; + const auto &manifest = sec->getManifest(); + if (!manifest.verifySignature(sec->getPublicKey())) { + LOG_ERROR << "Invalid signature of the manifest reported by secondary: " + << " serial: " << pending_ecu.first << " manifest: " << manifest; + // what should we do in this case ? + continue; + } + auto current_ecu_hash = manifest.installedImageHash(); + if (pending_ecu.second == current_ecu_hash) { + LOG_INFO << "The pending update " << current_ecu_hash << " has been installed on " << pending_ecu.first; + boost::optional pending_version; + if (storage->loadInstalledVersions(pending_ecu.first.ToString(), nullptr, &pending_version)) { + storage->saveEcuInstallationResult(pending_ecu.first, + data::InstallationResult(data::ResultCode::Numeric::kOk, "")); + + storage->saveInstalledVersion(pending_ecu.first.ToString(), *pending_version, + InstalledVersionUpdateMode::kCurrent); + + report_queue->enqueue(std_::make_unique( + pending_ecu.first, director_repo.getCorrelationId(), true)); + computeDeviceInstallationResult(nullptr, director_repo.getCorrelationId()); + } + } + } +} diff --git a/src/libaktualizr/primary/sotauptaneclient.h b/src/libaktualizr/primary/sotauptaneclient.h index e978a607f6..89a80564d9 100644 --- a/src/libaktualizr/primary/sotauptaneclient.h +++ b/src/libaktualizr/primary/sotauptaneclient.h @@ -26,26 +26,26 @@ #include "uptane/fetcher.h" #include "uptane/imagesrepository.h" #include "uptane/iterator.h" +#include "uptane/manifest.h" #include "uptane/secondaryinterface.h" class SotaUptaneClient { public: - SotaUptaneClient(Config &config_in, const std::shared_ptr &storage_in, - std::shared_ptr http_in, std::shared_ptr events_channel_in) + SotaUptaneClient(Config &config_in, std::shared_ptr &storage_in, std::shared_ptr http_in, + std::shared_ptr events_channel_in) : config(config_in), - uptane_manifest(config, storage_in), storage(storage_in), http(std::move(http_in)), package_manager_(PackageManagerFactory::makePackageManager(config.pacman, config.bootloader, storage, http)), uptane_fetcher(new Uptane::Fetcher(config, http)), report_queue(new ReportQueue(config, http)), - events_channel(std::move(events_channel_in)) {} + events_channel(std::move(events_channel_in)), + primary_ecu_serial_{Uptane::EcuSerial::Unknown()} {} - SotaUptaneClient(Config &config_in, const std::shared_ptr &storage_in, - std::shared_ptr http_in) + SotaUptaneClient(Config &config_in, std::shared_ptr &storage_in, std::shared_ptr http_in) : SotaUptaneClient(config_in, storage_in, std::move(http_in), nullptr) {} - SotaUptaneClient(Config &config_in, const std::shared_ptr &storage_in) + SotaUptaneClient(Config &config_in, std::shared_ptr storage_in) : SotaUptaneClient(config_in, storage_in, std::make_shared()) {} void initialize(); @@ -128,7 +128,8 @@ class SotaUptaneClient { void reportAktualizrConfiguration(); void verifySecondaries(); void sendMetadataToEcus(const std::vector &targets); - std::future sendFirmwareAsync(Uptane::SecondaryInterface &secondary, const std::shared_ptr &data); + std::future sendFirmwareAsync(Uptane::SecondaryInterface &secondary, + const Uptane::Target &target); std::vector sendImagesToEcus(const std::vector &targets); bool putManifestSimple(const Json::Value &custom = Json::nullValue); @@ -142,7 +143,8 @@ class SotaUptaneClient { std::unique_ptr findTargetHelper(const Uptane::Targets &cur_targets, const Uptane::Target &queried_target, int level, bool terminating, bool offline); - + void checkAndUpdatePendingSecondaries(); + const Uptane::EcuSerial &primaryEcuSerial() const { return primary_ecu_serial_; } template void sendEvent(Args &&... args) { std::shared_ptr event = std::make_shared(std::forward(args)...); @@ -156,7 +158,7 @@ class SotaUptaneClient { Config &config; Uptane::DirectorRepository director_repo; Uptane::ImagesRepository images_repo; - Uptane::Manifest uptane_manifest; + Uptane::ManifestIssuer::Ptr uptane_manifest; std::shared_ptr storage; std::shared_ptr http; std::shared_ptr package_manager_; @@ -171,6 +173,7 @@ class SotaUptaneClient { // ecu_serial => secondary* std::map> secondaries; std::mutex download_mutex; + mutable Uptane::EcuSerial primary_ecu_serial_; }; class TargetCompare { diff --git a/src/libaktualizr/storage/invstorage.h b/src/libaktualizr/storage/invstorage.h index 119d25094f..d8cf50b13e 100644 --- a/src/libaktualizr/storage/invstorage.h +++ b/src/libaktualizr/storage/invstorage.h @@ -169,6 +169,7 @@ class INvStorage { virtual bool loadInstallationLog(const std::string& ecu_serial, std::vector* log, bool only_installed) = 0; virtual bool hasPendingInstall() = 0; + virtual void getPendingEcus(std::vector>* pendingEcus) = 0; virtual void clearInstalledVersions() = 0; virtual void saveEcuInstallationResult(const Uptane::EcuSerial& ecu_serial, diff --git a/src/libaktualizr/storage/sqlstorage.cc b/src/libaktualizr/storage/sqlstorage.cc index c19f546a16..7e0916d1f3 100644 --- a/src/libaktualizr/storage/sqlstorage.cc +++ b/src/libaktualizr/storage/sqlstorage.cc @@ -1200,6 +1200,34 @@ bool SQLStorage::hasPendingInstall() { return statement.get_result_col_int(0) > 0; } +void SQLStorage::getPendingEcus(std::vector>* pendingEcus) { + SQLite3Guard db = dbConnection(); + + auto statement = db.prepareStatement("SELECT ecu_serial, sha256 FROM installed_versions where is_pending = 1"); + int statement_result = statement.step(); + if (statement_result != SQLITE_DONE && statement_result != SQLITE_ROW) { + throw std::runtime_error("Failed to get ECUs with a pending target installation: " + db.errmsg()); + } + + std::vector> ecu_res; + + if (statement_result == SQLITE_DONE) { + // if there are no any record in the DB + return; + } + + for (; statement_result != SQLITE_DONE; statement_result = statement.step()) { + std::string ecu_serial = statement.get_result_col_str(0).value(); + std::string hash = statement.get_result_col_str(1).value(); + ecu_res.emplace_back( + std::make_pair(Uptane::EcuSerial(ecu_serial), Uptane::Hash(Uptane::Hash::Type::kSha256, hash))); + } + + if (pendingEcus != nullptr) { + *pendingEcus = std::move(ecu_res); + } +} + void SQLStorage::clearInstalledVersions() { SQLite3Guard db = dbConnection(); diff --git a/src/libaktualizr/storage/sqlstorage.h b/src/libaktualizr/storage/sqlstorage.h index 27b2d8bc00..0269726d51 100644 --- a/src/libaktualizr/storage/sqlstorage.h +++ b/src/libaktualizr/storage/sqlstorage.h @@ -71,6 +71,7 @@ class SQLStorage : public SQLStorageBase, public INvStorage { bool loadInstallationLog(const std::string& ecu_serial, std::vector* log, bool only_installed) override; bool hasPendingInstall() override; + void getPendingEcus(std::vector>* pendingEcus) override; void clearInstalledVersions() override; void saveEcuInstallationResult(const Uptane::EcuSerial& ecu_serial, const data::InstallationResult& result) override; diff --git a/src/libaktualizr/uptane/CMakeLists.txt b/src/libaktualizr/uptane/CMakeLists.txt index 825d403b63..07d5d82894 100644 --- a/src/libaktualizr/uptane/CMakeLists.txt +++ b/src/libaktualizr/uptane/CMakeLists.txt @@ -7,7 +7,8 @@ set(SOURCES tuf.cc uptanerepository.cc directorrepository.cc - imagesrepository.cc) + imagesrepository.cc + manifest.cc) set(HEADERS exceptions.h @@ -17,7 +18,8 @@ set(HEADERS tuf.h uptanerepository.h directorrepository.h - imagesrepository.h) + imagesrepository.h + manifest.h) add_library(uptane OBJECT ${SOURCES}) diff --git a/src/libaktualizr/uptane/directorrepository.cc b/src/libaktualizr/uptane/directorrepository.cc index 9ad2e7276f..19e94eab08 100644 --- a/src/libaktualizr/uptane/directorrepository.cc +++ b/src/libaktualizr/uptane/directorrepository.cc @@ -139,4 +139,24 @@ void DirectorRepository::dropTargets(INvStorage& storage) { resetMeta(); } +bool DirectorRepository::matchTargetsWithImageTargets(const Uptane::Targets& image_targets) const { + // step 10 of https://uptane.github.io/papers/ieee-isto-6100.1.0.0.uptane-standard.html#rfc.section.5.4.4.2 + // TODO: no delegation support, consider reusing of findTargetInDelegationTree() + // TODO: reuse this verification at Primary, require some refactoring of Primary/sotauptaneclient + const auto& image_target_array = image_targets.targets; + const auto& director_target_array = targets.targets; + + for (const auto& director_target : director_target_array) { + auto found_it = std::find_if( + image_target_array.begin(), image_target_array.end(), + [&director_target](const Target& image_target) { return director_target.MatchTarget(image_target); }); + + if (found_it == image_target_array.end()) { + return false; + } + } + + return true; +} + } // namespace Uptane diff --git a/src/libaktualizr/uptane/directorrepository.h b/src/libaktualizr/uptane/directorrepository.h index 8b51c4e5dd..4e75a35d14 100644 --- a/src/libaktualizr/uptane/directorrepository.h +++ b/src/libaktualizr/uptane/directorrepository.h @@ -21,16 +21,18 @@ class DirectorRepository : public RepositoryCommon { return targets.getTargets(ecu_id, hw_id); } const std::string& getCorrelationId() const { return targets.correlation_id(); } - bool targetsExpired() const; + bool checkMetaOffline(INvStorage& storage); void dropTargets(INvStorage& storage); Exception getLastException() const { return last_exception; } bool updateMeta(INvStorage& storage, const IMetadataFetcher& fetcher) override; + bool matchTargetsWithImageTargets(const Uptane::Targets& image_targets) const; private: void resetMeta(); bool usePreviousTargets() const; + bool targetsExpired() const; private: FRIEND_TEST(Director, EmptyTargets); diff --git a/src/libaktualizr/uptane/imagesrepository.h b/src/libaktualizr/uptane/imagesrepository.h index 2242025b43..1af0deca6e 100644 --- a/src/libaktualizr/uptane/imagesrepository.h +++ b/src/libaktualizr/uptane/imagesrepository.h @@ -1,9 +1,6 @@ #ifndef IMAGES_REPOSITORY_H_ #define IMAGES_REPOSITORY_H_ -#include -#include - #include "uptanerepository.h" namespace Uptane { @@ -14,34 +11,29 @@ class ImagesRepository : public RepositoryCommon { public: ImagesRepository() : RepositoryCommon(RepositoryType::Image()) {} - void resetMeta(); - - bool verifyTargets(const std::string& targets_raw); - bool targetsExpired() { return targets->isExpired(TimeStamp::Now()); } - - bool verifyTimestamp(const std::string& timestamp_raw); - bool timestampExpired() { return timestamp.isExpired(TimeStamp::Now()); } - - bool verifySnapshot(const std::string& snapshot_raw); - bool snapshotExpired() { return snapshot.isExpired(TimeStamp::Now()); } - int64_t snapshotSize() { return timestamp.snapshot_size(); } - Exception getLastException() const { return last_exception; } - - static std::shared_ptr verifyDelegation(const std::string& delegation_raw, const Uptane::Role& role, - const Targets& parent_target); std::shared_ptr getTargets() const { return targets; } bool verifyRoleHashes(const std::string& role_data, const Uptane::Role& role) const; int getRoleVersion(const Uptane::Role& role) const; int64_t getRoleSize(const Uptane::Role& role) const; - - bool checkMetaOffline(INvStorage& storage); + static std::shared_ptr verifyDelegation(const std::string& delegation_raw, const Uptane::Role& role, + const Targets& parent_target); bool updateMeta(INvStorage& storage, const IMetadataFetcher& fetcher) override; + bool checkMetaOffline(INvStorage& storage); private: + bool targetsExpired() { return targets->isExpired(TimeStamp::Now()); } + bool timestampExpired() { return timestamp.isExpired(TimeStamp::Now()); } + bool snapshotExpired() { return snapshot.isExpired(TimeStamp::Now()); } + int64_t snapshotSize() { return timestamp.snapshot_size(); } + + void resetMeta(); + bool verifyTimestamp(const std::string& timestamp_raw); bool fetchSnapshot(INvStorage& storage, const IMetadataFetcher& fetcher, int local_version); + bool verifySnapshot(const std::string& snapshot_raw); bool fetchTargets(INvStorage& storage, const IMetadataFetcher& fetcher, int local_version); + bool verifyTargets(const std::string& targets_raw); std::shared_ptr targets; Uptane::TimestampMeta timestamp; diff --git a/src/libaktualizr/uptane/isotpsecondary.cc b/src/libaktualizr/uptane/isotpsecondary.cc index 27c8db483d..4a28675e60 100644 --- a/src/libaktualizr/uptane/isotpsecondary.cc +++ b/src/libaktualizr/uptane/isotpsecondary.cc @@ -35,7 +35,7 @@ namespace Uptane { IsoTpSecondary::IsoTpSecondary(const std::string& can_iface, uint16_t can_id) : conn(can_iface, LIBUPTINY_ISOTP_PRIMARY_CANID, can_id) {} -EcuSerial IsoTpSecondary::getSerial() { +EcuSerial IsoTpSecondary::getSerial() const { std::string out; std::string in; @@ -50,7 +50,7 @@ EcuSerial IsoTpSecondary::getSerial() { return EcuSerial(in.substr(1)); } -HardwareIdentifier IsoTpSecondary::getHwId() { +HardwareIdentifier IsoTpSecondary::getHwId() const { std::string out; std::string in; @@ -65,7 +65,7 @@ HardwareIdentifier IsoTpSecondary::getHwId() { return HardwareIdentifier(in.substr(1)); } -PublicKey IsoTpSecondary::getPublicKey() { +PublicKey IsoTpSecondary::getPublicKey() const { std::string out; std::string in; @@ -80,22 +80,22 @@ PublicKey IsoTpSecondary::getPublicKey() { return PublicKey(boost::algorithm::hex(in.substr(1)), KeyType::kED25519); } -Json::Value IsoTpSecondary::getManifest() { +Uptane::Manifest IsoTpSecondary::getManifest() const { std::string out; std::string in; out += static_cast(IsoTpUptaneMesType::kGetManifest); if (!conn.SendRecv(out, &in)) { - return Json::nullValue; + return Json::Value(Json::nullValue); } if (in[0] != static_cast(IsoTpUptaneMesType::kGetManifestResp)) { - return Json::nullValue; + return Json::Value(Json::nullValue); } return Utils::parseJSON(in.substr(1)); } -int IsoTpSecondary::getRootVersion(bool director) { +int IsoTpSecondary::getRootVersion(bool director) const { if (!director) { return 0; } @@ -137,8 +137,8 @@ bool IsoTpSecondary::putMetadata(const RawMetaPack& meta_pack) { return conn.Send(out); } -bool IsoTpSecondary::sendFirmware(const std::shared_ptr& data) { - size_t num_chunks = 1 + (data->length() - 1) / kChunkSize; +bool IsoTpSecondary::sendFirmware(const std::string& data) { + size_t num_chunks = 1 + (data.length() - 1) / kChunkSize; if (num_chunks > 127) { return false; @@ -151,9 +151,9 @@ bool IsoTpSecondary::sendFirmware(const std::shared_ptr& data) { out += static_cast(num_chunks); out += static_cast(i + 1); if (i == num_chunks - 1) { - out += data->substr(static_cast(i * kChunkSize)); + out += data.substr(static_cast(i * kChunkSize)); } else { - out += data->substr(static_cast(i * kChunkSize), static_cast(kChunkSize)); + out += data.substr(static_cast(i * kChunkSize), static_cast(kChunkSize)); } if (!conn.SendRecv(out, &in)) { return false; diff --git a/src/libaktualizr/uptane/isotpsecondary.h b/src/libaktualizr/uptane/isotpsecondary.h index 0bb59121c3..8a47d1413c 100644 --- a/src/libaktualizr/uptane/isotpsecondary.h +++ b/src/libaktualizr/uptane/isotpsecondary.h @@ -10,17 +10,17 @@ class IsoTpSecondary : public SecondaryInterface { public: explicit IsoTpSecondary(const std::string& can_iface, uint16_t can_id); - EcuSerial getSerial() override; - HardwareIdentifier getHwId() override; - PublicKey getPublicKey() override; + EcuSerial getSerial() const override; + HardwareIdentifier getHwId() const override; + PublicKey getPublicKey() const override; bool putMetadata(const RawMetaPack& meta_pack) override; - int getRootVersion(bool director) override; + int getRootVersion(bool director) const override; bool putRoot(const std::string& root, bool director) override; - bool sendFirmware(const std::shared_ptr& data) override; - Json::Value getManifest() override; + bool sendFirmware(const std::string& data) override; + Uptane::Manifest getManifest() const override; private: - IsoTpSendRecv conn; + mutable IsoTpSendRecv conn; }; } // namespace Uptane #endif // UPTANE_ISOTPSECONDARY_H_ diff --git a/src/libaktualizr/uptane/manifest.cc b/src/libaktualizr/uptane/manifest.cc new file mode 100644 index 0000000000..001eccc03d --- /dev/null +++ b/src/libaktualizr/uptane/manifest.cc @@ -0,0 +1,75 @@ +#include "manifest.h" + +#include "crypto/keymanager.h" + +namespace Uptane { + +Hash Manifest::installedImageHash() const { + // TODO: proper verification of the required fields + return Uptane::Hash(Uptane::Hash::Type::kSha256, + (*this)["signed"]["installed_image"]["fileinfo"]["hashes"]["sha256"].asString()); +} + +std::string Manifest::signature() const { + // TODO: proper verification of the required fields + return (*this)["signatures"][0]["sig"].asString(); +} + +std::string Manifest::signedBody() const { + // TODO: proper verification of the required fields + return Utils::jsonToCanonicalStr((*this)["signed"]); +} + +bool Manifest::verifySignature(PublicKey &&pub_key) const { + if (!(isMember("signatures") && isMember("signed"))) { + LOG_ERROR << "Missing either signature or the signing body/subject: " << *this; + return false; + } + + return pub_key.VerifySignature(signature(), signedBody()); +} + +Manifest ManifestIssuer::sign(const Manifest &manifest, const std::string &report_counter) const { + Manifest manifest_to_sign = manifest; + if (!report_counter.empty()) { + manifest_to_sign["report_counter"] = report_counter; + } + return key_mngr_->signTuf(manifest_to_sign); +} + +Manifest ManifestIssuer::assembleManifest(const InstalledImageInfo &installed_image_info, + const Uptane::EcuSerial &ecu_serial) { + Json::Value installed_image; + installed_image["filepath"] = installed_image_info.name; + installed_image["fileinfo"]["length"] = Json::UInt64(installed_image_info.len); + installed_image["fileinfo"]["hashes"]["sha256"] = installed_image_info.hash; + + Json::Value unsigned_ecu_version; + unsigned_ecu_version["attacks_detected"] = ""; + unsigned_ecu_version["installed_image"] = installed_image; + unsigned_ecu_version["ecu_serial"] = ecu_serial.ToString(); + unsigned_ecu_version["previous_timeserver_time"] = "1970-01-01T00:00:00Z"; + unsigned_ecu_version["timeserver_time"] = "1970-01-01T00:00:00Z"; + return unsigned_ecu_version; +} + +Hash ManifestIssuer::generateVersionHash(const std::string &data) { return Hash::generate(Hash::Type::kSha256, data); } + +std::string ManifestIssuer::generateVersionHashStr(const std::string &data) { + // think of unifying a hash case,we use both lower and upper cases + return boost::algorithm::to_lower_copy(generateVersionHash(data).HashString()); +} + +Manifest ManifestIssuer::assembleManifest(const InstalledImageInfo &installed_image_info) const { + return assembleManifest(installed_image_info, ecu_serial_); +} + +Manifest ManifestIssuer::assembleManifest(const Uptane::Target &target) const { + return assembleManifest(target.getTargetImageInfo()); +} + +Manifest ManifestIssuer::assembleAndSignManifest(const InstalledImageInfo &installed_image_info) const { + return key_mngr_->signTuf(assembleManifest(installed_image_info)); +} + +} // namespace Uptane diff --git a/src/libaktualizr/uptane/manifest.h b/src/libaktualizr/uptane/manifest.h new file mode 100644 index 0000000000..0f5a20b79e --- /dev/null +++ b/src/libaktualizr/uptane/manifest.h @@ -0,0 +1,50 @@ +#ifndef AKTUALIZR_UPTANE_MANIFEST_H +#define AKTUALIZR_UPTANE_MANIFEST_H + +#include "json/json.h" +#include "tuf.h" + +#include + +class KeyManager; + +namespace Uptane { + +class Manifest : public Json::Value { + public: + Manifest(const Json::Value &value = Json::Value()) : Json::Value(value) {} + + public: + Hash installedImageHash() const; + std::string signature() const; + std::string signedBody() const; + bool verifySignature(PublicKey &&pub_key) const; +}; + +class ManifestIssuer { + public: + using Ptr = std::shared_ptr; + + public: + ManifestIssuer(std::shared_ptr &key_mngr, Uptane::EcuSerial ecu_serial) + : ecu_serial_(std::move(ecu_serial)), key_mngr_(key_mngr) {} + + static Manifest assembleManifest(const InstalledImageInfo &installed_image_info, const Uptane::EcuSerial &ecu_serial); + static Hash generateVersionHash(const std::string &data); + static std::string generateVersionHashStr(const std::string &data); + + Manifest sign(const Manifest &manifest, const std::string &report_counter = "") const; + + Manifest assembleManifest(const InstalledImageInfo &installed_image_info) const; + Manifest assembleManifest(const Uptane::Target &target) const; + + Manifest assembleAndSignManifest(const InstalledImageInfo &installed_image_info) const; + + private: + const Uptane::EcuSerial ecu_serial_; + std::shared_ptr key_mngr_; +}; + +} // namespace Uptane + +#endif // AKTUALIZR_UPTANE_MANIFEST_H diff --git a/src/libaktualizr/uptane/secondaryinterface.h b/src/libaktualizr/uptane/secondaryinterface.h index 296eabf1f8..abf4d6a744 100644 --- a/src/libaktualizr/uptane/secondaryinterface.h +++ b/src/libaktualizr/uptane/secondaryinterface.h @@ -1,52 +1,41 @@ #ifndef UPTANE_SECONDARYINTERFACE_H #define UPTANE_SECONDARYINTERFACE_H -#include #include #include "json/json.h" - +#include "uptane/manifest.h" #include "uptane/tuf.h" -/* Json snippet returned by sendMetaXXX(): - * { - * valid = true/false, - * wait_for_target = true/false - * } - */ - namespace Uptane { class SecondaryInterface { public: - // This ctor should be removed as the secondary configuration SecondaryConfig - // is the secondaries's specific, see SecondaryConfig declaration - // explicit SecondaryInterface(SecondaryConfig sconfig_in) : sconfig(std::move(sconfig_in)) {} - virtual ~SecondaryInterface() = default; - // not clear what this method for, can be removed - // virtual void Initialize(){}; // optional step, called after device registration - // should be pure virtual, since the current implementation reads from the secondaries specific config - // virtual EcuSerial getSerial() { return Uptane::EcuSerial(sconfig.ecu_serial); } - virtual EcuSerial getSerial() = 0; - // should be pure virtual, since the current implementation reads from the secondaries specific config - // virtual Uptane::HardwareIdentifier getHwId() { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } - virtual Uptane::HardwareIdentifier getHwId() = 0; - virtual PublicKey getPublicKey() = 0; - - // getSerial(), getHwId() and getPublicKey() can be moved to seperate interface - // since their usage pattern differ from the following methods' one - virtual Json::Value getManifest() = 0; + using Ptr = std::shared_ptr; + + public: + virtual EcuSerial getSerial() const = 0; + virtual Uptane::HardwareIdentifier getHwId() const = 0; + virtual PublicKey getPublicKey() const = 0; + + virtual Uptane::Manifest getManifest() const = 0; virtual bool putMetadata(const RawMetaPack& meta_pack) = 0; - virtual int32_t getRootVersion(bool director) = 0; + + virtual int32_t getRootVersion(bool director) const = 0; virtual bool putRoot(const std::string& root, bool director) = 0; - // FIXME: Instead of std::string we should use StorageTargetRHandle - virtual bool sendFirmware(const std::shared_ptr& data) = 0; - // Should be removes as it's secondary specific - // const SecondaryConfig sconfig; + virtual bool sendFirmware(const std::string& data) = 0; + virtual data::ResultCode::Numeric install(const std::string& target_name) = 0; + + virtual ~SecondaryInterface() = default; + + public: + // make children non-copyable + SecondaryInterface(const SecondaryInterface&) = delete; + SecondaryInterface& operator=(const SecondaryInterface&) = delete; - // protected: - // SecondaryInterface() : sconfig{} {}; + protected: + SecondaryInterface() = default; }; } // namespace Uptane diff --git a/src/libaktualizr/uptane/tuf.h b/src/libaktualizr/uptane/tuf.h index af90567768..4a3ae1e27f 100644 --- a/src/libaktualizr/uptane/tuf.h +++ b/src/libaktualizr/uptane/tuf.h @@ -128,6 +128,15 @@ class Version { friend std::ostream &operator<<(std::ostream &os, const Version &v); }; +struct InstalledImageInfo { + InstalledImageInfo() : name{""}, len{0} {} + InstalledImageInfo(std::string name_in, uint64_t len_in, std::string hash_in) + : name(std::move(name_in)), len(len_in), hash(std::move(hash_in)) {} + std::string name; + uint64_t len; + std::string hash; +}; + std::ostream &operator<<(std::ostream &os, const Version &v); class HardwareIdentifier { @@ -265,12 +274,14 @@ class Target { * root commit object. */ bool IsOstree() const; + std::string type() const { return type_; } // Comparison is usually not meaningful. Use MatchTarget instead. bool operator==(const Target &t2) = delete; bool MatchTarget(const Target &t2) const; Json::Value toDebugJson() const; friend std::ostream &operator<<(std::ostream &os, const Target &t); + InstalledImageInfo getTargetImageInfo() const { return {filename(), length(), sha256Hash()}; } private: bool valid{true}; diff --git a/src/libaktualizr/uptane/uptane_ci_test.cc b/src/libaktualizr/uptane/uptane_ci_test.cc index bac3300052..c604c0da91 100644 --- a/src/libaktualizr/uptane/uptane_ci_test.cc +++ b/src/libaktualizr/uptane/uptane_ci_test.cc @@ -34,8 +34,6 @@ TEST(UptaneCI, ProvisionAndPutManifest) { config.postUpdateValues(); // re-run copy of urls auto storage = INvStorage::newStorage(config.storage); - Uptane::Manifest uptane_manifest{config, storage}; - auto sota_client = std_::make_unique(config, storage); EXPECT_NO_THROW(sota_client->initialize()); EXPECT_TRUE(sota_client->putManifestSimple()); diff --git a/src/libaktualizr/uptane/uptane_test.cc b/src/libaktualizr/uptane/uptane_test.cc index 1461882ede..bd7a4655f5 100644 --- a/src/libaktualizr/uptane/uptane_test.cc +++ b/src/libaktualizr/uptane/uptane_test.cc @@ -526,24 +526,25 @@ class SecondaryInterfaceMock : public Uptane::SecondaryInterface { manifest_["signed"] = manifest_unsigned; manifest_["signatures"].append(signature); } - PublicKey getPublicKey() override { return public_key_; } + PublicKey getPublicKey() const override { return public_key_; } - Uptane::HardwareIdentifier getHwId() override { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } - Uptane::EcuSerial getSerial() override { + Uptane::HardwareIdentifier getHwId() const override { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } + Uptane::EcuSerial getSerial() const override { if (!sconfig.ecu_serial.empty()) { return Uptane::EcuSerial(sconfig.ecu_serial); } return Uptane::EcuSerial(public_key_.KeyId()); } - Json::Value getManifest() override { return manifest_; } + Uptane::Manifest getManifest() const override { return manifest_; } MOCK_METHOD1(putMetadataMock, bool(const Uptane::RawMetaPack &)); - MOCK_METHOD1(getRootVersionMock, int32_t(bool)); + MOCK_CONST_METHOD1(getRootVersionMock, int32_t(bool)); bool putMetadata(const Uptane::RawMetaPack &meta_pack) override { return putMetadataMock(meta_pack); } - int32_t getRootVersion(bool director) override { return getRootVersionMock(director); } + int32_t getRootVersion(bool director) const override { return getRootVersionMock(director); } bool putRoot(const std::string &, bool) override { return true; } - bool sendFirmware(const std::shared_ptr &) override { return true; } + bool sendFirmware(const std::string &) override { return true; } + virtual data::ResultCode::Numeric install(const std::string &) override { return data::ResultCode::Numeric::kOk; } PublicKey public_key_; Json::Value manifest_; diff --git a/src/libaktualizr/uptane/uptanerepository.cc b/src/libaktualizr/uptane/uptanerepository.cc index b9727861a3..25a9fd73a1 100644 --- a/src/libaktualizr/uptane/uptanerepository.cc +++ b/src/libaktualizr/uptane/uptanerepository.cc @@ -19,18 +19,18 @@ namespace Uptane { -bool RepositoryCommon::initRoot(const std::string& root_raw) { +bool RepositoryCommon::initRoot(const std::string &root_raw) { try { root = Root(type, Utils::parseJSON(root_raw)); // initialization and format check root = Root(type, Utils::parseJSON(root_raw), root); // signature verification against itself - } catch (const std::exception& e) { + } catch (const std::exception &e) { LOG_ERROR << "Loading initial root failed: " << e.what(); return false; } return true; } -bool RepositoryCommon::verifyRoot(const std::string& root_raw) { +bool RepositoryCommon::verifyRoot(const std::string &root_raw) { try { int prev_version = rootVersion(); // 5.4.4.3.2.3. Version N+1 of the Root metadata file MUST have been signed @@ -46,7 +46,7 @@ bool RepositoryCommon::verifyRoot(const std::string& root_raw) { LOG_ERROR << "Version in root metadata doesn't match the expected value"; return false; } - } catch (const std::exception& e) { + } catch (const std::exception &e) { LOG_ERROR << "Signature verification for root metadata failed: " << e.what(); return false; } @@ -55,7 +55,7 @@ bool RepositoryCommon::verifyRoot(const std::string& root_raw) { void RepositoryCommon::resetRoot() { root = Root(Root::Policy::kAcceptAll); } -bool RepositoryCommon::updateRoot(INvStorage& storage, const IMetadataFetcher& fetcher, +bool RepositoryCommon::updateRoot(INvStorage &storage, const IMetadataFetcher &fetcher, const RepositoryType repo_type) { // 5.4.4.3.1. Load the previous Root metadata file. { @@ -107,9 +107,4 @@ bool RepositoryCommon::updateRoot(INvStorage& storage, const IMetadataFetcher& f return !rootExpired(); } -Json::Value Manifest::signManifest(const Json::Value& manifest_unsigned) const { - Json::Value manifest = keys_.signTuf(manifest_unsigned); - return manifest; -} - } // namespace Uptane diff --git a/src/libaktualizr/uptane/uptanerepository.h b/src/libaktualizr/uptane/uptanerepository.h index 12395db2f9..c0f69176f4 100644 --- a/src/libaktualizr/uptane/uptanerepository.h +++ b/src/libaktualizr/uptane/uptanerepository.h @@ -1,40 +1,13 @@ #ifndef UPTANE_REPOSITORY_H_ #define UPTANE_REPOSITORY_H_ -#include - #include "json/json.h" +#include "uptane/fetcher.h" -#include "config/config.h" -#include "crypto/crypto.h" -#include "crypto/keymanager.h" -#include "fetcher.h" -#include "logging/logging.h" -#include "storage/invstorage.h" +class INvStorage; namespace Uptane { -class Manifest { - public: - Manifest(const Config &config_in, std::shared_ptr storage_in) - : storage_{std::move(storage_in)}, keys_(storage_, config_in.keymanagerConfig()) {} - - Json::Value signManifest(const Json::Value &manifest_unsigned) const; - - void setPrimaryEcuSerialHwId(const std::pair &serials) { - primary_ecu_serial = serials.first; - primary_hardware_id = serials.second; - } - - EcuSerial getPrimaryEcuSerial() const { return primary_ecu_serial; } - - private: - Uptane::EcuSerial primary_ecu_serial{Uptane::EcuSerial::Unknown()}; - Uptane::HardwareIdentifier primary_hardware_id{Uptane::HardwareIdentifier::Unknown()}; - std::shared_ptr storage_; - KeyManager keys_; -}; - class RepositoryCommon { public: RepositoryCommon(RepositoryType type_in) : type{type_in} {} @@ -54,6 +27,7 @@ class RepositoryCommon { Root root; RepositoryType type; }; + } // namespace Uptane #endif diff --git a/src/libaktualizr/utilities/CMakeLists.txt b/src/libaktualizr/utilities/CMakeLists.txt index b03c99b1d7..fda70c09e8 100644 --- a/src/libaktualizr/utilities/CMakeLists.txt +++ b/src/libaktualizr/utilities/CMakeLists.txt @@ -2,7 +2,6 @@ set(SOURCES aktualizr_version.cc apiqueue.cc dequeue_buffer.cc sig_handler.cc - sockaddr_io.cc timer.cc types.cc utils.cc) @@ -14,7 +13,6 @@ set(HEADERS apiqueue.h exceptions.h fault_injection.h sig_handler.h - sockaddr_io.h timer.h types.h utils.h diff --git a/src/libaktualizr/utilities/sockaddr_io.cc b/src/libaktualizr/utilities/sockaddr_io.cc deleted file mode 100644 index 7e7e582a07..0000000000 --- a/src/libaktualizr/utilities/sockaddr_io.cc +++ /dev/null @@ -1,7 +0,0 @@ -#include "utilities/sockaddr_io.h" -#include "utilities/utils.h" - -std::ostream &operator<<(std::ostream &os, const sockaddr_storage &saddr) { - os << Utils::ipDisplayName(saddr) << ":" << Utils::ipPort(saddr); - return os; -} \ No newline at end of file diff --git a/src/libaktualizr/utilities/sockaddr_io.h b/src/libaktualizr/utilities/sockaddr_io.h deleted file mode 100644 index b1635ad9ec..0000000000 --- a/src/libaktualizr/utilities/sockaddr_io.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef UTILITIES_SOCKADDR_IO_H_ -#define UTILITIES_SOCKADDR_IO_H_ - -#include -#include - -std::ostream &operator<<(std::ostream &os, const sockaddr_storage &saddr); - -#endif // UTILITIES_SOCKADDR_IO_H_ \ No newline at end of file diff --git a/src/libaktualizr/utilities/utils.cc b/src/libaktualizr/utilities/utils.cc index 65252577c4..b7d6734e67 100644 --- a/src/libaktualizr/utilities/utils.cc +++ b/src/libaktualizr/utilities/utils.cc @@ -275,7 +275,10 @@ std::string Utils::genPrettyName() { std::string Utils::readFile(const boost::filesystem::path &filename, const bool trim) { boost::filesystem::path tmpFilename = filename; tmpFilename += ".new"; - + // that's kind of dangerous to include a specific use-case business logic + // into a generic function used by many use cases + // TODO: consider refactoring it, e.g. a generic readFile + a specific one with that includes + // the ".new" file handling if (boost::filesystem::exists(tmpFilename)) { LOG_WARNING << tmpFilename << " was found on FS, removing"; boost::filesystem::remove(tmpFilename); @@ -847,24 +850,21 @@ bool operator<(const sockaddr_storage &left, const sockaddr_storage &right) { return (res < 0); } -Socket::Socket(const std::string &ip, uint16_t port) : sock_address_{} { - memset(&sock_address_, 0, sizeof(sock_address_)); - sock_address_.sin_family = AF_INET; - inet_pton(AF_INET, ip.c_str(), &(sock_address_.sin_addr)); - sock_address_.sin_port = htons(port); - +Socket::Socket() { socket_fd_ = socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd_ < 0) { + if (-1 == socket_fd_) { throw std::system_error(errno, std::system_category(), "socket"); } } -Socket::~Socket() { - shutdown(socket_fd_, SHUT_RDWR); - ::close(socket_fd_); +Socket::~Socket() { ::close(socket_fd_); } + +std::string Socket::toString() { + auto saddr = Utils::ipGetSockaddr(socket_fd_); + return Utils::ipDisplayName(saddr) + ":" + std::to_string(Utils::ipPort(saddr)); } -int Socket::bind(in_port_t port, bool reuse) { +void Socket::bind(in_port_t port, bool reuse) { sockaddr_in sa{}; memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; @@ -872,15 +872,45 @@ int Socket::bind(in_port_t port, bool reuse) { sa.sin_addr.s_addr = htonl(INADDR_ANY); int reuseaddr = reuse ? 1 : 0; - if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) < 0) { - return errno; + if (-1 == setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr))) { + throw std::system_error(errno, std::system_category(), "socket"); } - return ::bind(socket_fd_, reinterpret_cast(&sa), sizeof(sa)); + if (-1 == ::bind(socket_fd_, reinterpret_cast(&sa), sizeof(sa))) { + throw std::system_error(errno, std::system_category(), "socket"); + } } -int Socket::connect() { - return ::connect(socket_fd_, reinterpret_cast(&sock_address_), sizeof(sock_address_)); +ListenSocket::ListenSocket(in_port_t port) : _port(port) { + bind(port); + if (_port == 0) { + // ephemeral port was bound, find out its real port number + auto ephemeral_port = Utils::ipPort(Utils::ipGetSockaddr(socket_fd_)); + if (-1 != ephemeral_port) { + _port = static_cast(ephemeral_port); + } + } +} + +ConnectionSocket::ConnectionSocket(const std::string &ip, in_port_t port, in_port_t bind_port) + : remote_sock_address_{} { + memset(&remote_sock_address_, 0, sizeof(remote_sock_address_)); + remote_sock_address_.sin_family = AF_INET; + if (-1 == inet_pton(AF_INET, ip.c_str(), &(remote_sock_address_.sin_addr))) { + throw std::system_error(errno, std::system_category(), "socket"); + } + remote_sock_address_.sin_port = htons(port); + + if (bind_port > 0) { + bind(bind_port); + } +} + +ConnectionSocket::~ConnectionSocket() { ::shutdown(socket_fd_, SHUT_RDWR); } + +int ConnectionSocket::connect() { + return ::connect(socket_fd_, reinterpret_cast(&remote_sock_address_), + sizeof(remote_sock_address_)); } CurlEasyWrapper::CurlEasyWrapper() { diff --git a/src/libaktualizr/utilities/utils.h b/src/libaktualizr/utilities/utils.h index c6bf73f930..9abf5c0074 100644 --- a/src/libaktualizr/utilities/utils.h +++ b/src/libaktualizr/utilities/utils.h @@ -122,29 +122,45 @@ class BasedPath { template using StructGuard = std::unique_ptr; -// helper object for RAII socket management -struct SocketCloser { - void operator()(const int *ptr) const { - close(*ptr); - delete ptr; - } -}; - -using SocketHandle = std::unique_ptr; bool operator<(const sockaddr_storage &left, const sockaddr_storage &right); // required by std::map class Socket { public: - Socket(const std::string &ip, uint16_t port); - ~Socket(); + Socket(); + virtual ~Socket() = 0; + + Socket(const Socket &) = delete; + Socket &operator=(const Socket &) = delete; + + int &operator*() { return socket_fd_; } + std::string toString(); + + protected: + void bind(in_port_t port, bool reuse = true); + + protected: + int socket_fd_; +}; + +class ConnectionSocket : public Socket { + public: + ConnectionSocket(const std::string &ip, in_port_t port, in_port_t bind_port = 0); + ~ConnectionSocket() override; - int bind(in_port_t port, bool reuse = true); + public: int connect(); - int getFD() { return socket_fd_; } private: - struct sockaddr_in sock_address_; - int socket_fd_; + struct sockaddr_in remote_sock_address_; +}; + +class ListenSocket : public Socket { + public: + ListenSocket(in_port_t port); + in_port_t port() const { return _port; } + + private: + in_port_t _port; }; // wrapper for curl handles diff --git a/src/libaktualizr/utilities/utils_test.cc b/src/libaktualizr/utilities/utils_test.cc index 6368a6465c..8fc9015bf5 100644 --- a/src/libaktualizr/utilities/utils_test.cc +++ b/src/libaktualizr/utilities/utils_test.cc @@ -309,33 +309,6 @@ TEST(Utils, writeFileJson) { EXPECT_EQ(result_json["key"].asString(), val["key"].asString()); } -TEST(Utils, ipUtils) { - int fd = socket(AF_INET6, SOCK_STREAM, 0); - - EXPECT_NE(fd, -1); - SocketHandle hdl(new int(fd)); - - sockaddr_in6 sa{}; - - memset(&sa, 0, sizeof(sa)); - sa.sin6_family = AF_INET6; - sa.sin6_port = htons(0); - sa.sin6_addr = IN6ADDR_ANY_INIT; - - int reuseaddr = 1; - if (setsockopt(*hdl, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) < 0) { - throw std::runtime_error("setsockopt(SO_REUSEADDR) failed"); - } - - EXPECT_NE(bind(*hdl, reinterpret_cast(&sa), sizeof(sa)), -1); - - sockaddr_storage ss{}; - EXPECT_NO_THROW(ss = Utils::ipGetSockaddr(*hdl)); - - EXPECT_NE(Utils::ipDisplayName(ss), "unknown"); - EXPECT_NE(Utils::ipPort(ss), -1); -} - TEST(Utils, shell) { std::string out; int statuscode = Utils::shell("ls /", &out); diff --git a/src/uptane_generator/repo.cc b/src/uptane_generator/repo.cc index c87f9e5b13..392cd61f8a 100644 --- a/src/uptane_generator/repo.cc +++ b/src/uptane_generator/repo.cc @@ -240,7 +240,7 @@ void Repo::generateCampaigns() const { c.estPreparationDuration = 20; Json::Value json; - campaign::JsonFromCampaigns(campaigns, json); + campaign::Campaign::JsonFromCampaigns(campaigns, json); Utils::writeFile(path_ / "campaigns.json", Utils::jsonToCanonicalStr(json)); } diff --git a/src/virtual_secondary/CMakeLists.txt b/src/virtual_secondary/CMakeLists.txt index 2017fe7547..5f1cb3beff 100644 --- a/src/virtual_secondary/CMakeLists.txt +++ b/src/virtual_secondary/CMakeLists.txt @@ -6,6 +6,8 @@ set(TARGET virtual_secondary) add_library(${TARGET} STATIC ${SOURCES} + $ + $ ) target_include_directories(${TARGET} PUBLIC ${PROJECT_SOURCE_DIR}/src/virtual_secondary) diff --git a/src/virtual_secondary/managedsecondary.cc b/src/virtual_secondary/managedsecondary.cc index 14cadbe9b0..594d87e5bc 100644 --- a/src/virtual_secondary/managedsecondary.cc +++ b/src/virtual_secondary/managedsecondary.cc @@ -9,8 +9,9 @@ #include "crypto/crypto.h" #include "logging/logging.h" - -#include +#include "uptane/manifest.h" +#include "uptane/uptanerepository.h" +#include "utilities/exceptions.h" namespace Primary { @@ -109,7 +110,7 @@ bool ManagedSecondary::putMetadata(const Uptane::RawMetaPack &meta_pack) { return true; } -int ManagedSecondary::getRootVersion(const bool director) { +int ManagedSecondary::getRootVersion(const bool director) const { if (director) { return current_meta.director_root.version(); } @@ -138,7 +139,7 @@ bool ManagedSecondary::putRoot(const std::string &root, const bool director) { return true; } -bool ManagedSecondary::sendFirmware(const std::shared_ptr &data) { +bool ManagedSecondary::sendFirmware(const std::string &data) { std::lock_guard l(install_mutex); if (expected_target_name.empty()) { @@ -148,7 +149,7 @@ bool ManagedSecondary::sendFirmware(const std::shared_ptr &data) { return false; } - if (data->size() > static_cast(expected_target_length)) { + if (data.size() > static_cast(expected_target_length)) { detected_attack = "overflow"; return false; } @@ -156,13 +157,13 @@ bool ManagedSecondary::sendFirmware(const std::shared_ptr &data) { std::vector::const_iterator it; for (it = expected_target_hashes.begin(); it != expected_target_hashes.end(); it++) { if (it->TypeString() == "sha256") { - if (boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(*data))) != + if (boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(data))) != boost::algorithm::to_lower_copy(it->HashString())) { detected_attack = "wrong_hash"; return false; } } else if (it->TypeString() == "sha512") { - if (boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha512digest(*data))) != + if (boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha512digest(data))) != boost::algorithm::to_lower_copy(it->HashString())) { detected_attack = "wrong_hash"; return false; @@ -170,32 +171,23 @@ bool ManagedSecondary::sendFirmware(const std::shared_ptr &data) { } } detected_attack = ""; - const bool result = storeFirmware(expected_target_name, *data); + const bool result = storeFirmware(expected_target_name, data); return result; } -Json::Value ManagedSecondary::getManifest() { - std::string hash; - std::string targetname; - size_t target_len; - if (!getFirmwareInfo(&targetname, target_len, &hash)) { - return Json::nullValue; - } - - Json::Value manifest; - - // package manager will generate this part in future - Json::Value installed_image; - installed_image["filepath"] = targetname; +data::ResultCode::Numeric ManagedSecondary::install(const std::string &target_name) { + (void)target_name; + return data::ResultCode::Numeric::kOk; +} - installed_image["fileinfo"]["hashes"]["sha256"] = hash; - installed_image["fileinfo"]["length"] = static_cast(target_len); +Uptane::Manifest ManagedSecondary::getManifest() const { + Uptane::InstalledImageInfo firmware_info; + if (!getFirmwareInfo(firmware_info)) { + return Json::Value(Json::nullValue); + } + Json::Value manifest = Uptane::ManifestIssuer::assembleManifest(firmware_info, getSerial()); manifest["attacks_detected"] = detected_attack; - manifest["installed_image"] = installed_image; - manifest["ecu_serial"] = getSerial().ToString(); - manifest["previous_timeserver_time"] = "1970-01-01T00:00:00Z"; - manifest["timeserver_time"] = "1970-01-01T00:00:00Z"; Json::Value signed_ecu_version; diff --git a/src/virtual_secondary/managedsecondary.h b/src/virtual_secondary/managedsecondary.h index 2e24b610f4..f8f2f08fd0 100644 --- a/src/virtual_secondary/managedsecondary.h +++ b/src/virtual_secondary/managedsecondary.h @@ -45,20 +45,22 @@ class ManagedSecondary : public Uptane::SecondaryInterface { void Initialize(); - Uptane::EcuSerial getSerial() override { + Uptane::EcuSerial getSerial() const override { if (!sconfig.ecu_serial.empty()) { return Uptane::EcuSerial(sconfig.ecu_serial); } return Uptane::EcuSerial(public_key_.KeyId()); } - Uptane::HardwareIdentifier getHwId() override { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } - PublicKey getPublicKey() override { return public_key_; } + Uptane::HardwareIdentifier getHwId() const override { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } + PublicKey getPublicKey() const override { return public_key_; } bool putMetadata(const Uptane::RawMetaPack& meta_pack) override; - int getRootVersion(bool director) override; + int getRootVersion(bool director) const override; bool putRoot(const std::string& root, bool director) override; - bool sendFirmware(const std::shared_ptr& data) override; - Json::Value getManifest() override; + bool sendFirmware(const std::string& data) override; + data::ResultCode::Numeric install(const std::string& target_name) override; + + Uptane::Manifest getManifest() const override; bool loadKeys(std::string* pub_key, std::string* priv_key); @@ -79,7 +81,7 @@ class ManagedSecondary : public Uptane::SecondaryInterface { std::mutex install_mutex; virtual bool storeFirmware(const std::string& target_name, const std::string& content) = 0; - virtual bool getFirmwareInfo(std::string* target_name, size_t& target_len, std::string* sha256hash) = 0; + virtual bool getFirmwareInfo(Uptane::InstalledImageInfo& firmware_info) const = 0; void storeKeys(const std::string& pub_key, const std::string& priv_key); void rawToMeta(); diff --git a/src/virtual_secondary/partialverificationsecondary.cc b/src/virtual_secondary/partialverificationsecondary.cc index bfe405426a..9952e1cceb 100644 --- a/src/virtual_secondary/partialverificationsecondary.cc +++ b/src/virtual_secondary/partialverificationsecondary.cc @@ -57,12 +57,12 @@ bool PartialVerificationSecondary::putMetadata(const RawMetaPack &meta) { return true; } -Json::Value PartialVerificationSecondary::getManifest() { +Uptane::Manifest PartialVerificationSecondary::getManifest() const { throw NotImplementedException(); return Json::Value(); } -int PartialVerificationSecondary::getRootVersion(bool director) { +int PartialVerificationSecondary::getRootVersion(bool director) const { (void)director; throw NotImplementedException(); return 0; @@ -76,10 +76,14 @@ bool PartialVerificationSecondary::putRoot(const std::string &root, bool directo return false; } -bool PartialVerificationSecondary::sendFirmware(const std::shared_ptr &data) { +bool PartialVerificationSecondary::sendFirmware(const std::string &data) { (void)data; throw NotImplementedException(); - return false; +} + +data::ResultCode::Numeric PartialVerificationSecondary::install(const std::string &target_name) { + (void)target_name; + throw NotImplementedException(); } void PartialVerificationSecondary::storeKeys(const std::string &public_key, const std::string &private_key) { diff --git a/src/virtual_secondary/partialverificationsecondary.h b/src/virtual_secondary/partialverificationsecondary.h index f633435b21..2773e94276 100644 --- a/src/virtual_secondary/partialverificationsecondary.h +++ b/src/virtual_secondary/partialverificationsecondary.h @@ -30,21 +30,22 @@ class PartialVerificationSecondary : public SecondaryInterface { public: explicit PartialVerificationSecondary(Primary::PartialVerificationSecondaryConfig sconfig_in); - EcuSerial getSerial() override { + EcuSerial getSerial() const override { if (!sconfig.ecu_serial.empty()) { return Uptane::EcuSerial(sconfig.ecu_serial); } return Uptane::EcuSerial(public_key_.KeyId()); } - Uptane::HardwareIdentifier getHwId() override { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } - PublicKey getPublicKey() override { return public_key_; } + Uptane::HardwareIdentifier getHwId() const override { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } + PublicKey getPublicKey() const override { return public_key_; } bool putMetadata(const RawMetaPack& meta) override; - int getRootVersion(bool director) override; + int getRootVersion(bool director) const override; bool putRoot(const std::string& root, bool director) override; - bool sendFirmware(const std::shared_ptr& data) override; - Json::Value getManifest() override; + bool sendFirmware(const std::string& data) override; + data::ResultCode::Numeric install(const std::string& target_name) override; + Uptane::Manifest getManifest() const override; private: void storeKeys(const std::string& public_key, const std::string& private_key); diff --git a/src/virtual_secondary/virtualsecondary.cc b/src/virtual_secondary/virtualsecondary.cc index 37174d81d3..5e31ca9c0c 100644 --- a/src/virtual_secondary/virtualsecondary.cc +++ b/src/virtual_secondary/virtualsecondary.cc @@ -70,26 +70,37 @@ VirtualSecondary::VirtualSecondary(Primary::VirtualSecondaryConfig sconfig_in) bool VirtualSecondary::storeFirmware(const std::string& target_name, const std::string& content) { if (fiu_fail((std::string("secondary_install_") + getSerial().ToString()).c_str()) != 0) { + // consider changing this approach of the fault injection, since the current approach impacts the non-test code flow + // here as well as it doesn't test the installation failure on secondary from an end-to-end perspective as it + // injects an error on the middle of the control flow that would have happened if an installation error had happened + // in case of the virtual or the ip-secondary or any other secondary, e.g. add a mock secondary that returns an + // error to sendFirmware/install request we might consider passing the installation description message from + // Secondary, not just bool and/or data::ResultCode::Numeric return false; } + + // TODO: it does not make much sense to read, pass via a function parameter to Virtual secondary + // and store the file that has been already downloaded by Primary + // Primary should apply ECU (primary, virtual, secondary) specific verification, download and installation logic in + // the first place Utils::writeFile(sconfig.target_name_path, target_name); Utils::writeFile(sconfig.firmware_path, content); sync(); return true; } -bool VirtualSecondary::getFirmwareInfo(std::string* target_name, size_t& target_len, std::string* sha256hash) { +bool VirtualSecondary::getFirmwareInfo(Uptane::InstalledImageInfo& firmware_info) const { std::string content; if (!boost::filesystem::exists(sconfig.target_name_path) || !boost::filesystem::exists(sconfig.firmware_path)) { - *target_name = std::string("noimage"); + firmware_info.name = std::string("noimage"); content = ""; } else { - *target_name = Utils::readFile(sconfig.target_name_path.string()); + firmware_info.name = Utils::readFile(sconfig.target_name_path.string()); content = Utils::readFile(sconfig.firmware_path.string()); } - *sha256hash = boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(content))); - target_len = content.size(); + firmware_info.hash = Uptane::ManifestIssuer::generateVersionHashStr(content); + firmware_info.len = content.size(); return true; } diff --git a/src/virtual_secondary/virtualsecondary.h b/src/virtual_secondary/virtualsecondary.h index 4db18eb868..68ff553721 100644 --- a/src/virtual_secondary/virtualsecondary.h +++ b/src/virtual_secondary/virtualsecondary.h @@ -27,7 +27,7 @@ class VirtualSecondary : public ManagedSecondary { private: bool storeFirmware(const std::string& target_name, const std::string& content) override; - bool getFirmwareInfo(std::string* target_name, size_t& target_len, std::string* sha256hash) override; + bool getFirmwareInfo(Uptane::InstalledImageInfo& firmware_info) const override; }; } // namespace Primary diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8edc66a3a1..812208eaff 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -189,7 +189,7 @@ set_tests_properties(test_log_negative add_test(NAME test_ip_secondary COMMAND ${PROJECT_SOURCE_DIR}/tests/ipsecondary_test.py - --build-dir ${PROJECT_BINARY_DIR} --src-dir ${PROJECT_SOURCE_DIR}) + --build-dir ${PROJECT_BINARY_DIR} --src-dir ${PROJECT_SOURCE_DIR} --ostree ${BUILD_OSTREE}) add_test(NAME test_backend_failure COMMAND ${PROJECT_SOURCE_DIR}/tests/test_backend_failure.py diff --git a/tests/aktualizr.supp b/tests/aktualizr.supp index 51aae5c554..d2554f850e 100644 --- a/tests/aktualizr.supp +++ b/tests/aktualizr.supp @@ -90,3 +90,19 @@ fun:ostree_sysroot_simple_write_deployment ... } +{ + asn1c ber_decode issues + Memcheck:Cond + ... + fun:CHOICE_decode_ber + fun:ber_decode + ... +} +{ + asn1c ber_decode issues + Memcheck:Value8 + ... + fun:CHOICE_decode_ber + fun:ber_decode + ... +} diff --git a/tests/ipsecondary_test.py b/tests/ipsecondary_test.py index 72d3e1d38d..4f87357b34 100755 --- a/tests/ipsecondary_test.py +++ b/tests/ipsecondary_test.py @@ -5,7 +5,8 @@ from os import getcwd, chdir, path -from test_fixtures import with_aktualizr, with_uptane_backend, KeyStore, with_secondary +from test_fixtures import with_aktualizr, with_uptane_backend, KeyStore, with_secondary, with_treehub,\ + with_sysroot, with_director logger = logging.getLogger("IPSecondaryTest") @@ -81,12 +82,50 @@ def test_secondary_update(uptane_repo, secondary, aktualizr, **kwargs): return test_result +@with_treehub() +@with_uptane_backend() +@with_director() +@with_sysroot() +@with_secondary(start=False) +@with_aktualizr(start=False, run_mode='once', output_logs=True) +def test_secondary_ostree_update(uptane_repo, secondary, aktualizr, treehub, sysroot, director, **kwargs): + """Test Secondary ostree update if a boot order of Secondary and Primary is undefined""" + + test_result = True + target_rev = treehub.revision + uptane_repo.add_ostree_target(secondary.id, target_rev) + + with secondary: + with aktualizr: + aktualizr.wait_for_completion() + + pending_rev = aktualizr.get_current_pending_image_info(secondary.id) + + if pending_rev != target_rev: + logger.error("Pending version {} != the target one {}".format(pending_rev, target_rev)) + return False + + sysroot.update_revision(pending_rev) + secondary.emulate_reboot() + + with secondary: + with aktualizr: + aktualizr.wait_for_completion() + + installed_rev = aktualizr.get_current_image_info(secondary.id) + + if installed_rev != target_rev: + logger.error("Installed version {} != the target one {}".format(installed_rev, target_rev)) + return False + + return director.get_install_result() + @with_uptane_backend() @with_secondary(start=False) @with_aktualizr(start=False, output_logs=False, wait_timeout=0.1) def test_primary_timeout_during_first_run(uptane_repo, secondary, aktualizr, **kwargs): - '''Test Aktualizr's timeout of waiting for Secondaries during initial boot''' + """Test Aktualizr's timeout of waiting for Secondaries during initial boot""" secondary_image_filename = "secondary_image_filename_001.img" secondary_image_hash = uptane_repo.add_image(id=secondary.id, image_filename=secondary_image_filename) @@ -150,17 +189,24 @@ def test_primary_timeout_after_device_is_registered(uptane_repo, secondary, aktu parser = argparse.ArgumentParser(description='Test IP Secondary') parser.add_argument('-b', '--build-dir', help='build directory', default='build') parser.add_argument('-s', '--src-dir', help='source directory', default='.') + parser.add_argument('-o', '--ostree', help='ostree support', default='OFF') + input_params = parser.parse_args() KeyStore.base_dir = path.abspath(input_params.src_dir) initial_cwd = getcwd() chdir(input_params.build_dir) - test_suite = [test_secondary_update_if_secondary_starts_first, - test_secondary_update_if_primary_starts_first, - test_secondary_update, - test_primary_timeout_during_first_run, - test_primary_timeout_after_device_is_registered] + test_suite = [ + test_secondary_update, + test_secondary_update_if_secondary_starts_first, + test_secondary_update_if_primary_starts_first, + test_primary_timeout_during_first_run, + test_primary_timeout_after_device_is_registered + ] + + if input_params.ostree == 'ON': + test_suite.append(test_secondary_ostree_update) test_suite_run_result = True for test in test_suite: diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py index c3f2b01fee..509449762e 100644 --- a/tests/test_fixtures.py +++ b/tests/test_fixtures.py @@ -57,7 +57,7 @@ def __init__(self, aktualizr_primary_exe, aktualizr_info_exe, id, secondary_cfg_file=self._secondary_config_file, director=director.base_url if director else '', image_repo=image_repo.base_url if image_repo else '', - pacmam_type='ostree' if treehub and sysroot else 'fake', + pacman_type='ostree' if treehub and sysroot else 'fake', ostree_sysroot=sysroot.path if sysroot else '', treehub_server=treehub.base_url if treehub else '', sentinel_dir=self._storage_dir.name, @@ -92,7 +92,7 @@ def __init__(self, aktualizr_primary_exe, aktualizr_info_exe, id, sqldb_path = "{db_path}" [pacman] - type = "{pacmam_type}" + type = "{pacman_type}" sysroot = "{ostree_sysroot}" ostree_server = "{treehub_server}" os = "dummy-os" @@ -172,13 +172,15 @@ def get_current_image_info(self, ecu_id): else: return self._get_current_image_info(ecu_id) + def get_current_pending_image_info(self, ecu_id): + return self._get_current_image_info(ecu_id, secondary_image_hash_field='pending image hash: ') + # applicable only to secondary ECUs due to inconsistency in presenting information # about primary and secondary ECUs # ugly stuff that could be removed if Aktualizr had exposed API to check status # or aktializr-info had output status/info in a structured way (e.g. json) - def _get_current_image_info(self, ecu_id): - secondary_image_hash_field = 'installed image hash: ' - secondary_image_filename_field = 'installed image filename: ' + def _get_current_image_info(self, ecu_id, secondary_image_hash_field='installed image hash: '): + #secondary_image_filename_field = 'installed image filename: ' aktualizr_status = self.get_info() ecu_serial = ecu_id[1] ecu_info_position = aktualizr_status.find(ecu_serial) @@ -278,7 +280,8 @@ def cert(): class IPSecondary: - def __init__(self, aktualizr_secondary_exe, id, port=9050, primary_port=9040): + def __init__(self, aktualizr_secondary_exe, id, port=9050, primary_port=9040, + sysroot=None, treehub=None, ostree_mock_path=None, **kwargs): self.id = id self.port = port @@ -286,14 +289,28 @@ def __init__(self, aktualizr_secondary_exe, id, port=9050, primary_port=9040): self._storage_dir = tempfile.TemporaryDirectory() self.port = self.get_free_port() self.primary_port = self.get_free_port() + self._sentinel_file = 'need_reboot' + self.reboot_sentinel_file = os.path.join(self._storage_dir.name, self._sentinel_file) with open(path.join(self._storage_dir.name, 'config.toml'), 'w+') as config_file: config_file.write(IPSecondary.CONFIG_TEMPLATE.format(serial=id[1], hw_ID=id[0], port=self.port, primary_port=self.primary_port, storage_dir=self._storage_dir.name, - db_path=path.join(self._storage_dir.name, 'db.sql'))) + db_path=path.join(self._storage_dir.name, 'db.sql'), + pacman_type='ostree' if treehub and sysroot else 'fake', + ostree_sysroot=sysroot.path if sysroot else '', + treehub_server=treehub.base_url if treehub else '', + sentinel_dir=self._storage_dir.name, + sentinel_name=self._sentinel_file + )) self._config_file = config_file.name + self._run_env = {} + if sysroot and ostree_mock_path: + self._run_env['LD_PRELOAD'] = os.path.abspath(ostree_mock_path) + self._run_env['OSTREE_DEPLOYMENT_VERSION_FILE'] = sysroot.version_file + + CONFIG_TEMPLATE = ''' [uptane] ecu_serial = "{serial}" @@ -310,7 +327,15 @@ def __init__(self, aktualizr_secondary_exe, id, port=9050, primary_port=9040): sqldb_path = "{db_path}" [pacman] - type = "fake" + type = "{pacman_type}" + sysroot = "{ostree_sysroot}" + ostree_server = "{treehub_server}" + os = "dummy-os" + + [bootloader] + reboot_sentinel_dir = "{sentinel_dir}" + reboot_sentinel_name = "{sentinel_name}" + reboot_command = "" ''' def is_running(self): @@ -326,7 +351,10 @@ def get_free_port(): def __enter__(self): self._process = subprocess.Popen([self._aktualizr_secondary_exe, '-c', self._config_file], - stdout=open(devnull, 'w'), close_fds=True) + stdout=None, + stderr=None, + close_fds=True, + env=self._run_env) logger.debug("IP Secondary {} has been started: {}".format(self.id, self.port)) return self @@ -335,6 +363,9 @@ def __exit__(self, exc_type, exc_val, exc_tb): self._process.wait(timeout=60) logger.debug("IP Secondary {} has been stopped".format(self.id)) + def emulate_reboot(self): + os.remove(self.reboot_sentinel_file) + class UptaneRepo(HTTPServer): def __init__(self, doc_root, ifc, port, client_handler_map={}): @@ -826,7 +857,7 @@ def with_secondary(start=True, id=('secondary-hw-ID-001', str(uuid4())), def decorator(test): @wraps(test) def wrapper(*args, **kwargs): - secondary = IPSecondary(aktualizr_secondary_exe=aktualizr_secondary_exe, id=id) + secondary = IPSecondary(aktualizr_secondary_exe=aktualizr_secondary_exe, id=id, **kwargs) if start: with secondary: result = test(*args, **kwargs, secondary=secondary) diff --git a/tests/test_update.py b/tests/test_update.py index f2fb11c102..23e01e89ba 100755 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -21,10 +21,10 @@ while aktualizr/primary is configured with ostree package manager """ @with_uptane_backend(start_generic_server=True) +@with_secondary(start=True) @with_director() @with_treehub() @with_sysroot() -@with_secondary(start=True) @with_aktualizr(start=False, run_mode='once', output_logs=True) def test_primary_ostree_secondary_fake_updates(uptane_repo, secondary, aktualizr, director, uptane_server, sysroot, treehub): diff --git a/tests/test_utils.cc b/tests/test_utils.cc index e7dce5eb95..cc2ee2d1c7 100644 --- a/tests/test_utils.cc +++ b/tests/test_utils.cc @@ -19,7 +19,7 @@ #include "logging/logging.h" -std::string TestUtils::getFreePort() { +in_port_t TestUtils::getFreePortAsInt() { int s = socket(AF_INET, SOCK_STREAM, 0); if (s == -1) { std::cout << "socket() failed: " << errno; @@ -42,9 +42,11 @@ std::string TestUtils::getFreePort() { throw std::runtime_error("getsockname failed"); } close(s); - return std::to_string(ntohs(sa.sin_port)); + return sa.sin_port; } +std::string TestUtils::getFreePort() { return std::to_string(ntohs(getFreePortAsInt())); } + void TestUtils::writePathToConfig(const boost::filesystem::path &toml_in, const boost::filesystem::path &toml_out, const boost::filesystem::path &storage_path) { // Append our temp_dir path as storage.path to the config file. This is a hack diff --git a/tests/test_utils.h b/tests/test_utils.h index d548924700..8972900a4e 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -10,6 +10,7 @@ struct TestUtils { static std::string getFreePort(); + static in_port_t getFreePortAsInt(); static void writePathToConfig(const boost::filesystem::path &toml_in, const boost::filesystem::path &toml_out, const boost::filesystem::path &storage_path); static void waitForServer(const std::string &address); diff --git a/tests/uptane_vector_tests.cc b/tests/uptane_vector_tests.cc index 0c187e4d70..230f8eb41b 100644 --- a/tests/uptane_vector_tests.cc +++ b/tests/uptane_vector_tests.cc @@ -96,11 +96,11 @@ TEST_P(UptaneVector, Test) { logger_set_threshold(boost::log::trivial::trace); auto storage = INvStorage::newStorage(config.storage); - Uptane::Manifest uptane_manifest{config, storage}; auto uptane_client = std_::make_unique(config, storage); Uptane::EcuSerial ecu_serial(config.provision.primary_ecu_serial); Uptane::HardwareIdentifier hw_id(config.provision.primary_ecu_hardware_id); uptane_client->hw_ids.insert(std::make_pair(ecu_serial, hw_id)); + uptane_client->primary_ecu_serial_ = ecu_serial; Uptane::EcuMap ecu_map{{ecu_serial, hw_id}}; Uptane::Target target("test_filename", ecu_map, {{Uptane::Hash::Type::kSha256, "sha256"}}, 1, ""); storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kCurrent);