Skip to content

Commit

Permalink
Import initial root.json from the filesystem
Browse files Browse the repository at this point in the history
This is useful for security in general, and critical for offline updates where
there isn't a TLS connection to bootstrap the fetch of the initial root.json.
During manufacture time flashing tool should write the initial root.json into
/var/sota/import/{repo|director}/root.json (assuming import.base_path is left
as /var/sota/import).

Signed-off-by: Phil Wise <[email protected]>
  • Loading branch information
cajun-rat committed Feb 22, 2022
1 parent 718bd87 commit 3b8fd67
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 4 deletions.
5 changes: 4 additions & 1 deletion src/libaktualizr/storage/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ target_sources(config PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/storage_config.cc)
add_aktualizr_test(NAME storage_atomic SOURCES storage_atomic_test.cc PROJECT_WORKING_DIRECTORY)
add_aktualizr_test(NAME sql_utils SOURCES sql_utils_test.cc PROJECT_WORKING_DIRECTORY)
add_aktualizr_test(NAME sqlstorage SOURCES sqlstorage_test.cc ARGS ${CMAKE_CURRENT_SOURCE_DIR}/test)
add_aktualizr_test(NAME storage_common SOURCES storage_common_test.cc PROJECT_WORKING_DIRECTORY)
add_aktualizr_test(NAME storage_common
SOURCES storage_common_test.cc
LIBRARIES uptane_generator_lib
PROJECT_WORKING_DIRECTORY)

add_test(NAME test_schema_migration
COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/schema_migration_test.sh ${PROJECT_SOURCE_DIR}/config/sql)
Expand Down
28 changes: 28 additions & 0 deletions src/libaktualizr/storage/invstorage.cc
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,33 @@ void INvStorage::importInstalledVersions(const boost::filesystem::path& base_pat
}
}

void INvStorage::importInitialRoot(const boost::filesystem::path& base_path) {
importInitialRootFile(base_path / "repo/root.json", Uptane::RepositoryType::Image());
importInitialRootFile(base_path / "director/root.json", Uptane::RepositoryType::Director());
}

void INvStorage::importInitialRootFile(const boost::filesystem::path& root_path, Uptane::RepositoryType repo_type) {
std::string root_tmp; // Only needed for loadLatestRoot
if (!loadLatestRoot(&root_tmp, repo_type)) {
if (boost::filesystem::is_regular_file(root_path)) {
try {
std::string root_str = Utils::readFile(root_path);
Uptane::Root orig_root(Uptane::Root::Policy::kAcceptAll);
Uptane::Root new_root(repo_type, Utils::parseJSON(root_str), orig_root);
// No exception. Save it
storeRoot(root_str, repo_type, Uptane::Version(new_root.version()));
LOG_INFO << "Imported initial " << repo_type << " root keys from " << root_path;
} catch (Uptane::Exception& e) {
LOG_WARNING << "Couldn't import initial " << repo_type << " root keys from " << root_path << " " << e.what();
}
} else {
LOG_DEBUG << "Not importing " << root_path << " because it doesn't exist";
}
} else {
LOG_TRACE << "Root for " << repo_type << " already present, not importing";
}
}

