Skip to content

Commit

Permalink
api: Add CheckInLocal and adapt CheckIn to use new TUF API
Browse files Browse the repository at this point in the history
Also add a new checkMeta method to the TUF API, and unit tests for
CheckInLocal.

Signed-off-by: Andre Detsch <[email protected]>
  • Loading branch information
detsch committed Nov 28, 2023
1 parent 7798315 commit d9452f6
Show file tree
Hide file tree
Showing 8 changed files with 133 additions and 39 deletions.
14 changes: 14 additions & 0 deletions include/aktualizr-lite/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -313,9 +325,11 @@ class AkliteClient {

private:
void Init(Config &config, bool finalize = true, bool apply_lock = true);
CheckInResult UpdateMetaAndGetTargets(std::shared_ptr<aklite::tuf::RepoSource> repo_src) const;

bool read_only_{false};
std::shared_ptr<LiteClient> client_;
std::shared_ptr<aklite::tuf::TufRepo> tuf_repo_;
std::vector<std::string> secondary_hwids_;
mutable bool configUploaded_{false};
};
Expand Down
1 change: 1 addition & 0 deletions include/aktualizr-lite/tuf/tuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ class TufRepo {
public:
virtual std::vector<TufTarget> GetTargets() = 0;
virtual void updateMeta(std::shared_ptr<RepoSource> repo_src) = 0;
virtual void checkMeta() = 0;
};

} // namespace aklite::tuf
Expand Down
107 changes: 75 additions & 32 deletions src/api.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<boost::filesystem::path> AkliteClient::CONFIG_DIRS = {"/usr/lib/sota/conf.d", "/var/sota/sota.toml",
"/etc/sota/conf.d/"};

Expand Down Expand Up @@ -93,6 +99,8 @@ void AkliteClient::Init(Config& config, bool finalize, bool apply_lock) {
client_->finalizeInstall();
}
}

tuf_repo_ = std::make_unique<aklite::tuf::AkRepo>(client_->config);
}

AkliteClient::AkliteClient(const std::vector<boost::filesystem::path>& config_dirs, bool read_only, bool finalize) {
Expand All @@ -119,57 +127,92 @@ 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<TufTarget> filterTargets(const std::vector<Uptane::Target>& allTargets,
const Uptane::HardwareIdentifier& hwidToFind,
const std::vector<std::string>& tags,
const std::vector<std::string>& secondary_hwids) {
std::vector<TufTarget> targets;
for (const auto& t : client_->allTargets()) {
for (const auto& t : allTargets) {
int ver = 0;
try {
ver = std::stoi(t.custom_version(), nullptr, 0);
} catch (const std::invalid_argument& exc) {
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()) {
if (it == hwidToFind) {
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;
}
}
}
}

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<aklite::tuf::RepoSource> 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<Uptane::Target> 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<std::string>("tag", client_->tags.empty() ? "" : client_->tags.at(0));
auto current = client_->getCurrent();
pt.put<std::string>("dockerapps", Target::appsStr(current, ComposeAppManager::Config(client_->config.pacman).apps));
pt.put<std::string>("target", current.filename());
pt.put<std::string>("ostreehash", current.sha256Hash());

auto repo_src = std::make_shared<aklite::tuf::AkHttpsRepoSource>("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<aklite::tuf::LocalRepoSource>("temp-local-repo-source", path);
return UpdateMetaAndGetTargets(repo_src);
}

boost::property_tree::ptree AkliteClient::GetConfig() const {
Expand Down Expand Up @@ -320,10 +363,10 @@ std::unique_ptr<InstallContext> AkliteClient::Installer(const TufTarget& t, std:
}
std::unique_ptr<Uptane::Target> 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<Uptane::Target>(tt);
tuf_repo_->checkMeta();
for (const auto& tt : tuf_repo_->GetTargets()) {
if (tt.Name() == t.Name()) {
target = std::make_unique<Uptane::Target>(Target::fromTufTarget(tt));
break;
}
}
Expand Down
17 changes: 11 additions & 6 deletions src/tuf/akhttpsreposource.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand All @@ -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<std::string> headers;
Expand All @@ -33,10 +42,6 @@ void AkHttpsRepoSource::init(const std::string& name_in, boost::property_tree::p
}
auto http_client = std::make_shared<HttpClientWithShare>(&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),
Expand Down
3 changes: 2 additions & 1 deletion src/tuf/akhttpsreposource.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ namespace aklite::tuf {
class AkHttpsRepoSource : public RepoSource {
public:
AkHttpsRepoSource(const std::string& name_in, boost::property_tree::ptree& pt);
AkHttpsRepoSource(const std::string& name_in, boost::property_tree::ptree& pt, Config& config);

std::string fetchRoot(int version) override;
std::string fetchTimestamp() override;
std::string fetchSnapshot() override;
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);

Expand Down
2 changes: 2 additions & 0 deletions src/tuf/akrepo.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions src/tuf/akrepo.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class AkRepo : public TufRepo {
explicit AkRepo(const Config& config);
std::vector<TufTarget> GetTargets() override;
void updateMeta(std::shared_ptr<RepoSource> repo_src) override;
void checkMeta() override;

private:
void init(const boost::filesystem::path& storage_path);
Expand Down
27 changes: 27 additions & 0 deletions tests/apiclient_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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));

Expand Down

0 comments on commit d9452f6

Please sign in to comment.