Skip to content
This repository has been archived by the owner on May 21, 2024. It is now read-only.

Commit

Permalink
OLPSUP-9046: Handle expired Director's target metadata
Browse files Browse the repository at this point in the history
- Apply pending update even if its metadata are expired provided that it
was installed before the expiration
- Add tests for the target metadata expiration use-case

Signed-off-by: Mike Sul <[email protected]>
  • Loading branch information
Mike Sul committed Feb 3, 2020
1 parent 05fee03 commit 748d6e8
Show file tree
Hide file tree
Showing 21 changed files with 323 additions and 68 deletions.
7 changes: 4 additions & 3 deletions src/aktualizr_secondary/aktualizr_secondary.cc
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ AktualizrSecondary::AktualizrSecondary(AktualizrSecondaryConfig config, std::sha
std::vector<Uptane::Target> installed_versions;
boost::optional<Uptane::Target> pending_target;
storage_->loadInstalledVersions(ecu_serial_.ToString(), nullptr, &pending_target);
data::InstallationResult install_res =
data::InstallationResult(data::ResultCode::Numeric::kUnknown, "Unknown installation error");
LOG_INFO << "Pending update found; attempting to apply it. Target hash: " << pending_target->sha256Hash();

if (!!pending_target) {
data::InstallationResult install_res =
data::InstallationResult(data::ResultCode::Numeric::kUnknown, "Unknown installation error");
LOG_INFO << "Pending update found; attempting to apply it. Target hash: " << pending_target->sha256Hash();

install_res = update_agent_->applyPendingInstall(*pending_target);

if (install_res.result_code != data::ResultCode::Numeric::kNeedCompletion) {
Expand Down
13 changes: 1 addition & 12 deletions src/aktualizr_secondary/update_agent_ostree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -78,18 +78,7 @@ data::ResultCode::Numeric OstreeUpdateAgent::install(const Uptane::Target& targe
}

data::InstallationResult OstreeUpdateAgent::applyPendingInstall(const Uptane::Target& target) {
if (!_ostreePackMan->rebootDetected()) {
// it should be removed from here, once we refactor the ostree package manager,
// e.g. _ostreePackMan->finalizeInstall() does this check/if
return data::InstallationResult(data::ResultCode::Numeric::kNeedCompletion,
"Reboot is required for the pending update application");
}

data::InstallationResult install_result = _ostreePackMan->finalizeInstall(target);
// it should be removed from here, once we refactor the ostree package manager,
// e.g. the pacman will reset/clear the flag by itself
_ostreePackMan->rebootFlagClear();
return install_result;
return _ostreePackMan->finalizeInstall(target);
}

void extractCredentialsArchive(const std::string& archive, std::string* ca, std::string* cert, std::string* pkey,
Expand Down
2 changes: 1 addition & 1 deletion src/libaktualizr/package_manager/debianmanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class DebianManager : public PackageManagerInterface {
Json::Value getInstalledPackages() const override;
Uptane::Target getCurrent() const override;
data::InstallationResult install(const Uptane::Target &target) const override;
data::InstallationResult finalizeInstall(const Uptane::Target &target) const override {
data::InstallationResult finalizeInstall(const Uptane::Target &target) override {
(void)target;
throw std::runtime_error("Unimplemented");
}
Expand Down
15 changes: 14 additions & 1 deletion src/libaktualizr/package_manager/ostreemanager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,20 @@ void OstreeManager::completeInstall() const {
bootloader_->reboot();
}

data::InstallationResult OstreeManager::finalizeInstall(const Uptane::Target &target) const {
data::InstallationResult OstreeManager::finalizeInstall(const Uptane::Target &target) {
if (!bootloader_->rebootDetected()) {
return data::InstallationResult(data::ResultCode::Numeric::kNeedCompletion,
"Reboot is required for the pending update application");
}

data::InstallationResult install_result = applyInstall(target);
// it should be removed from here, once we refactor the ostree package manager,
// e.g. the pacman will reset/clear the flag by itself
bootloader_->rebootFlagClear();
return install_result;
}

data::InstallationResult OstreeManager::applyInstall(const Uptane::Target &target) {
LOG_INFO << "Checking installation of new OSTree sysroot";
const std::string current_hash = getCurrentHash();

Expand Down
3 changes: 2 additions & 1 deletion src/libaktualizr/package_manager/ostreemanager.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class OstreeManager : public PackageManagerInterface {
bool imageUpdated();
data::InstallationResult install(const Uptane::Target &target) const override;
void completeInstall() const override;
data::InstallationResult finalizeInstall(const Uptane::Target &target) const override;
data::InstallationResult finalizeInstall(const Uptane::Target &target) override;
bool fetchTarget(const Uptane::Target &target, Uptane::Fetcher &fetcher, const KeyManager &keys,
FetcherProgressCb progress_cb, const api::FlowControlToken *token) override;
TargetStatus verifyTarget(const Uptane::Target &target) const override;
Expand All @@ -65,6 +65,7 @@ class OstreeManager : public PackageManagerInterface {

private:
TargetStatus verifyTargetInternal(const Uptane::Target &target) const;
data::InstallationResult applyInstall(const Uptane::Target &target);
};

#endif // OSTREE_H_
10 changes: 9 additions & 1 deletion src/libaktualizr/package_manager/packagemanagerfake.cc
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,12 @@ void PackageManagerFake::completeInstall() const {
bootloader_->reboot(true);
}

data::InstallationResult PackageManagerFake::finalizeInstall(const Uptane::Target& target) const {
data::InstallationResult PackageManagerFake::finalizeInstall(const Uptane::Target& target) {
if (config.fake_need_reboot && !bootloader_->rebootDetected()) {
return data::InstallationResult(data::ResultCode::Numeric::kNeedCompletion,
"Reboot is required for the pending update application");
}

boost::optional<Uptane::Target> pending_version;
storage_->loadPrimaryInstalledVersions(nullptr, &pending_version);

Expand Down Expand Up @@ -80,6 +85,9 @@ data::InstallationResult PackageManagerFake::finalizeInstall(const Uptane::Targe
data::InstallationResult(data::ResultCode::Numeric::kInternalError, "Pending and new target do not match");
}

if (config.fake_need_reboot) {
bootloader_->rebootFlagClear();
}
return install_res;
}

Expand Down
2 changes: 1 addition & 1 deletion src/libaktualizr/package_manager/packagemanagerfake.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class PackageManagerFake : public PackageManagerInterface {

data::InstallationResult install(const Uptane::Target &target) const override;
void completeInstall() const override;
data::InstallationResult finalizeInstall(const Uptane::Target &target) const override;
data::InstallationResult finalizeInstall(const Uptane::Target &target) override;
bool fetchTarget(const Uptane::Target &target, Uptane::Fetcher &fetcher, const KeyManager &keys,
FetcherProgressCb progress_cb, const api::FlowControlToken *token) override;
};
Expand Down
2 changes: 2 additions & 0 deletions src/libaktualizr/package_manager/packagemanagerfake_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ TEST(PackageManagerFake, FinalizeAfterReboot) {
EXPECT_EQ(result.result_code, data::ResultCode::Numeric::kNeedCompletion);
storage->savePrimaryInstalledVersion(target, InstalledVersionUpdateMode::kPending);

fakepm.completeInstall();

result = fakepm.finalizeInstall(target);
EXPECT_EQ(result.result_code, data::ResultCode::Numeric::kOk);
}
Expand Down
2 changes: 1 addition & 1 deletion src/libaktualizr/package_manager/packagemanagerinterface.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class PackageManagerInterface {
virtual Uptane::Target getCurrent() const = 0;
virtual data::InstallationResult install(const Uptane::Target& target) const = 0;
virtual void completeInstall() const { throw std::runtime_error("Unimplemented"); };
virtual data::InstallationResult finalizeInstall(const Uptane::Target& target) const = 0;
virtual data::InstallationResult finalizeInstall(const Uptane::Target& target) = 0;
virtual bool rebootDetected() { return bootloader_->rebootDetected(); };
virtual void rebootFlagClear() { bootloader_->rebootFlagClear(); };
virtual void updateNotify() { bootloader_->updateNotify(); };
Expand Down
4 changes: 4 additions & 0 deletions src/libaktualizr/primary/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ add_aktualizr_test(NAME metadata_fetch SOURCES metadata_fetch_test.cc PROJECT_WO
ARGS "$<TARGET_FILE:uptane-generator>" LIBRARIES uptane_generator_lib)
target_link_libraries(t_metadata_fetch virtual_secondary)

add_aktualizr_test(NAME metadata_expiration SOURCES metadata_expiration_test.cc PROJECT_WORKING_DIRECTORY
ARGS "$<TARGET_FILE:uptane-generator>" LIBRARIES uptane_generator_lib)
target_link_libraries(t_metadata_expiration virtual_secondary)

add_aktualizr_test(NAME device_cred_prov SOURCES device_cred_prov_test.cc PROJECT_WORKING_DIRECTORY LIBRARIES PUBLIC uptane_generator_lib)
set_tests_properties(test_device_cred_prov PROPERTIES LABELS "crypto")

Expand Down
212 changes: 212 additions & 0 deletions src/libaktualizr/primary/metadata_expiration_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#include <gtest/gtest.h>

#include <string>

#include "httpfake.h"
#include "primary/aktualizr.h"
#include "test_utils.h"
#include "uptane_test_common.h"

boost::filesystem::path uptane_generator_path;

class MetadataExpirationTest : public ::testing::Test {
protected:
MetadataExpirationTest() : uptane_gen_(uptane_generator_path.string()) {
Process uptane_gen(uptane_generator_path.string());
uptane_gen.run({"generate", "--path", meta_dir_.PathString()});

auto http = std::make_shared<HttpFake>(temp_dir_.Path(), "", meta_dir_.Path() / "repo");
Config conf = UptaneTestCommon::makeTestConfig(temp_dir_, http->tls_server);
conf.pacman.fake_need_reboot = true;
conf.uptane.force_install_completion = true;
conf.bootloader.reboot_sentinel_dir = temp_dir_.Path();

logger_set_threshold(boost::log::trivial::trace);

storage_ = INvStorage::newStorage(conf.storage);
aktualizr_ = std::make_shared<UptaneTestCommon::TestAktualizr>(conf, storage_, http);
}

void addImage() {
uptane_gen_.run({"image", "--path", meta_dir_.PathString(), "--filename", "tests/test_data/firmware.txt",
"--targetname", target_filename_, "--hwid", "primary_hw"});

target_image_hash_ = boost::algorithm::to_lower_copy(
boost::algorithm::hex(Crypto::sha256digest(Utils::readFile("tests/test_data/firmware.txt"))));
}

void addTarget(const std::string& target_filename, int expiration_delta = 0) {
if (expiration_delta != 0) {
time_t new_expiration_time;
std::time(&new_expiration_time);
new_expiration_time += expiration_delta;
struct tm new_expiration_time_str {};
gmtime_r(&new_expiration_time, &new_expiration_time_str);

uptane_gen_.run({"addtarget", "--path", meta_dir_.PathString(), "--targetname", target_filename, "--hwid",
"primary_hw", "--serial", "CA:FE:A6:D2:84:9D", "--expires",
TimeStamp(new_expiration_time_str).ToString()});

} else {
uptane_gen_.run({"addtarget", "--path", meta_dir_.PathString(), "--targetname", target_filename, "--hwid",
"primary_hw", "--serial", "CA:FE:A6:D2:84:9D"});
}
}

void addTargetAndSign(const std::string& target_filename, int expiration_delta = 0) {
addTarget(target_filename, expiration_delta);
uptane_gen_.run({"signtargets", "--path", meta_dir_.PathString()});
}

void refreshTargetMetadata() {
// refresh the target metadata in the repo/Director
uptane_gen_.run({"refresh", "--path", meta_dir_.PathString(), "--repotype", "director", "--keyname", "targets"});
}

void addTargetToInstall(int expiration_delta = 0) {
addImage();
addTargetAndSign(target_filename_, expiration_delta);
}

protected:
Process uptane_gen_;
const std::string target_filename_ = "firmware.txt";
std::string target_image_hash_;

TemporaryDirectory meta_dir_;
TemporaryDirectory temp_dir_;
std::shared_ptr<INvStorage> storage_;
std::shared_ptr<UptaneTestCommon::TestAktualizr> aktualizr_;
};

TEST_F(MetadataExpirationTest, MetadataExpirationBeforeInstallation) {
aktualizr_->Initialize();
result::UpdateCheck update_result = aktualizr_->CheckUpdates().get();
EXPECT_EQ(update_result.status, result::UpdateStatus::kNoUpdatesAvailable);

addTargetToInstall(-1);

// run the uptane cycle an try to install the target
aktualizr_->UptaneCycle();

// check if the target has been installed and pending to be applied after a reboot
auto& client = aktualizr_->uptane_client();
ASSERT_FALSE(client->hasPendingUpdates());
ASSERT_FALSE(client->isInstallCompletionRequired());

auto currently_installed_target = client->getCurrent();

EXPECT_NE(target_image_hash_, currently_installed_target.sha256Hash());
EXPECT_NE(target_filename_, currently_installed_target.filename());

refreshTargetMetadata();

// run the uptane cycle an try to install the target
aktualizr_->UptaneCycle();

// check if the target has been installed and pending to be applied after a reboot
ASSERT_TRUE(client->hasPendingUpdates());
ASSERT_TRUE(client->isInstallCompletionRequired());

// force reboot
client->completeInstall();

// emulate aktualizr fresh start
aktualizr_->Initialize();
aktualizr_->UptaneCycle();

ASSERT_FALSE(client->hasPendingUpdates());
ASSERT_FALSE(client->isInstallCompletionRequired());

currently_installed_target = client->getCurrent();
EXPECT_EQ(target_image_hash_, currently_installed_target.sha256Hash());
EXPECT_EQ(target_filename_, currently_installed_target.filename());
}

TEST_F(MetadataExpirationTest, MetadataExpirationAfterInstallationAndBeforeReboot) {
aktualizr_->Initialize();

result::UpdateCheck update_result = aktualizr_->CheckUpdates().get();
EXPECT_EQ(update_result.status, result::UpdateStatus::kNoUpdatesAvailable);

addTargetToInstall(2);

// run the uptane cycle to install the target
aktualizr_->UptaneCycle();

// check if the target has been installed and pending to be applied after a reboot
auto& client = aktualizr_->uptane_client();
ASSERT_TRUE(client->hasPendingUpdates());
ASSERT_TRUE(client->isInstallCompletionRequired());

// emulate the target metadata expiration while the uptane cycle is running
std::this_thread::sleep_for(std::chrono::seconds(3));
aktualizr_->UptaneCycle();

// since the installation happenned before the metadata expiration we expect that
// the update is still pending and will be applied after a reboot
ASSERT_TRUE(client->hasPendingUpdates());
ASSERT_TRUE(client->isInstallCompletionRequired());

// force reboot
client->completeInstall();

// emulate aktualizr fresh start
aktualizr_->Initialize();
aktualizr_->UptaneCycle();

// check if the pending target has been applied. it should be applied in even if it's metadata are expired
// as long as it was installed at the moment when they were not expired
auto currently_installed_target = client->getCurrent();
EXPECT_EQ(target_image_hash_, currently_installed_target.sha256Hash());
EXPECT_EQ(target_filename_, currently_installed_target.filename());
}

TEST_F(MetadataExpirationTest, MetadataExpirationAfterInstallationAndBeforeApplication) {
aktualizr_->Initialize();

result::UpdateCheck update_result = aktualizr_->CheckUpdates().get();
EXPECT_EQ(update_result.status, result::UpdateStatus::kNoUpdatesAvailable);

addTargetToInstall(2);

// run the uptane cycle to install the target
aktualizr_->UptaneCycle();

// check if the target has been installed and pending to be applied after a reboot
auto& client = aktualizr_->uptane_client();
ASSERT_TRUE(client->hasPendingUpdates());
ASSERT_TRUE(client->isInstallCompletionRequired());

// wait until the target metadata are expired
std::this_thread::sleep_for(std::chrono::seconds(3));

// force reboot
client->completeInstall();

// emulate aktualizr fresh start
aktualizr_->Initialize();
aktualizr_->UptaneCycle();

// check if the pending target has been applied. it should be applied in even if it's metadta are expired
// as long as it was installed at the moment when they were not expired
auto currently_installed_target = client->getCurrent();
EXPECT_EQ(target_image_hash_, currently_installed_target.sha256Hash());
EXPECT_EQ(target_filename_, currently_installed_target.filename());
}

int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
if (argc != 2) {
std::cerr << "Error: " << argv[0] << " requires the path to the uptane-generator utility\n";
return EXIT_FAILURE;
}
uptane_generator_path = argv[1];

logger_init();
logger_set_threshold(boost::log::trivial::trace);

return RUN_ALL_TESTS();
}

// vim: set tabstop=2 shiftwidth=2 expandtab:
Loading

0 comments on commit 748d6e8

Please sign in to comment.