Skip to content
This repository has been archived by the owner on May 21, 2024. It is now read-only.

Fix/ota 3060/check targets before download #1296

Merged
merged 7 commits into from
Sep 2, 2019

Conversation

pattivacek
Copy link
Collaborator

@pattivacek pattivacek commented Aug 14, 2019

This is still a work in progress, as there is additional work I'd like to include before merging. Also, test_aktualizr-lite is failing because of my changes and I'd like to discuss with @doanac how to reach a mutually satisfactory solution.

As to the changes, the biggest part is to recheck the Uptane metadata before downloading or installing a target. uptaneIteration() is now exclusively used during the check for updates, and it actually fetches the necessary metadata. uptaneOfflineIteration() is now exclusively used during downloading and installing to verify the integrity of the stored metadata. If a user then attempts to install a target that doesn't match the stored metadata, it will be rejected.

That stipulation required a lot of reworking of the Uptane tests, because several of them installed fake packages as a shortcut. That no longer works, so I had to expand several of the tests quite a bit. As a result, several of these tests are now rather redundant. We should re-evaluate the test cases in uptane_test and aktualizr_test and determine which are still meaningful and unique.

The other notable change is to only drop Director targets after a failed installation attempt. Otherwise this prevents being able to split updates into multiple individual calls to the Install API call. (A unit test is pending.) This still isn't my favorite fix for this problem, but in the short term, it's an improvement. [That was already merged in https://github.com//pull/1301.]

@pattivacek pattivacek force-pushed the fix/OTA-3060/check-targets-before-download branch 4 times, most recently from 9ae8adb to 46a07c2 Compare August 19, 2019 09:36
@pattivacek
Copy link
Collaborator Author

@doanac I'm still working on a few things for this PR, but one of the blockers is the aktualizr-lite test. Can you take a look and see if there's an easy solution for you? I don't want to accidentally break your expectations of how it should work.

@pattivacek pattivacek force-pushed the fix/OTA-3060/check-targets-before-download branch from 46a07c2 to 488dbd3 Compare August 20, 2019 15:17
@codecov-io
Copy link

codecov-io commented Aug 20, 2019

Codecov Report

Merging #1296 into master will decrease coverage by 0.21%.
The diff coverage is 83.33%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1296      +/-   ##
==========================================
- Coverage   80.03%   79.81%   -0.22%     
==========================================
  Files         177      177              
  Lines       10392    10475      +83     
==========================================
+ Hits         8317     8361      +44     
- Misses       2075     2114      +39
Impacted Files Coverage Δ
src/libaktualizr/package_manager/ostreemanager.h 100% <ø> (ø) ⬆️
src/libaktualizr/uptane/iterator.h 100% <ø> (ø) ⬆️
...rc/libaktualizr/package_manager/dockerappmanager.h 80% <ø> (ø) ⬆️
...ktualizr/package_manager/packagemanagerinterface.h 94.73% <ø> (ø) ⬆️
src/aktualizr_lite/main.cc 76.88% <100%> (+0.56%) ⬆️
src/libaktualizr/primary/sotauptaneclient.h 100% <100%> (ø) ⬆️
...tualizr/package_manager/packagemanagerinterface.cc 93.75% <100%> (+1.06%) ⬆️
...c/libaktualizr/package_manager/dockerappmanager.cc 68.91% <29.41%> (-13.34%) ⬇️
src/libaktualizr/package_manager/ostreemanager.cc 79.13% <80%> (+1.16%) ⬆️
src/libaktualizr/uptane/iterator.cc 88.99% <80%> (-0.64%) ⬇️
... and 6 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update eae7fdb...f22b669. Read the comment docs.

@pattivacek pattivacek force-pushed the fix/OTA-3060/check-targets-before-download branch from 488dbd3 to ac47217 Compare August 20, 2019 15:20
@doanac
Copy link
Collaborator

doanac commented Aug 20, 2019

@patrickvacek - i've fetched your branch and have re-created the failure. I'm traveling to ELC today, so it might be a couple of days before I get real time to debug.

One side comment I'd add to this PR: We could probably add a verifyTarget method to the docker-app-manager code that checks to validate the .dockerapp target is syntactically valid.

@@ -536,27 +527,33 @@ result::Download SotaUptaneClient::downloadImages(const std::vector<Uptane::Targ
result::Download result;
std::vector<Uptane::Target> downloaded_targets;

result::UpdateStatus update_status = checkUpdatesOffline(targets);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@patrickvacek - this looks to be the call that's breaking aktualizr-lite. The problem is that downloadImages was originally a code path that did not have any Uptane dependencies and checkUpdatesOffline is adding one in.

I haven't fully thought through this, but one way we could avoid the Uptane path is by making the downloadImage method public so that aktualizr-lite could call that directly and maybe the new pacman->verifyTarget API as well?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks to be the call that's breaking aktualizr-lite.

Agreed.

The problem is that downloadImages was originally a code path that did not have any Uptane dependencies and checkUpdatesOffline is adding one in.

Yes, it should've always been there, but we were lazy before.

one way we could avoid the Uptane path is by making the downloadImage method public so that aktualizr-lite could call that directly

Not my favorite solution, but I'm not sure how many options we have, and I think we've already set the precedent that it's okay for tools in the aktualizr repo to access SotaUptaneClient functions directly, but "normal" users should go through the Aktualizr class API.

maybe the new pacman->verifyTarget API as well

That is called automatically during installation. Why would you want to call it after downloading?

We could probably add a verifyTarget method to the docker-app-manager code that checks to validate the .dockerapp target is syntactically valid.

Cool, sounds like a good idea!

I'm traveling to ELC today, so it might be a couple of days before I get real time to debug.

That's okay, I still have some work to do on this PR and it's been slow progress due to competing priorities. Anyway, have fun!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe the new pacman->verifyTarget API as well

That is called automatically during installation. Why would you want to call it after downloading?

Now I understand; it's because you don't call uptaneInstall directly!

I wish it were possible for you to use the same API but just skip the parts you don't need for your package manager of choice. Is it also possible to mock the DockerApp targets like we do for the fake package manager?

@pattivacek pattivacek force-pushed the fix/OTA-3060/check-targets-before-download branch 2 times, most recently from 6c8b8b4 to c77a5e2 Compare August 23, 2019 16:03
@pattivacek pattivacek mentioned this pull request Aug 28, 2019
@pattivacek pattivacek force-pushed the fix/OTA-3060/check-targets-before-download branch 3 times, most recently from 709896b to 454a558 Compare August 29, 2019 14:40
@pattivacek
Copy link
Collaborator Author

pattivacek commented Aug 29, 2019

This is finally ready for review. The only thing still missing is just getting aktualizr-lite's do_update function to work. I've provided a stub for verifyTarget() but it's currently bypassed anyway.

@pattivacek pattivacek force-pushed the fix/OTA-3060/check-targets-before-download branch from a9c9da7 to f68ab08 Compare August 29, 2019 15:31
/* Target was found, but is larger than expected. */
kOversized,
/* Target was found, but hash did not match the metadata. */
kHashMismatch,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm thinking a kInvalid might be good for this enum as well. eg - a debian package or docker-app target content were invalid.

@doanac
Copy link
Collaborator

doanac commented Aug 29, 2019

Here's what I've changed to get things going:

diff --git a/src/aktualizr_lite/main.cc b/src/aktualizr_lite/main.cc
index a469beaf..09417e26 100644
--- a/src/aktualizr_lite/main.cc
+++ b/src/aktualizr_lite/main.cc
@@ -169,14 +169,11 @@ static std::unique_ptr<Uptane::Target> find_target(const std::shared_ptr<SotaUpt
 }
 
 static int do_update(SotaUptaneClient &client, INvStorage &storage, Uptane::Target &target) {
-  std::vector<Uptane::Target> targets{target};
-  auto result = client.downloadImages(targets);
-  if (result.status != result::DownloadStatus::kSuccess &&
-      result.status != result::DownloadStatus::kNothingToDownload) {
-    LOG_ERROR << "Unable to download update: " + result.message;
+  if (!client.downloadImage(target).first) {
     return 1;
   }
 
+  // TODO make pacman->verifyTarget something we can get to via client->
   auto iresult = client.PackageInstall(target);
   if (iresult.result_code.num_code == data::ResultCode::Numeric::kNeedCompletion) {
     LOG_INFO << "Update complete. Please reboot the device to activate";
diff --git a/src/libaktualizr/package_manager/dockerappmanager.cc b/src/libaktualizr/package_manager/dockerappmanager.cc
index da23dcfd..022f8c31 100644
--- a/src/libaktualizr/package_manager/dockerappmanager.cc
+++ b/src/libaktualizr/package_manager/dockerappmanager.cc
@@ -10,7 +10,7 @@ struct DockerApp {
         app_bin(config.docker_app_bin),
         compose_bin(config.docker_compose_bin) {}
 
-  bool render(const std::string &app_content) {
+  bool render(const std::string &app_content, bool persist) {
     auto bin = boost::filesystem::canonical(app_bin).string();
     Utils::writeFile(app_root / (name + ".dockerapp"), app_content);
     std::string cmd("cd " + app_root.string() + " && " + bin + " app render " + name);
@@ -22,7 +22,9 @@ struct DockerApp {
       LOG_ERROR << "Unable to run " << cmd << " output:\n" << yaml;
       return false;
     }
-    Utils::writeFile(app_root / "docker-compose.yml", yaml);
+    if (persist) {
+      Utils::writeFile(app_root / "docker-compose.yml", yaml);
+    }
     return true;
   }
 
@@ -101,7 +103,7 @@ data::InstallationResult DockerAppManager::install(const Uptane::Target &target)
     std::stringstream ss;
     ss << *storage_->openTargetFile(app_target);
     DockerApp dapp(app, config);
-    return dapp.render(ss.str()) && dapp.start();
+    return dapp.render(ss.str(), true) && dapp.start();
   };
   if (!iterate_apps(target, cb)) {
     return data::InstallationResult(data::ResultCode::Numeric::kInstallFailed, "Could not render docker app");
@@ -113,6 +115,16 @@ TargetStatus DockerAppManager::verifyTarget(const Uptane::Target &target) const
   if (target.IsOstree()) {
     return OstreeManager::verifyTarget(target);
   }
-  // TODO: verify DockerApp targets
+
+  auto cb = [this](const std::string &app, const Uptane::Target &app_target) {
+    LOG_INFO << "Verifying " << app << " -> " << app_target;
+    std::stringstream ss;
+    ss << *storage_->openTargetFile(app_target);
+    DockerApp dapp(app, config);
+    return dapp.render(ss.str(), false);
+  };
+  if (!iterate_apps(target, cb)) {
+    return TargetStatus::kInvalid;
+  }
   return TargetStatus::kGood;
 }
diff --git a/src/libaktualizr/package_manager/packagemanagerinterface.h b/src/libaktualizr/package_manager/packagemanagerinterface.h
index cbf4b3c4..4587fb5b 100644
--- a/src/libaktualizr/package_manager/packagemanagerinterface.h
+++ b/src/libaktualizr/package_manager/packagemanagerinterface.h
@@ -29,6 +29,8 @@ enum class TargetStatus {
   kOversized,
   /* Target was found, but hash did not match the metadata. */
   kHashMismatch,
+  /* Target was found and has valid metadata but the content is not suitable for the packagemanager */
+  kInvalid,
 };
 
 class PackageManagerInterface {
diff --git a/src/libaktualizr/primary/sotauptaneclient.h b/src/libaktualizr/primary/sotauptaneclient.h
index 84ea2c7c..28b11e60 100644
--- a/src/libaktualizr/primary/sotauptaneclient.h
+++ b/src/libaktualizr/primary/sotauptaneclient.h
@@ -43,6 +43,8 @@ class SotaUptaneClient {
   void addNewSecondary(const std::shared_ptr<Uptane::SecondaryInterface> &sec);
   result::Download downloadImages(const std::vector<Uptane::Target> &targets,
                                   const api::FlowControlToken *token = nullptr);
+  std::pair<bool, Uptane::Target> downloadImage(const Uptane::Target &target,
+                                                const api::FlowControlToken *token = nullptr);
   void reportPause();
   void reportResume();
   void sendDeviceData();
@@ -118,8 +120,6 @@ class SotaUptaneClient {
 
   bool putManifestSimple(const Json::Value &custom = Json::nullValue);
   bool getNewTargets(std::vector<Uptane::Target> *new_targets, unsigned int *ecus_count = nullptr);
-  std::pair<bool, Uptane::Target> downloadImage(const Uptane::Target &target,
-                                                const api::FlowControlToken *token = nullptr);
   void rotateSecondaryRoot(Uptane::RepositoryType repo, Uptane::SecondaryInterface &secondary);
   bool updateDirectorMeta();
   bool checkDirectorMetaOffline();

@pattivacek
Copy link
Collaborator Author

@doanac Thanks, that did the trick! Your code looks fine for now. We can refine it later.

There are now no blockers on this PR, it just needs review.

@@ -631,18 +627,20 @@ std::pair<bool, Uptane::Target> SotaUptaneClient::downloadImage(const Uptane::Ta
return {success, target};
}

bool SotaUptaneClient::uptaneIteration() {
bool SotaUptaneClient::uptaneIteration(std::vector<Uptane::Target> *targets, unsigned int *ecus_count) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we allowed the targets parameter to be null? It could help in niche cases, like tests and should be pretty cheap.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

uptaneIteration() is now exclusively used during the check for updates,
and it actually fetches the necessary metadata.

uptaneOfflineIteration() is now exclusively used during downloading and
installing to verify the integrity of the stored metadata. If a user
then attempts to install a target that doesn't match the stored
metadata, it will be rejected.

That stipulation required a lot of reworking of the Uptane tests,
because several of them installed fake packages as a shortcut. That no
longer works, so I had to expand several of the tests quite a bit. As a
result, several of these tests are now rather redundant. We should
re-evaluate the test cases in uptane_test and aktualizr_test and
determine which are still meaningful and unique.

Signed-off-by: Patrick Vacek <patrickvacek@gmail.com>
Specifically, if we already thought we downloaded all the metadata we
needed (while checking for updates), we shouldn't be downloading more
when re-checking the metadata before downloading or installing updates.

Signed-off-by: Patrick Vacek <patrickvacek@gmail.com>
Signed-off-by: Patrick Vacek <patrickvacek@gmail.com>
Tested at a low level with the fake package manager and at a slightly
higher level with OSTree.

Fixes OTA-2191.

Signed-off-by: Patrick Vacek <patrickvacek@gmail.com>
checkTargetFile() just checks that the stored metadata is consistent. We
need to go further and actually recheck the hash of the downloaded data.

Tested at a low level and high level for the fake package manager. Only
tested at a high level for OSTree, and because of the limitations of the
mocked sysroot, it's not as thorough as it could be.

Signed-off-by: Patrick Vacek <patrickvacek@gmail.com>
Currently only checks OSTree targets, but it's a start.

Signed-off-by: Patrick Vacek <patrickvacek@gmail.com>
Authored-by: Andy Doan <andy@foundries.io>
Signed-off-by: Patrick Vacek <patrickvacek@gmail.com>
@pattivacek pattivacek force-pushed the fix/OTA-3060/check-targets-before-download branch from db8f08f to f22b669 Compare September 2, 2019 12:50
@pattivacek pattivacek merged commit 60effa4 into master Sep 2, 2019
@pattivacek pattivacek deleted the fix/OTA-3060/check-targets-before-download branch September 2, 2019 14:55
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants