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  Uptane::SecondaryInterface::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  Uptane::SecondaryInterface::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, Uptane::Hash(Uptane::Hash::Type::kSha256, rev), 0, hardware_id, "", Delegation(),
154  custom);
155 
156  _uptane_repo.addTarget(rev, hardware_id, serial, "");
157  _uptane_repo.signTargets();
158 
159  return getCurrentMetadata();
160  }
161 
162  Uptane::RawMetaPack getCurrentMetadata() const {
163  Uptane::RawMetaPack metadata;
164 
165  boost::filesystem::load_string_file(_director_dir / "root.json", metadata.director_root);
166  boost::filesystem::load_string_file(_director_dir / "targets.json", metadata.director_targets);
167 
168  boost::filesystem::load_string_file(_imagerepo_dir / "root.json", metadata.image_root);
169  boost::filesystem::load_string_file(_imagerepo_dir / "timestamp.json", metadata.image_timestamp);
170  boost::filesystem::load_string_file(_imagerepo_dir / "snapshot.json", metadata.image_snapshot);
171  boost::filesystem::load_string_file(_imagerepo_dir / "targets.json", metadata.image_targets);
172 
173  return metadata;
174  }
175 
176  std::shared_ptr<std::string> getImageData(const std::string& targetname) const {
177  auto image_data = std::make_shared<std::string>();
178  boost::filesystem::load_string_file(_root_dir / targetname, *image_data);
179  return image_data;
180  }
181 
182  private:
183  TemporaryDirectory _root_dir;
184  boost::filesystem::path _director_dir{_root_dir / "repo/director"};
185  boost::filesystem::path _imagerepo_dir{_root_dir / "repo/repo"};
186  UptaneRepo _uptane_repo{_root_dir.Path(), "", ""};
187 };
188 
189 class SecondaryOstreeTest : public ::testing::Test {
190  public:
191  static const char* curOstreeRootfsRev(OstreeDeployment* ostree_depl) {
192  (void)ostree_depl;
193  return _sysroot->getDeploymentRev();
194  }
195 
196  static OstreeDeployment* curOstreeDeployment(OstreeSysroot* ostree_sysroot) {
197  (void)ostree_sysroot;
198  return _sysroot->getDeployment();
199  }
200 
201  static void setOstreeRootfsTemplate(const std::string& ostree_rootfs_template) {
202  _ostree_rootfs_template = ostree_rootfs_template;
203  }
204 
205  protected:
206  static void SetUpTestSuite() {
207  _treehub = std::make_shared<Treehub>("tests/sota_tools/treehub_server.py");
208  _sysroot = std::make_shared<OstreeRootfs>(_ostree_rootfs_template);
209  }
210 
211  static void TearDownTestSuite() {
212  _treehub.reset();
213  _sysroot.reset();
214  }
215 
216  protected:
218 
219  Uptane::RawMetaPack addDefaultTarget() { return addTarget(_treehub->curRev()); }
220 
221  Uptane::RawMetaPack addTarget(const std::string& rev = "", const std::string& hardware_id = "",
222  const std::string& serial = "") {
223  auto rev_to_apply = rev.empty() ? _treehub->curRev() : rev;
224  auto hw_id = hardware_id.empty() ? _secondary.hardwareID() : hardware_id;
225  auto serial_id = serial.empty() ? _secondary.serial() : serial;
226 
227  _uptane_repo.addOstreeRev(rev, hw_id, serial_id);
228 
229  return currentMetadata();
230  }
231 
232  Uptane::RawMetaPack currentMetadata() const { return _uptane_repo.getCurrentMetadata(); }
233 
234  std::string getCredsToSend() const {
235  std::map<std::string, std::string> creds_map = {
236  {"ca.pem", ""}, {"client.pem", ""}, {"pkey.pem", ""}, {"server.url", _treehub->url()}};
237 
238  std::stringstream creads_strstream;
239  Utils::writeArchive(creds_map, creads_strstream);
240 
241  return creads_strstream.str();
242  }
243 
244  Uptane::Hash treehubCurRevHash() const { return Uptane::Hash(Uptane::Hash::Type::kSha256, _treehub->curRev()); }
245  Uptane::Hash sysrootCurRevHash() const {
246  return Uptane::Hash(Uptane::Hash::Type::kSha256, _sysroot->getDeploymentRev());
247  }
248  const std::string& treehubCurRev() const { return _treehub->curRev(); }
249 
250  protected:
251  static std::shared_ptr<Treehub> _treehub;
252  static std::string _ostree_rootfs_template;
253  static std::shared_ptr<OstreeRootfs> _sysroot;
254 
255  AktualizrSecondaryWrapper _secondary{*_sysroot, *_treehub};
256  UptaneRepoWrapper _uptane_repo;
257 };
258 
259 std::shared_ptr<Treehub> SecondaryOstreeTest::_treehub{nullptr};
260 std::string SecondaryOstreeTest::_ostree_rootfs_template{"./build/ostree_repo"};
261 std::shared_ptr<OstreeRootfs> SecondaryOstreeTest::_sysroot{nullptr};
262 
263 TEST_F(SecondaryOstreeTest, fullUptaneVerificationInvalidRevision) {
264  EXPECT_TRUE(_secondary->putMetadata(addTarget("invalid-revision")));
265  EXPECT_FALSE(_secondary->sendFirmware(getCredsToSend()));
266 }
267 
268 TEST_F(SecondaryOstreeTest, fullUptaneVerificationInvalidHwID) {
269  EXPECT_FALSE(_secondary->putMetadata(addTarget("", "invalid-hardware-id", "")));
270 }
271 
272 TEST_F(SecondaryOstreeTest, fullUptaneVerificationInvalidSerial) {
273  EXPECT_FALSE(_secondary->putMetadata(addTarget("", "", "invalid-serial-id")));
274 }
275 
276 TEST_F(SecondaryOstreeTest, verifyUpdatePositive) {
277  // check the version reported in the manifest just after an initial boot
278  Uptane::Manifest manifest = _secondary->getManifest();
279  EXPECT_TRUE(manifest.verifySignature(_secondary->getPublicKey()));
280  EXPECT_EQ(manifest.installedImageHash(), sysrootCurRevHash());
281 
282  // send metadata and do their full Uptane verification
283  EXPECT_TRUE(_secondary->putMetadata(addDefaultTarget()));
284 
285  // emulate reboot to make sure that we can continue with an update installation after reboot
286  _secondary.reboot();
287 
288  // send and install firmware
289  EXPECT_TRUE(_secondary->sendFirmware(getCredsToSend()));
290  EXPECT_EQ(_secondary->install(treehubCurRev()), data::ResultCode::Numeric::kNeedCompletion);
291 
292  // check if the update was installed and pending
293  EXPECT_TRUE(_secondary.getPendingVersion().MatchHash(treehubCurRevHash()));
294  // manifest should still report the old version
295  manifest = _secondary->getManifest();
296  EXPECT_TRUE(manifest.verifySignature(_secondary->getPublicKey()));
297  EXPECT_EQ(manifest.installedImageHash(), sysrootCurRevHash());
298 
299  // emulate reboot
300  _sysroot->setNewDeploymentRev(treehubCurRev());
301  _secondary.reboot();
302 
303  // check if the version in the DB and reported in the manifest matches with the installed and applied one
304  EXPECT_FALSE(_secondary.getPendingVersion().IsValid());
305  EXPECT_TRUE(_secondary.getCurrentVersion().MatchHash(treehubCurRevHash()));
306  manifest = _secondary->getManifest();
307  EXPECT_TRUE(manifest.verifySignature(_secondary->getPublicKey()));
308  EXPECT_EQ(manifest.installedImageHash(), treehubCurRevHash());
309 
310  // emulate reboot
311  // check if the installed version persists after a reboot
312  _secondary.reboot();
313  EXPECT_FALSE(_secondary.getPendingVersion().IsValid());
314  EXPECT_TRUE(_secondary.getCurrentVersion().MatchHash(treehubCurRevHash()));
315  manifest = _secondary->getManifest();
316  EXPECT_TRUE(manifest.verifySignature(_secondary->getPublicKey()));
317  EXPECT_EQ(manifest.installedImageHash(), treehubCurRevHash());
318 }
319 
320 int main(int argc, char** argv) {
321  ::testing::InitGoogleTest(&argc, argv);
322 
323  if (argc != 2) {
324  std::cerr << "Error: " << argv[0] << " <ostree rootfs path>\n";
325  return EXIT_FAILURE;
326  }
327 
328  SecondaryOstreeTest::setOstreeRootfsTemplate(argv[1]);
329 
330  logger_init();
331  logger_set_threshold(boost::log::trivial::info);
332 
333  return RUN_ALL_TESTS();
334 }
335 
336 extern "C" OstreeDeployment* ostree_sysroot_get_booted_deployment(OstreeSysroot* ostree_sysroot) {
337  return SecondaryOstreeTest::curOstreeDeployment(ostree_sysroot);
338 }
339 
340 extern "C" const char* ostree_deployment_get_csum(OstreeDeployment* ostree_deployment) {
341  return SecondaryOstreeTest::curOstreeRootfsRev(ostree_deployment);
342 }
UptaneRepoWrapper
Definition: aktualizr_secondary_ostree_test.cc:145
OstreeRootfs
Definition: aktualizr_secondary_ostree_test.cc:53
AktualizrSecondaryConfig
Definition: aktualizr_secondary_config.h:40
Uptane::Hash
The hash of a file or TUF metadata.
Definition: tuf.h:209
SecondaryOstreeTest
Definition: aktualizr_secondary_ostree_test.cc:189
Uptane::RawMetaPack
Definition: tuf.h:535
Treehub
Definition: aktualizr_secondary_ostree_test.cc:16
Process
Definition: test_utils.h:19
TemporaryDirectory
Definition: utils.h:82
Uptane::Target
Definition: tuf.h:238
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:13
Delegation
Definition: repo.h:19