Aktualizr
C++ SOTA Client
All Classes Namespaces Files Functions Variables Enumerations Enumerator Pages
aktualizr_secondary_ostree_test.cc
1 #include <gtest/gtest.h>
2 
3 #include <ostree.h>
4 
5 #include "boost/algorithm/string/trim.hpp"
6 #include "boost/process.hpp"
7 
8 #include "logging/logging.h"
9 #include "test_utils.h"
10 
11 #include "aktualizr_secondary_ostree.h"
12 #include "update_agent_ostree.h"
13 #include "uptane_repo.h"
14 
15 class Treehub {
16  public:
17  Treehub(const std::string& server_path)
18  : port_(TestUtils::getFreePort()),
19  url_("http://127.0.0.1:" + port_),
20  process_(server_path, "-p", port_, "-d", root_dir_.PathString(), "-s0.5", "--create") {
21  TestUtils::waitForServer(url() + "/");
22  auto rev_process = Process("ostree").run({"rev-parse", "--repo", root_dir_.PathString(), "master"});
23  EXPECT_EQ(std::get<0>(rev_process), 0) << std::get<2>(rev_process);
24  cur_rev_ = std::get<1>(rev_process);
25  boost::trim_right_if(cur_rev_, boost::is_any_of(" \t\r\n"));
26 
27  LOG_INFO << "Treehub is running on: " << port_ << " current revision: " << cur_rev_;
28  }
29 
30  ~Treehub() {
31  process_.terminate();
32  process_.wait_for(std::chrono::seconds(10));
33  if (process_.running()) {
34  LOG_ERROR << "Failed to stop Treehub server";
35  } else {
36  LOG_INFO << "Treehub server has been stopped";
37  }
38  }
39 
40  public:
41  const std::string& url() const { return url_; }
42  const std::string& curRev() const { return cur_rev_; }
43 
44  private:
45  TemporaryDirectory root_dir_;
46  const std::string port_;
47  const std::string url_;
48  boost::process::child process_;
49  std::string cur_rev_;
50 };
51 
52 class OstreeRootfs {
53  public:
54  OstreeRootfs(const std::string& rootfs_template) {
55  auto sysroot_copy = Process("cp").run({"-r", rootfs_template, getPath().c_str()});
56  EXPECT_EQ(std::get<0>(sysroot_copy), 0) << std::get<1>(sysroot_copy);
57 
58  auto deployment_rev = Process("ostree").run(
59  {"rev-parse", std::string("--repo"), getPath().string() + "/ostree/repo", "generate-remote/generated"});
60 
61  EXPECT_EQ(std::get<0>(deployment_rev), 0) << std::get<2>(deployment_rev);
62 
63  rev_ = std::get<1>(deployment_rev);
64  boost::trim_right_if(rev_, boost::is_any_of(" \t\r\n"));
65 
66  deployment_.reset(ostree_deployment_new(0, getOSName(), getDeploymentRev(), getDeploymentSerial(),
67  getDeploymentRev(), getDeploymentSerial()));
68  }
69 
70  const boost::filesystem::path& getPath() const { return sysroot_dir_; }
71  const char* getDeploymentRev() const { return rev_.c_str(); }
72  int getDeploymentSerial() const { return 0; }
73  const char* getOSName() const { return os_name_.c_str(); }
74 
75  OstreeDeployment* getDeployment() const { return deployment_.get(); }
76  void setNewDeploymentRev(const std::string& new_rev) { rev_ = new_rev; }
77 
78  private:
79  struct OstreeDeploymentDeleter {
80  void operator()(OstreeDeployment* e) const { g_object_unref(reinterpret_cast<gpointer>(e)); }
81  };
82 
83  private:
84  const std::string os_name_{"dummy-os"};
85  TemporaryDirectory tmp_dir_;
86  boost::filesystem::path sysroot_dir_{tmp_dir_ / "ostree-rootfs"};
87  std::string rev_;
88  std::unique_ptr<OstreeDeployment, OstreeDeploymentDeleter> deployment_;
89 };
90 
92  public:
93  AktualizrSecondaryWrapper(const OstreeRootfs& sysroot, const Treehub& treehub) {
94  // OSTree update
95 
96  config_.pacman.type = PACKAGE_MANAGER_OSTREE;
97  config_.pacman.os = sysroot.getOSName();
98  config_.pacman.sysroot = sysroot.getPath();
99  config_.pacman.ostree_server = treehub.url();
100 
101  config_.bootloader.reboot_sentinel_dir = storage_dir_.Path();
102  config_.bootloader.reboot_sentinel_name = "need_reboot";
103 
104  config_.storage.path = storage_dir_.Path();
105  config_.storage.type = StorageType::kSqlite;
106 
107  storage_ = INvStorage::newStorage(config_.storage);
108  secondary_ = std::make_shared<AktualizrSecondaryOstree>(config_, storage_);
109  secondary_->initialize();
110  }
111 
112  public:
113  std::shared_ptr<AktualizrSecondaryOstree>& operator->() { return secondary_; }
114 
115  Uptane::Target getPendingVersion() const { return getVersion().first; }
116 
117  Uptane::Target getCurrentVersion() const { return getVersion().second; }
118 
119  std::pair<Uptane::Target, Uptane::Target> getVersion() const {
120  boost::optional<Uptane::Target> current_target;
121  boost::optional<Uptane::Target> pending_target;
122 
123  storage_->loadInstalledVersions(secondary_->serial().ToString(), &current_target, &pending_target);
124 
125  return std::make_pair(!pending_target ? Uptane::Target::Unknown() : *pending_target,
126  !current_target ? Uptane::Target::Unknown() : *current_target);
127  }
128 
129  std::string hardwareID() const { return secondary_->hwID().ToString(); }
130 
131  std::string serial() const { return secondary_->serial().ToString(); }
132 
133  void reboot() {
134  boost::filesystem::remove(storage_dir_ / config_.bootloader.reboot_sentinel_name);
135  secondary_ = std::make_shared<AktualizrSecondaryOstree>(config_, storage_);
136  secondary_->initialize();
137  }
138 
139  private:
140  TemporaryDirectory storage_dir_;
141  AktualizrSecondaryConfig config_;
142  std::shared_ptr<INvStorage> storage_;
143  std::shared_ptr<AktualizrSecondaryOstree> secondary_;
144 };
145 
147  public:
148  UptaneRepoWrapper() { uptane_repo_.generateRepo(KeyType::kED25519); }
149 
150  Metadata addOstreeRev(const std::string& rev, const std::string& hardware_id, const std::string& serial) {
151  // it makes sense to add 'addOstreeImage' to UptaneRepo interface/class uptane_repo.h
152  auto custom = Json::Value();
153  custom["targetFormat"] = "OSTREE";
154  uptane_repo_.addCustomImage(rev, Hash(Hash::Type::kSha256, rev), 0, hardware_id, "", Delegation(), custom);
155 
156  uptane_repo_.addTarget(rev, hardware_id, serial, "");
157  uptane_repo_.signTargets();
158 
159  return getCurrentMetadata();
160  }
161 
162  Uptane::MetaBundle getCurrentMetadata() const {
163  Uptane::MetaBundle meta_bundle;
164  std::string metadata;
165 
166  boost::filesystem::load_string_file(director_dir_ / "root.json", metadata);
167  meta_bundle.insert({std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Root()), std::move(metadata)});
168  boost::filesystem::load_string_file(director_dir_ / "targets.json", metadata);
169  meta_bundle.insert(
170  {std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Targets()), std::move(metadata)});
171 
172  boost::filesystem::load_string_file(imagerepo_dir_ / "root.json", metadata);
173  meta_bundle.insert({std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Root()), std::move(metadata)});
174  boost::filesystem::load_string_file(imagerepo_dir_ / "timestamp.json", metadata);
175  meta_bundle.insert(
176  {std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Timestamp()), std::move(metadata)});
177  boost::filesystem::load_string_file(imagerepo_dir_ / "snapshot.json", metadata);
178  meta_bundle.insert(
179  {std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Snapshot()), std::move(metadata)});
180  boost::filesystem::load_string_file(imagerepo_dir_ / "targets.json", metadata);
181  meta_bundle.insert({std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Targets()), std::move(metadata)});
182 
183  return meta_bundle;
184  }
185 
186  std::shared_ptr<std::string> getImageData(const std::string& targetname) const {
187  auto image_data = std::make_shared<std::string>();
188  boost::filesystem::load_string_file(root_dir_ / targetname, *image_data);
189  return image_data;
190  }
191 
192  private:
193  TemporaryDirectory root_dir_;
194  boost::filesystem::path director_dir_{root_dir_ / "repo/director"};
195  boost::filesystem::path imagerepo_dir_{root_dir_ / "repo/repo"};
196  UptaneRepo uptane_repo_{root_dir_.Path(), "", ""};
197 };
198 
199 class SecondaryOstreeTest : public ::testing::Test {
200  public:
201  static const char* curOstreeRootfsRev(OstreeDeployment* ostree_depl) {
202  (void)ostree_depl;
203  return sysroot_->getDeploymentRev();
204  }
205 
206  static OstreeDeployment* curOstreeDeployment(OstreeSysroot* ostree_sysroot) {
207  (void)ostree_sysroot;
208  return sysroot_->getDeployment();
209  }
210 
211  static void setOstreeRootfsTemplate(const std::string& ostree_rootfs_template) {
212  ostree_rootfs_template_ = ostree_rootfs_template;
213  }
214 
215  protected:
216  static void SetUpTestSuite() {
217  treehub_ = std::make_shared<Treehub>("tests/sota_tools/treehub_server.py");
218  sysroot_ = std::make_shared<OstreeRootfs>(ostree_rootfs_template_);
219  }
220 
221  static void TearDownTestSuite() {
222  treehub_.reset();
223  sysroot_.reset();
224  }
225 
226  protected:
228 
229  Uptane::MetaBundle addDefaultTarget() { return addTarget(treehub_->curRev()); }
230 
231  Uptane::MetaBundle addTarget(const std::string& rev = "", const std::string& hardware_id = "",
232  const std::string& serial = "") {
233  auto rev_to_apply = rev.empty() ? treehub_->curRev() : rev;
234  auto hw_id = hardware_id.empty() ? secondary_.hardwareID() : hardware_id;
235  auto serial_id = serial.empty() ? secondary_.serial() : serial;
236 
237  uptane_repo_.addOstreeRev(rev, hw_id, serial_id);
238 
239  return currentMetadata();
240  }
241 
242  Uptane::MetaBundle currentMetadata() const { return uptane_repo_.getCurrentMetadata(); }
243 
244  std::string getCredsToSend() const {
245  std::map<std::string, std::string> creds_map = {
246  {"ca.pem", ""}, {"client.pem", ""}, {"pkey.pem", ""}, {"server.url", treehub_->url()}};
247 
248  std::stringstream creads_strstream;
249  Utils::writeArchive(creds_map, creads_strstream);
250 
251  return creads_strstream.str();
252  }
253 
254  Hash treehubCurRevHash() const { return Hash(Hash::Type::kSha256, treehub_->curRev()); }
255  Hash sysrootCurRevHash() const { return Hash(Hash::Type::kSha256, sysroot_->getDeploymentRev()); }
256  const std::string& treehubCurRev() const { return treehub_->curRev(); }
257 
258  protected:
259  static std::shared_ptr<Treehub> treehub_;
260  static std::string ostree_rootfs_template_;
261  static std::shared_ptr<OstreeRootfs> sysroot_;
262 
263  AktualizrSecondaryWrapper secondary_{*sysroot_, *treehub_};
264  UptaneRepoWrapper uptane_repo_;
265 };
266 
267 std::shared_ptr<Treehub> SecondaryOstreeTest::treehub_{nullptr};
268 std::string SecondaryOstreeTest::ostree_rootfs_template_{"./build/ostree_repo"};
269 std::shared_ptr<OstreeRootfs> SecondaryOstreeTest::sysroot_{nullptr};
270 
271 TEST_F(SecondaryOstreeTest, fullUptaneVerificationInvalidRevision) {
272  EXPECT_TRUE(secondary_->putMetadata(addTarget("invalid-revision")).isSuccess());
273  EXPECT_FALSE(secondary_->downloadOstreeUpdate(getCredsToSend()).isSuccess());
274 }
275 
276 TEST_F(SecondaryOstreeTest, fullUptaneVerificationInvalidHwID) {
277  EXPECT_FALSE(secondary_->putMetadata(addTarget("", "invalid-hardware-id", "")).isSuccess());
278 }
279 
280 TEST_F(SecondaryOstreeTest, fullUptaneVerificationInvalidSerial) {
281  EXPECT_FALSE(secondary_->putMetadata(addTarget("", "", "invalid-serial-id")).isSuccess());
282 }
283 
284 TEST_F(SecondaryOstreeTest, verifyUpdatePositive) {
285  // check the version reported in the manifest just after an initial boot
286  Uptane::Manifest manifest = secondary_->getManifest();
287  EXPECT_TRUE(manifest.verifySignature(secondary_->publicKey()));
288  EXPECT_EQ(manifest.installedImageHash(), sysrootCurRevHash());
289 
290  // send metadata and do their full Uptane verification
291  EXPECT_TRUE(secondary_->putMetadata(addDefaultTarget()).isSuccess());
292 
293  // emulate reboot to make sure that we can continue with an update installation after reboot
294  secondary_.reboot();
295 
296  EXPECT_TRUE(secondary_->downloadOstreeUpdate(getCredsToSend()).isSuccess());
297  EXPECT_EQ(secondary_->install().result_code.num_code, data::ResultCode::Numeric::kNeedCompletion);
298 
299  // check if the update was installed and pending
300  EXPECT_TRUE(secondary_.getPendingVersion().MatchHash(treehubCurRevHash()));
301  // manifest should still report the old version
302  manifest = secondary_->getManifest();
303  EXPECT_TRUE(manifest.verifySignature(secondary_->publicKey()));
304  EXPECT_EQ(manifest.installedImageHash(), sysrootCurRevHash());
305 
306  // emulate reboot
307  sysroot_->setNewDeploymentRev(treehubCurRev());
308  secondary_.reboot();
309 
310  // check if the version in the DB and reported in the manifest matches with the installed and applied one
311  EXPECT_FALSE(secondary_.getPendingVersion().IsValid());
312  EXPECT_TRUE(secondary_.getCurrentVersion().MatchHash(treehubCurRevHash()));
313  manifest = secondary_->getManifest();
314  EXPECT_TRUE(manifest.verifySignature(secondary_->publicKey()));
315  EXPECT_EQ(manifest.installedImageHash(), treehubCurRevHash());
316 
317  // emulate reboot
318  // check if the installed version persists after a reboot
319  secondary_.reboot();
320  EXPECT_FALSE(secondary_.getPendingVersion().IsValid());
321  EXPECT_TRUE(secondary_.getCurrentVersion().MatchHash(treehubCurRevHash()));
322  manifest = secondary_->getManifest();
323  EXPECT_TRUE(manifest.verifySignature(secondary_->publicKey()));
324  EXPECT_EQ(manifest.installedImageHash(), treehubCurRevHash());
325 }
326 
327 int main(int argc, char** argv) {
328  ::testing::InitGoogleTest(&argc, argv);
329 
330  if (argc != 2) {
331  std::cerr << "Error: " << argv[0] << " <ostree rootfs path>\n";
332  return EXIT_FAILURE;
333  }
334 
335  SecondaryOstreeTest::setOstreeRootfsTemplate(argv[1]);
336 
337  logger_init();
338  logger_set_threshold(boost::log::trivial::info);
339 
340  return RUN_ALL_TESTS();
341 }
342 
343 extern "C" OstreeDeployment* ostree_sysroot_get_booted_deployment(OstreeSysroot* ostree_sysroot) {
344  return SecondaryOstreeTest::curOstreeDeployment(ostree_sysroot);
345 }
346 
347 extern "C" const char* ostree_deployment_get_csum(OstreeDeployment* ostree_deployment) {
348  return SecondaryOstreeTest::curOstreeRootfsRev(ostree_deployment);
349 }
UptaneRepoWrapper
Definition: aktualizr_secondary_ostree_test.cc:146
Hash
The Hash class The hash of a file or Uptane metadata.
Definition: types.h:159
OstreeRootfs
Definition: aktualizr_secondary_ostree_test.cc:52
AktualizrSecondaryConfig
Definition: aktualizr_secondary_config.h:35
SecondaryOstreeTest
Definition: aktualizr_secondary_ostree_test.cc:199
Treehub
Definition: aktualizr_secondary_ostree_test.cc:15
Process
Definition: test_utils.h:19
TemporaryDirectory
Definition: utils.h:82
Uptane::Target
Definition: types.h:379
Metadata
Definition: aktualizr_secondary_metadata.h:7
UptaneRepo
Definition: uptane_repo.h:7
AktualizrSecondaryWrapper
Definition: aktualizr_secondary_ostree_test.cc:91
Uptane::Manifest
Definition: types.h:448
Delegation
Definition: repo.h:19