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