void INvStorage::importData(const ImportConfig& import_config) {
importPrimaryKeys(import_config.base_path, import_config.uptane_public_key_path,
import_config.uptane_private_key_path);
Expand All @@ -127,6 +154,7 @@ void INvStorage::importData(const ImportConfig& import_config) {
importUpdateSimple(import_config.base_path, &INvStorage::storeTlsPkey, &INvStorage::loadTlsPkey,
import_config.tls_pkey_path, "client TLS key");
importInstalledVersions(import_config.base_path);
importInitialRoot(import_config.base_path);
}

std::shared_ptr<INvStorage> INvStorage::newStorage(const StorageConfig& config, const bool readonly) {
Expand Down
11 changes: 11 additions & 0 deletions src/libaktualizr/storage/invstorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,17 @@ class INvStorage {
void importPrimaryKeys(const boost::filesystem::path& base_path, const utils::BasedPath& import_pubkey_path,
const utils::BasedPath& import_privkey_path);

/**
* Import initial image and director root.json from the filesystem.
* These would be loaded onto the device during provisioning at a well-known
* location such as /var/sota/import/repo/root.json (image repo) and
* /var/sota/import/director/root.json for the director repo.
*
* @param base_path e.g. '/var/sota/import'
*/
void importInitialRoot(const boost::filesystem::path& base_path);
void importInitialRootFile(const boost::filesystem::path& root_path, Uptane::RepositoryType repo_type);

protected:
const StorageConfig config_;
};
Expand Down
46 changes: 43 additions & 3 deletions src/libaktualizr/storage/storage_common_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@
#include <boost/filesystem.hpp>

#include "libaktualizr/types.h"
#include "repo.h"
#include "storage/sqlstorage.h"
#include "utilities/utils.h"

std::unique_ptr<INvStorage> Storage(const boost::filesystem::path &dir) {
namespace fs = boost::filesystem;

std::unique_ptr<INvStorage> Storage(const fs::path &dir) {
StorageConfig storage_config;
storage_config.type = StorageType::kSqlite;
storage_config.path = dir;
return std::unique_ptr<INvStorage>(new SQLStorage(storage_config, false));
}

StorageConfig MakeConfig(StorageType type, const boost::filesystem::path &storage_dir) {
StorageConfig MakeConfig(StorageType type, const fs::path &storage_dir) {
StorageConfig config;

config.type = type;
Expand Down Expand Up @@ -524,7 +527,7 @@ TEST(StorageCommon, LoadStoreSecondaryInfo) {
TEST(StorageImport, ImportData) {
TemporaryDirectory temp_dir;
std::unique_ptr<INvStorage> storage = Storage(temp_dir.Path());
boost::filesystem::create_directories(temp_dir / "import");
fs::create_directories(temp_dir / "import");

ImportConfig import_config;
import_config.base_path = temp_dir.Path() / "import";
Expand Down Expand Up @@ -640,6 +643,43 @@ TEST(StorageImport, ImportData) {
EXPECT_EQ(tls_pkey, tls_pkey_in3);
}

TEST(StorageImport, ImportInitialRoot) {
TemporaryDirectory temp_dir;
std::unique_ptr<INvStorage> storage = Storage(temp_dir.Path());
fs::create_directories(temp_dir / "import");

ImportConfig import_config;
import_config.base_path = temp_dir.Path() / "import";

// Generate a set of valid Uptane root keys
auto repo_path = temp_dir.Path() / "repo";
Repo image_repo{Uptane::RepositoryType::Image(), repo_path, "", ""};
image_repo.generateRepo();
Repo director_repo{Uptane::RepositoryType::Director(), repo_path, "", ""};
director_repo.generateRepo();
director_repo.rotate(Uptane::Role::Root());

EXPECT_FALSE(storage->loadLatestRoot(nullptr, Uptane::RepositoryType::Image()));
EXPECT_FALSE(storage->loadLatestRoot(nullptr, Uptane::RepositoryType::Director()));

fs::create_directories(import_config.base_path / "repo");
fs::create_directories(import_config.base_path / "director");

fs::copy(repo_path / "repo/repo/root.json", import_config.base_path / "repo/root.json");
Utils::writeFile(import_config.base_path / "director/root.json", std::string("invalid"));

storage->importData(import_config);
EXPECT_TRUE(storage->loadLatestRoot(nullptr, Uptane::RepositoryType::Image()));
EXPECT_FALSE(storage->loadLatestRoot(nullptr, Uptane::RepositoryType::Director()))
<< "Director root.json was invalid. It shouldn't have been imported";

// Copy the real director root.json over
fs::copy_file(repo_path / "repo/director/root.json", import_config.base_path / "director/root.json",
fs::copy_option::overwrite_if_exists);
storage->importData(import_config);
EXPECT_TRUE(storage->loadLatestRoot(nullptr, Uptane::RepositoryType::Director()));
}

#ifndef __NO_MAIN__
int main(int argc, char **argv) {
::testing::InitGoogleTest(&argc, argv);
Expand Down

0 comments on commit 3b8fd67

Please sign in to comment.