Skip to content

Commit

Permalink
tuf: Create a new API to encapsulate TUF operations
Browse files Browse the repository at this point in the history
The API alows a better isolation between aktualizr-lite and
libaktualizr, and also allows the use of multiple sources for a same TUF
repository.

Signed-off-by: Andre Detsch <[email protected]>
  • Loading branch information
detsch committed Nov 29, 2023
1 parent e5106f2 commit 28b1224
Show file tree
Hide file tree
Showing 12 changed files with 581 additions and 59 deletions.
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ option(ALLOW_MANUAL_ROLLBACK "Set to ON to build with support of manual rollback
option(BUILD_AKLITE_OFFLINE "Set to ON to build cli command for an offline update" OFF)
option(BUILD_AKLITE_WITH_NERDCTL "Set to ON to build with support of nerdctl/containerd" OFF)
option(BUILD_AKLITE_APPS "Set to ON to build cli command for Apps management" ON)
option(BUILD_TUFCTL "Set to ON to build sample tuf control application" OFF)

# If we build the sota tools we don't need aklite (???) and vice versa
# if we build aklite we don't need the sota tools
Expand Down Expand Up @@ -61,7 +62,9 @@ if(BUILD_AKLITE)
if(BUILD_AKLITE_OFFLINE)
add_subdirectory(apps/aklite-offline)
endif(BUILD_AKLITE_OFFLINE)

if(BUILD_TUFCTL)
add_subdirectory(apps/tufctl)
endif(BUILD_TUFCTL)
endif(BUILD_AKLITE)

# Use `-LH` options (cmake <args> -LH) to output all variables
Expand All @@ -72,3 +75,4 @@ message(STATUS "ALLOW_MANUAL_ROLLBACK: ${ALLOW_MANUAL_ROLLBACK}")
message(STATUS "BUILD_AKLITE_OFFLINE: ${BUILD_AKLITE_OFFLINE}")
message(STATUS "BUILD_AKLITE_WITH_NERDCTL: ${BUILD_AKLITE_WITH_NERDCTL}")
message(STATUS "BUILD_AKLITE_APPS: ${BUILD_AKLITE_APPS}")
message(STATUS "BUILD_TUFCTL: ${BUILD_TUFCTL}")
31 changes: 31 additions & 0 deletions apps/tufctl/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
cmake_minimum_required (VERSION 3.5)

set(TARGET tufctl)
project(${TARGET})

set(SRC main.cpp)
# set(HEADERS cmds.h)

add_executable(${TARGET} ${SRC})
add_dependencies(aktualizr-lite ${TARGET})
set_property(TARGET ${TARGET} PROPERTY CXX_STANDARD 17)

set(INCS
${AKLITE_DIR}/include/
${AKLITE_DIR}/src/
${AKTUALIZR_DIR}/src/libaktualizr
${AKTUALIZR_DIR}/include
${AKTUALIZR_DIR}/third_party/jsoncpp/include
# odd dependency of libaktualizr/http/httpclient.h
${AKTUALIZR_DIR}/third_party/googletest/googletest/include
${LIBOSTREE_INCLUDE_DIRS}
)

target_include_directories(${TARGET} PRIVATE ${INCS})

target_link_libraries(${TARGET} aktualizr_lite ${AKLITE_OFFLINE_LIB})

install(TARGETS ${TARGET} RUNTIME DESTINATION bin)

# enable creating clang-tidy targets for each source file (see aktualizr/CMakeLists.txt for details)
aktualizr_source_file_checks(${SRC} ${HEADERS})
106 changes: 106 additions & 0 deletions apps/tufctl/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/ptree_fwd.hpp>
#include "aktualizr-lite/tuf/tuf.h"
#include "tuf/akhttpsreposource.h"
#include "tuf/akrepo.h"
#include "tuf/localreposource.h"

// Strip leading and trailing quotes
std::string strip_quotes(const std::string& value) {
std::string res = value;
res.erase(std::remove(res.begin(), res.end(), '\"'), res.end());
return res;
}

int main(int argc, char** argv) {
if (argc < 2) {
std::cerr << "Usage example: " << argv[0] << " repo_sources.toml" << std::endl;
exit(1);
}

boost::filesystem::path storage_path;

std::vector<std::shared_ptr<aklite::tuf::RepoSource>> sources;
// Set up
{
boost::property_tree::ptree pt;
boost::property_tree::ini_parser::read_ini(argv[1], pt);

std::ostringstream oss;
boost::property_tree::write_json(oss, pt);
std::cout << oss.str();

for (boost::property_tree::ptree::iterator pos = pt.begin(); pos != pt.end();) {
std::cout << pos->first << std::endl;

if (pos->first.rfind("source ") == 0) {
std::cout << "got repo " << pos->first.substr(7) << std::endl;
std::string uri = pos->second.get<std::string>("uri");
std::cout << "uri " << uri << " " << uri.rfind("\"file://", 0) << std::endl;

std::shared_ptr<aklite::tuf::RepoSource> source;
if (uri.rfind("\"file://", 0) == 0) {
auto local_path = Utils::stripQuotes(uri).erase(0, strlen("file://"));
source = std::make_shared<aklite::tuf::LocalRepoSource>(pos->first, local_path);
} else {
source = std::make_shared<aklite::tuf::AkHttpsRepoSource>(pos->first, pos->second);
}
sources.push_back(source);
}

if (pos->first == "storage") {
storage_path = strip_quotes(pos->second.get<std::string>("path"));
}

++pos;
}
}

// Try individual fetch operations. sota.toml is not used
{
for (auto const& source : sources) {
Json::StreamWriterBuilder builder;
builder["indentation"] = " ";
try {
auto json = source->fetchRoot(1);
std::cout << json << std::endl;
} catch (std::runtime_error e) {
std::cout << e.what() << std::endl;
}
try {
auto json = source->fetchTimestamp();
std::cout << json << std::endl;
} catch (std::runtime_error e) {
std::cout << e.what() << std::endl;
}

try {
auto json = source->fetchSnapshot();
std::cout << json << std::endl;
} catch (std::runtime_error e) {
std::cout << e.what() << std::endl;
}
try {
auto json = source->fetchTargets();
// std::cout << json << std::endl;
} catch (std::runtime_error e) {
std::cout << e.what() << std::endl;
}
}
}

// Perform TUF refresh for each repo source, using libaktualizr implementation of Repo
{
aklite::tuf::AkRepo repo(storage_path);
for (auto const& source : sources) {
repo.updateMeta(source);
}

auto targets = repo.GetTargets();
for (auto const& target : targets) {
std::cout << target.Name() << " " << target.Sha256Hash() << std::endl;
}
}
}
59 changes: 3 additions & 56 deletions include/aktualizr-lite/api.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,65 +12,12 @@

#include "json/json.h"

#include "tuf/tuf.h"

class Config;
class LiteClient;

/**
* A high-level representation of a TUF Target in terms applicable to a
* FoundriesFactory.
*/
class TufTarget {
public:
explicit TufTarget() : name_{"unknown"} {}
TufTarget(std::string name, std::string sha256, int version, Json::Value custom)
: name_(std::move(name)), sha256_(std::move(sha256)), version_(version), custom_(std::move(custom)) {}

/**
* Return the TUF Target name. This is the key in the targets.json key/value
* singed.metadata dictionary.
*/
const std::string &Name() const { return name_; }
/**
* Return the sha256 OStree hash of the Target.
*/
const std::string &Sha256Hash() const { return sha256_; }
/**
* Return the FoundriesFactory CI build number or in TUF, custom.version.
*/
int Version() const { return version_; }

/**
* Return TUF custom data for a Target.
*/
const Json::Value &Custom() const { return custom_; }

/**
* Is this a known target in the Tuf manifest? There are two common causes
* to this situation:
* 1) A device has been re-registered (sql.db got wiped out) and the
* /var/sota/installed_versions file is missing. The device might
* running the correct target but the system isn't sure.
* 2) A device might be running a Target from a different tag it's not
* configured for. This means the Target isn't present in the targets.json
* this device is getting from the device-gateway.
*/
bool IsUnknown() const { return name_ == "unknown"; }

/**
* @brief Compares the given target with the other target
* @param other - the other target to compare with
* @return true if the targets match, otherwise false
*/
bool operator==(const TufTarget &other) const {
return other.name_ == name_ && other.sha256_ == sha256_ && other.version_ == version_;
}

private:
std::string name_;
std::string sha256_;
int version_{-1};
Json::Value custom_;
};
using aklite::tuf::TufTarget;

/**
* The response from an AkliteClient call to CheckIn()
Expand Down
118 changes: 118 additions & 0 deletions include/aktualizr-lite/tuf/tuf.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright (c) 2023 Foundries.io
// SPDX-License-Identifier: Apache-2.0

#ifndef AKLITE_TUF_TUF_H_
#define AKLITE_TUF_TUF_H_

#include <string>

#include "json/json.h"

/**
* This header contains the definition of an EXPERIMENTAL API for accessing TUF
* functionality. It allows for better isolation between Aktualizr-lite and
* libaktualizr, and better handling of multiple metadata sources for a same
* TUF repository (for example, mirrors or pre-fetched metadata files).
*/

namespace aklite::tuf {

/**
* Interface for a TUF repository metadata source.
*/
class RepoSource {
public:
virtual ~RepoSource() = default;
RepoSource(const RepoSource&) = delete;
RepoSource(const RepoSource&&) = delete;
RepoSource& operator=(const RepoSource&) = delete;
RepoSource& operator=(const RepoSource&&) = delete;

virtual std::string fetchRoot(int version) = 0;
virtual std::string fetchTimestamp() = 0;
virtual std::string fetchSnapshot() = 0;
virtual std::string fetchTargets() = 0;

protected:
RepoSource() = default;
};

/**
* A high-level representation of a TUF Target in terms applicable to a
* FoundriesFactory.
*/
class TufTarget {
public:
explicit TufTarget() : name_{"unknown"} {}
TufTarget(std::string name, std::string sha256, int version, Json::Value custom)
: name_(std::move(name)), sha256_(std::move(sha256)), version_(version), custom_(std::move(custom)) {}

/**
* Return the TUF Target name. This is the key in the targets.json key/value
* singed.metadata dictionary.
*/
const std::string& Name() const { return name_; }
/**
* Return the sha256 OStree hash of the Target.
*/
const std::string& Sha256Hash() const { return sha256_; }
/**
* Return the FoundriesFactory CI build number or in TUF, custom.version.
*/
int Version() const { return version_; }

/**
* Return TUF custom data for a Target.
*/
const Json::Value& Custom() const { return custom_; }

/**
* Is this a known target in the Tuf manifest? There are two common causes
* to this situation:
* 1) A device has been re-registered (sql.db got wiped out) and the
* /var/sota/installed_versions file is missing. The device might
* running the correct target but the system isn't sure.
* 2) A device might be running a Target from a different tag it's not
* configured for. This means the Target isn't present in the targets.json
* this device is getting from the device-gateway.
*/
bool IsUnknown() const { return name_ == "unknown"; }

/**
* @brief Compares the given target with the other target
* @param other - the other target to compare with
* @return true if the targets match, otherwise false
*/
bool operator==(const TufTarget& other) const {
return other.name_ == name_ && other.sha256_ == sha256_ && other.version_ == version_;
}

private:
std::string name_;
std::string sha256_;
int version_{-1};
Json::Value custom_;
};

/**
* Interface for a TUF specification engine, handling a single repository,
* fed through one or more consistent RepoSource instances.
*/
class Repo {
public:
virtual ~Repo() = default;
Repo(const Repo&) = delete;
Repo(const Repo&&) = delete;
Repo& operator=(const Repo&) = delete;
Repo& operator=(const Repo&&) = delete;

virtual std::vector<TufTarget> GetTargets() = 0;
virtual void updateMeta(std::shared_ptr<RepoSource> repo_src) = 0;

protected:
Repo() = default;
};

} // namespace aklite::tuf

#endif
11 changes: 9 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ set(SRC helpers.cc
target.cc
appengine.cc
cli/cli.cc
api.cc)
api.cc
tuf/akhttpsreposource.cc
tuf/localreposource.cc
tuf/akrepo.cc)

set(HEADERS helpers.h
storage/stat.h
Expand All @@ -40,7 +43,11 @@ set(HEADERS helpers.h
installer.h
exec.h
cli/cli.h
../include/aktualizr-lite/api.h)
tuf/akhttpsreposource.h
tuf/localreposource.h
tuf/akrepo.h
../include/aktualizr-lite/api.h
../include/aktualizr-lite/tuf/tuf.h)

if (BUILD_AKLITE_WITH_NERDCTL)
set(SRC ${SRC} containerd/client.cc containerd/engine.cc)
Expand Down
Loading

0 comments on commit 28b1224

Please sign in to comment.