Hi everyone, I’m having a few related questions that we’re currently thinking about; what’s the recommended best practice for verifying a canister’s wasm programmatically? Is it calling the canister_status endpoint of the IC management canister and checking the module hash (as described here: Canister settings | Internet Computer)?
Is there a way to see who upgraded the canister in the past (like a log of upgrades)? And how could we ensure that only certain controllers are actually allowed to upgrade the canister code? Are there existing mechanisms for these?
By “packages” here in this sentence you are talking about system packages (as in .deb packages), right? As opposed to mops packages I mean (or crates for Rust).
Yes, you can see which principal made an upgrade. The history returned by canister_info from the management canister (same as in @Severin’s answer above) contains this information. But:
the call is not available from outside the IC, only to canisters
it only has 20 history entries, not all
Here’s a frontend that does the call for you and that can track more than 20 entires of history (if you ask it to track a canister id): https://history.ic0.info/
canister_status is only available to controllers of the canister. I believe you want to programmatically get the module hash of any third-party canister. The options are:
From canister code: As @Severin mentioned above, call the management canister function canister_info. But it only works from canister code, not from outside the IC.
I don’t know how to make building the assets reproducible. But I looked once into how comparing locally built assets against the assets in an asset canister. And the easiest way I found is to use the list method of an asset canister, which lists all files with their sha256 hashes, and then compare each file one by one. This approach seemed easier than to build a Merkle tree of hashes of files and then compare it to a root hash in the state tree.
Ok, please do this approach and let’s try it out. That’s also why I made my template repo, as a proposal to try it out. But please make sure the dependency on cpu architecture is taken care of (which nix alone does not).
I think this discussion has brought forward two opposing philosophies, both valid, and both should be tried out in practice.
Philosophy 1:
pin as much as possible, in particular all packages
argument that pinning everything is better for long-term verifiability
argument that pinning everything is better against supply-chain attacks
developer environment = validator environment
Philosophy 2:
analyse in detail what impacts build and what doesn’t
use the information gained, pin as little as possible
argument that pinning as little as possible is better for long-term verifiability
argument that pinning as little as possible is better against supply-chain attacks
Have you seen any best practice how to limit which controllers are allowed to upgrade the canister and which aren’t allowed to? We want to set this such that only one of the controllers can run the update (and thus change the code) and no one else.
We want to keep at least one other canister and the user as controllers but want to make sure that only one dedicated canister is the controller who can run code updates. Would adding a simple check in the preupgrade hook be a good idea or could this be achieved more elegantly?
We use it as a whitelisting mechanism such that only controllers can call certain canister endpoints and to make common controller calls, e.g. to get canister info like the cycle balance.
True, having access to the caller in preupgrade is the prerequisite for that idea to work, I had just assumed it has. I’ll look into this
Overall, I would avoid assigning principals as controllers if they shouldn’t have full access to the canister (e.g. perform upgrades). For example, controllers can also stop and wipe canisters, which I assume isn’t something you’d want to give to controllers that only need information like cycles.
Then should access-control the endpoints in canister code, like @sea-snake said, by maintaining an allow list in canister code, not rely on who is an IC-level controller.
For checking cycle balance the usual way is to add a black hole canister who reads the cycle balance as a proxy.
My point is that even if we perfectly standardize reproducible builds, the problems still exist:
Not many people attempt reproduction or code verification.
For example, in the past, we’ve seen numerous NNS proposals related to infrastructure upgrades where most lacked this kind of verification. Most people had no idea who performed the verification or what the results were, leaving them to vote blindly.
Even for the most critical IC infrastructure, this is already the case—let alone for other dApps, where the situation is even worse.