diff --git a/include/aktualizr-lite/api.h b/include/aktualizr-lite/api.h index 6886b799..45a4914a 100644 --- a/include/aktualizr-lite/api.h +++ b/include/aktualizr-lite/api.h @@ -240,6 +240,18 @@ class AkliteClient { */ CheckInResult CheckIn() const; + /** + * Performs a simplified "check-in" accessing locally available TUF metadata files. + * No communication is done with the device gateway. It consists of: + * 1) attempt to read a new new root.json - normally not found. + * 2) read timestamp and snapshot metadata. + * 3) read a new targets.json if needed + * + * This is an EXPERIMENTAL implementation. If there is Target data to be updated + * later on, it will still be fetched from the remote servers (ostree, app registry). + */ + CheckInResult CheckInLocal(const std::string &path) const; + /** * Return the active aktualizr-lite configuration. */ @@ -313,9 +325,11 @@ class AkliteClient { private: void Init(Config &config, bool finalize = true, bool apply_lock = true); + CheckInResult UpdateMetaAndGetTargets(std::shared_ptr repo_src) const; bool read_only_{false}; std::shared_ptr client_; + std::shared_ptr tuf_repo_; std::vector secondary_hwids_; mutable bool configUploaded_{false}; }; diff --git a/include/aktualizr-lite/tuf/tuf.h b/include/aktualizr-lite/tuf/tuf.h index c1e93649..34de05d5 100644 --- a/include/aktualizr-lite/tuf/tuf.h +++ b/include/aktualizr-lite/tuf/tuf.h @@ -108,6 +108,7 @@ class Repo { virtual std::vector GetTargets() = 0; virtual void updateMeta(std::shared_ptr repo_src) = 0; + virtual void checkMeta() = 0; protected: Repo() = default; diff --git a/src/api.cc b/src/api.cc index 46eeaf99..a0dd0dbb 100644 --- a/src/api.cc +++ b/src/api.cc @@ -13,6 +13,12 @@ #include "primary/reportqueue.h" #include "target.h" +#include "aktualizr-lite/tuf/tuf.h" +#include "composeappmanager.h" +#include "tuf/akhttpsreposource.h" +#include "tuf/akrepo.h" +#include "tuf/localreposource.h" + const std::vector AkliteClient::CONFIG_DIRS = {"/usr/lib/sota/conf.d", "/var/sota/sota.toml", "/etc/sota/conf.d/"}; @@ -93,6 +99,8 @@ void AkliteClient::Init(Config& config, bool finalize, bool apply_lock) { client_->finalizeInstall(); } } + + tuf_repo_ = std::make_unique(client_->config); } AkliteClient::AkliteClient(const std::vector& config_dirs, bool read_only, bool finalize) { @@ -119,31 +127,13 @@ AkliteClient::~AkliteClient() { static bool compareTargets(const TufTarget& a, const TufTarget& b) { return a.Version() < b.Version(); } -CheckInResult AkliteClient::CheckIn() const { - if (!configUploaded_) { - client_->reportAktualizrConfiguration(); - configUploaded_ = true; - } - client_->reportNetworkInfo(); - client_->reportHwInfo(); - client_->reportAppsState(); - - auto status = CheckInResult::Status::Ok; - Uptane::HardwareIdentifier hwidToFind(client_->config.provision.primary_ecu_hardware_id); - - LOG_INFO << "Refreshing Targets metadata"; - const auto rc = client_->updateImageMeta(); - if (!std::get<0>(rc)) { - LOG_WARNING << "Unable to update latest metadata, using local copy: " << std::get<1>(rc); - if (!client_->checkImageMetaOffline()) { - LOG_ERROR << "Unable to use local copy of TUF data"; - return CheckInResult(CheckInResult::Status::Failed, "", {}); - } - status = CheckInResult::Status::OkCached; - } - +// Returns a sorted list of targets matching tags and hwid (or one of secondary_hwids) +static std::vector filterTargets(const std::vector& allTargets, + const Uptane::HardwareIdentifier& hwidToFind, + const std::vector& tags, + const std::vector& secondary_hwids) { std::vector targets; - for (const auto& t : client_->allTargets()) { + for (const auto& t : allTargets) { int ver = 0; try { ver = std::stoi(t.custom_version(), nullptr, 0); @@ -151,7 +141,7 @@ CheckInResult AkliteClient::CheckIn() const { LOG_ERROR << "Invalid version number format: " << t.custom_version(); ver = -1; } - if (!target_has_tags(t, client_->tags)) { + if (!target_has_tags(t, tags)) { continue; } for (const auto& it : t.hardwareIds()) { @@ -159,7 +149,7 @@ CheckInResult AkliteClient::CheckIn() const { targets.emplace_back(t.filename(), t.sha256Hash(), ver, t.custom_data()); break; } - for (const auto& hwid : secondary_hwids_) { + for (const auto& hwid : secondary_hwids) { if (it == Uptane::HardwareIdentifier(hwid)) { targets.emplace_back(t.filename(), t.sha256Hash(), ver, t.custom_data()); break; @@ -167,9 +157,62 @@ CheckInResult AkliteClient::CheckIn() const { } } } - std::sort(targets.begin(), targets.end(), compareTargets); - return CheckInResult(status, client_->config.provision.primary_ecu_hardware_id, targets); + return targets; +} + +CheckInResult AkliteClient::UpdateMetaAndGetTargets(std::shared_ptr repo_src) const { + auto status = CheckInResult::Status::Ok; + + LOG_INFO << "Refreshing Targets metadata"; + try { + tuf_repo_->updateMeta(std::move(repo_src)); + } catch (const std::exception& e) { + LOG_WARNING << "Unable to update latest metadata, using local copy: " << e.what(); + try { + tuf_repo_->checkMeta(); + } catch (const std::exception& e) { + LOG_ERROR << "Unable to use local copy of TUF data"; + return CheckInResult(CheckInResult::Status::Failed, "", {}); + } + status = CheckInResult::Status::OkCached; + } + + auto allTargetsTuf = tuf_repo_->GetTargets(); + std::vector allTargets; + allTargets.reserve(allTargetsTuf.size()); + for (auto const& ut : allTargetsTuf) { + allTargets.emplace_back(Target::fromTufTarget(ut)); + } + + Uptane::HardwareIdentifier hwidToFind(client_->config.provision.primary_ecu_hardware_id); + auto matchingTargets = filterTargets(allTargets, hwidToFind, client_->tags, secondary_hwids_); + return CheckInResult(status, client_->config.provision.primary_ecu_hardware_id, matchingTargets); +} + +CheckInResult AkliteClient::CheckIn() const { + if (!configUploaded_) { + client_->reportAktualizrConfiguration(); + configUploaded_ = true; + } + client_->reportNetworkInfo(); + client_->reportHwInfo(); + client_->reportAppsState(); + + boost::property_tree::ptree pt; + pt.put("tag", client_->tags.empty() ? "" : client_->tags.at(0)); + auto current = client_->getCurrent(); + pt.put("dockerapps", Target::appsStr(current, ComposeAppManager::Config(client_->config.pacman).apps)); + pt.put("target", current.filename()); + pt.put("ostreehash", current.sha256Hash()); + + auto repo_src = std::make_shared("temp-remote-repo-source", pt, client_->config); + return UpdateMetaAndGetTargets(repo_src); +} + +CheckInResult AkliteClient::CheckInLocal(const std::string& path) const { + auto repo_src = std::make_shared("temp-local-repo-source", path); + return UpdateMetaAndGetTargets(repo_src); } boost::property_tree::ptree AkliteClient::GetConfig() const { @@ -320,10 +363,10 @@ std::unique_ptr AkliteClient::Installer(const TufTarget& t, std: } std::unique_ptr target; // Make sure the metadata is loaded from storage and valid. - client_->checkImageMetaOffline(); - for (const auto& tt : client_->allTargets()) { - if (tt.filename() == t.Name()) { - target = std::make_unique(tt); + tuf_repo_->checkMeta(); + for (const auto& tt : tuf_repo_->GetTargets()) { + if (tt.Name() == t.Name()) { + target = std::make_unique(Target::fromTufTarget(tt)); break; } } diff --git a/src/tuf/akhttpsreposource.cc b/src/tuf/akhttpsreposource.cc index 53c5c688..5ad77f92 100644 --- a/src/tuf/akhttpsreposource.cc +++ b/src/tuf/akhttpsreposource.cc @@ -9,7 +9,16 @@ namespace aklite::tuf { -AkHttpsRepoSource::AkHttpsRepoSource(const std::string& name_in, boost::property_tree::ptree& pt) { init(name_in, pt); } +AkHttpsRepoSource::AkHttpsRepoSource(const std::string& name_in, boost::property_tree::ptree& pt) { + boost::program_options::variables_map m; + Config config(m); + fillConfig(config, pt); + init(name_in, pt, config); +} + +AkHttpsRepoSource::AkHttpsRepoSource(const std::string& name_in, boost::property_tree::ptree& pt, Config& config) { + init(name_in, pt, config); +} static std::string readFileIfExists(const utils::BasedPath& based_path) { if (based_path.empty()) { @@ -23,7 +32,7 @@ static std::string readFileIfExists(const utils::BasedPath& based_path) { } } -void AkHttpsRepoSource::init(const std::string& name_in, boost::property_tree::ptree& pt) { +void AkHttpsRepoSource::init(const std::string& name_in, boost::property_tree::ptree& pt, Config& config) { name_ = name_in; std::vector headers; @@ -33,10 +42,6 @@ void AkHttpsRepoSource::init(const std::string& name_in, boost::property_tree::p } auto http_client = std::make_shared(&headers); - boost::program_options::variables_map m; - Config config(m); - fillConfig(config, pt); - P11EngineGuard p11(config.p11.module, config.p11.pass, config.p11.label); http_client->setCerts(config.tls.ca_source == CryptoSource::kFile ? readFileIfExists(config.import.tls_cacert_path) : p11->getItemFullId(config.p11.tls_cacert_id), diff --git a/src/tuf/akhttpsreposource.h b/src/tuf/akhttpsreposource.h index 81212964..84643849 100644 --- a/src/tuf/akhttpsreposource.h +++ b/src/tuf/akhttpsreposource.h @@ -16,7 +16,7 @@ class AkHttpsRepoSource : public RepoSource { std::string fetchTargets() override; private: - void init(const std::string& name_in, boost::property_tree::ptree& pt); + void init(const std::string& name_in, boost::property_tree::ptree& pt, Config& config); static void fillConfig(Config& config, boost::property_tree::ptree& pt); std::string fetchRole(const Uptane::Role& role, int64_t maxsize, Uptane::Version version); diff --git a/src/tuf/akrepo.cc b/src/tuf/akrepo.cc index 88e16bf3..8afcf9ed 100644 --- a/src/tuf/akrepo.cc +++ b/src/tuf/akrepo.cc @@ -62,4 +62,6 @@ void AkRepo::FetcherWrapper::fetchLatestRole(std::string* result, int64_t maxsiz fetchRole(result, maxsize, repo, role, Uptane::Version()); } +void AkRepo::checkMeta() { image_repo_.checkMetaOffline(*storage_); } + } // namespace aklite::tuf diff --git a/src/tuf/akrepo.h b/src/tuf/akrepo.h index 3bd1b2f4..345e9ef4 100644 --- a/src/tuf/akrepo.h +++ b/src/tuf/akrepo.h @@ -15,6 +15,7 @@ class AkRepo : public Repo { explicit AkRepo(const Config& config); std::vector GetTargets() override; void updateMeta(std::shared_ptr repo_src) override; + void checkMeta() override; private: void init(const boost::filesystem::path& storage_path); diff --git a/tests/apiclient_test.cc b/tests/apiclient_test.cc index 32beae10..33b51daf 100644 --- a/tests/apiclient_test.cc +++ b/tests/apiclient_test.cc @@ -125,6 +125,33 @@ TEST_F(ApiClientTest, CheckIn) { ASSERT_EQ(new_target.sha256Hash(), result.Targets()[1].Sha256Hash()); } +TEST_F(ApiClientTest, CheckInLocal) { + AkliteClient client(createLiteClient(InitialVersion::kOn)); + + // Accessing repo metadata files directly from the local filesystem + auto repo_dir = getTufRepo().getRepoPath(); + auto result = client.CheckInLocal(repo_dir); + ASSERT_EQ(CheckInResult::Status::Ok, result.status); + ASSERT_EQ(1, result.Targets().size()); + + // No communication is done with the device gateway inside CheckInLocal + auto events = getDeviceGateway().getEvents(); + ASSERT_EQ(0, events.size()); + ASSERT_EQ("", getDeviceGateway().readSotaToml()); + + ASSERT_EQ(CheckInResult::Status::Ok, result.status); + ASSERT_EQ(1, result.Targets().size()); + + auto new_target = createTarget(); + result = client.CheckInLocal(repo_dir); + ASSERT_EQ(0, getDeviceGateway().getEvents().size()); + ASSERT_EQ("", getDeviceGateway().readSotaToml()); + ASSERT_EQ(CheckInResult::Status::Ok, result.status); + ASSERT_EQ(2, result.Targets().size()); + ASSERT_EQ(new_target.filename(), result.Targets()[1].Name()); + ASSERT_EQ(new_target.sha256Hash(), result.Targets()[1].Sha256Hash()); +} + TEST_F(ApiClientTest, CheckInWithoutTargetImport) { AkliteClient client(createLiteClient(InitialVersion::kOff));