-
Notifications
You must be signed in to change notification settings - Fork 305
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ostree-prepare-root: Validate ed25519 signatures when requested #2921
Conversation
Feedback from @cgwalters: We can just read the signatures from the commit object, there is no need for the separate signature file. Especially now that prepare-root links to glib and can easily use gvariant. |
Right. Don't get me wrong, I know the existing GPG signature stuff is heavyweight. And AFAIK some users just want to avoid GPL3 entirely that motivated the ed25519 signatures. But...hopefully the existing ed25519 stuff is light enough for those who want that. For CoreOS today our initramfs is so huge already that adding gpg probably wouldn't be too bad...though I haven't evaluated it. As you mentioned before we have the libcurl/libsoup dependencies (and libcurl is transitively big) but we could likely compile just the signature stuff into a new static library linked into ostree-prepare-root. |
I don't necessarily disagree with gpg use due to the code size alone, but rather due to the complexity of its key/crypto mechanisms and ecosystem. And it really is a total pain in the butt to work with. ed25519 is just super simple, doing one thing and just one thing, which is the thing we want. Also, again, I'd like to emphasize that the signature of the commit itself (used during pull, and is the same for all releases from that software published, with the private key hidden deep in some signing hardware) and the signature of the composefs image (new one generated each build, never reused, with secure key easy and cheap to generate/use and then delete) are very different things. |
On Thu, Jul 6, 2023, at 5:49 PM, Alexander Larsson wrote:
I don't necessarily disagree with gpg use due to the code size alone,
but rather due to the complexity of its key/crypto mechanisms and
ecosystem. And it really is a total pain in the but to work with.
ed25519 is just super simple, doing one thing and just one thing, which
is the thing we want.
Agreed on this.
Also, again, I'd like to emphasize that the signature of the commit
itself (used during pull, and is the same for all releases from that
software published, with the private key hidden deep in some signing
hardware) and the signature of the composefs image (new one generated
each build, never reused, with secure key easy and cheap to
generate/use and then delete) are very different things.
Hmm. Ok interesting. So you are seeing this key as simply a way to authenticate a chain from exactly one initramfs/uki to exactly one rootfs, right?
It’s something we could do without asymmetric crypto at all, ie we could just use a sha256 hash, except for the circular dependency of having the uki/initramfs shipped in the root file system tree and ostree commit.
But…that’s solvable in theory by splitting out the kernel/initramfs into something shipped as a distinct unit but lifecycled together.
Hmm. Actually, couldn’t we just omit /usr/lib/modules/$ver/vmlinuz from the composefs digest computation? And also we don’t see it at runtime too. Then in a UKI scenario, we can embed the desired rootfs composefs digest in it.
Note a consequence of “strict root and uki binding” (whether implemented via asymmetric crypto or a digest) is that updating the rootfs requires updating the kernel image too.
Whereas reusing the pull signatures (or I guess more generally signing the composefs digest with a key reused across builds) allows us to avoid changing the kernel binary for pure userspace changes. This seems like a somewhat desirable property, right?
|
Yes. I would like to tie initrd to ostree commit tightly. It could be a security problem if you can mix and match kernels and userspace. For example, you could boot a new signed kernel with and old unsecure userspace. I agree that if we ignore the circular dependency we wouldn't have to use a public key. But we can't just ignore that. The reason for the public key is that we can pre-generate it and store the in the initrd without cycles. I've been trying to avoid this in various ways, but I think it is really the easiest and best way. In theory, if strip out the initrd file when we create the composefs image, we could then embed a hash in it and avoid the cycle, but computing such an initrd sound like a gigantic hairball. You have to have the complete commit ready and then go back and tweak the initrd. As for this tying the initrd/kernel with the userspace. Yes, that is a problem (and it also happens if we do the above digest approach). It is a slight cost, yes, but in practice I think it is a cost you must bear if you want a secureboot approach. Allowing users to mix and match between kernels and userspace is just not secure enough. |
Yes, though there's many things one could do here; e.g. we still include the kernel version in the ostree commit metadata, so we can verify before entering that we're booted with the kernel we expect. Also in practice many generic use cases have important loadable modules, and that will also restrict things to userspace trees with exact matching kernel versions. Hmm...I'm tempted to add the kver validation just on general principle.
I don't see what's so bad about that; an important thing here is to s/commit/filesystem tree/ - the ostree commit is really just metadata that we generate at the end. For the split initramfs case because the Linux kernel will accept a chain of cpio blobs it's just something like Anyways backing up a bit...I'm not opposed to these patches and I think with a bit of cleanup it could make sense as an option. But I don't think it's the only viable option at all, so perhaps we call this "transient composefs digest signatures" or so? |
One goal I had in mind with re-using the pull signatures is that I think this is the model we'll need to take for integrating with the OCI ecosystem anyways in general (so this relates more to containers/composefs#151 ) - basically we inject the computed composefs digest into the manifest, and then it can be covered by sigstore/cosign. This is just very clear and easy to explain and test and debug. Which is also true of ostree using the pull signatures, however in the ostree case we do have the more special nature of the kernel/initramfs dance and chaining from SB. |
In looking at this quckly; omitting the kernel/initramfs from ostree+composefs would need to be done in both |
That is the easy part though. The hard part is that during ostree commit (or as a step just before it) you have to:
This stuff is hard, as it is very specific to how the UKI and initrd looks, how it is buillt and how it is signed. Especially the uefi or aboot UKI signing is very complex, due to the security apparatus around it. All of this goes away if we use a per-build keypair, and it also fixes the security issues around kernel/userspace mix-and-match. If that is not fixed like that we have to add a lot of custom code around signatures so that we can handle revoking, burning hardware fuses for old keys, etc. I'm pretty sure this is unworkable. |
What I think makes most sense is to:
|
bd99da2
to
a9fc476
Compare
Updated to new approach, which is much smaller. Will start doing some testing. |
This new version has the commits from #2922 as well |
We already landed a handy API for that.
Sure, but in this proposed "transient linkage key" flow, these steps are still required, right? It's just that you can do them asynchronously/decoupled.
I don't think there's any "re-inject". We don't need an The flow can be:
Assuming I'm correct in replacing "this" with "[injecting via digest]" here - I don't agree it's unworkable. I'm tempted to Just Do It in rpm-ostree by default. (Though practically speaking we need to enable composefs support in Fedora at least by default...I can't think of a big reason not to?) I do agree that "transient linkage key" has some stronger semantics and some potential build process advantages.
But I want to dig into this more...you're arguing that "transient linkage key" is easier for builders who may have special processes around the UKI - but the transient key still must be injected and built into it. Let's assume there's a distinct "build server/service" for this that operates over RPC. Kind of like how Fedora's robosignatory works.
You're arguing there's two "build" processes right? Let's call them "build-uki" and "build-userspace". This then is "build-uki", right? So the output of build-uki actually returns two things:
OK, sure.
Yes, OK. (Though ISTM it would perhaps make sense to validate both keys? Just...on general principle?)
(And pass it to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK cool I like the direction here. I have some refactoring requests which I think will make longer term maintenance easier. I'd be happy to try doing them if you prefer!
errx (EXIT_FAILURE, "Signature validation requested, but no signatures in commit"); | ||
|
||
g_autoptr (GBytes) commit_data = g_variant_get_data_as_bytes (commit); | ||
if (!validate_signature (commit_data, signatures, (guchar *)pubkey, pubkey_size)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see some cases where we want to revalidate the "pull signature" on the commit before mounting. So in theory this could be a separate option; perhaps something like ostree config set sysroot.verify-signatures-before-mount true
or so?
But we'd forcibly require that to happen in the composefs-signed case.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I'd like to do this still. But I also think we can probably just merge this and do followups.
Feel free to try it, as I'm on my way out. Otherwise I'll continue with that on monday. |
Ok, this discussion is becoming a bit long and hard to follow. I'll do some thread-sniping below, but overall I guess I come from a slightly different perspective, where I'm working both with an existing osbuild-based workflow (not fcos), as well as an additional different UKI format (android boot, not UEFI). I think that colors my perception of this a bit.
What do you mean here?
Yes, independent of anything else, we do need a way to allow somewhere during the build to essentially call out to some flexible signature mechanism. However, I would like that to be as independent from ostree as possible, so it is easy to integrate with the build, both when building ostree and for non-ostree images.
Who would compute this digest? Its kinda tricky as it has to produce exactly the same metadata as the later commit. For example, you can't just run it on the on-disk userspace filesystem tree without a deep knowledge of how that would be converted to ostree metadata (canonicalization, timestamps, commit filter options, etc).
There is no need for the "transient" key to have the same special processes around it. It can be created locally as a regular file on the machine during the build/compose, and then deleted at the end. This is very similar to how the kernel rpm build generates a throwaway key to sign the kernel modules, and then builds the public part into the kernel image itself.
I imagine the setup to be ordered differently. The key isn't really generated by build-uki, but already exists when it is invoked. Basically, before you even do any userspace or initrd building you generate a key-pair. Then you put the public key in your userspace rootfs (say as |
Yeah, definitely true. Of these, I think SELinux labeling is the hardest; ostree wants to own this. But...we could move it into |
a9fc476
to
1483595
Compare
@alexlarsson OK only compile tested so far...but I think this will work, WDYT?
|
There's no reason to have these distinct really. If we're using libsodium, we want it in the same places we're using openssl. Prep for further refactoring.
1483595
to
bd9b864
Compare
OK I updated this with a prep cleanup commit, a commit which introduces |
1898524
to
dccf643
Compare
These looks good to me, so i squashed them. Will do some testing today. |
dccf643
to
d764186
Compare
Can you also add at least a stub to |
else if (strncmp (ot_composefs, "signed=", strlen ("signed=")) == 0) | ||
{ | ||
composefs_mode = OSTREE_COMPOSEFS_MODE_SIGNED; | ||
composefs_pubkey = ot_composefs + strlen ("signed="); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having the pubkey on the kernel commandline is probably fine, but longer term it feels like what we want instead is to have this baked into the initramfs as e.g. /usr/lib/ostree/rootfs-initramfs-binding-key.pub
or so?
Basically in general I think kernel arguments should primarily be for things interpreted by the Linux kernel. (Of course the historical use case of passing arguments to init (systemd) will probably always be there)
And this is actually argument that even ostree=
should work this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean, that instead of checking the commandline we just check for the existence of this file in the initrd, and if it is there we assume we need to validate against it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right.
BTW one thing that might be nice here is to write the state into /run/ostree-booted
- we could make that an a{sv}
and write e.g. composefs = "signed"
into it and that way e.g. ostree admin status
and equivalent can show that the composefs was handled in a signed way (instead of these tools parsing kernel arguments, and also so they're explicitly verifying the post-execution state instead of the configuration-to-be-applied).
I edited the PR title to be a bit clearer, can you do the same for the commit message? (A minor procedural note; I'd recommend using Github PR "draft" state instead of adding "WIP:" into the title, because I've seen more than one PR merge with the commit message changed but the PR title still in "WIP:" state, and that PR title ends up in the merge commit message forever 😄) |
Thanks, I wasn't aware of how that worked. That's actually a very strong comparison. In fact the more I think about it, the more this is basically extending the kernel module signatures to the rest of the userspace filesystem tree. Right? (If one has an LSM configured and enforcing such that kmods can only be loaded from a filesystem tree with verified integrity such as dm-verity or composefs, then we don't need the distinct kernel key at all, right?) |
Yeah, thats probably the right way to see it.
You need a slightly tighter rule. Like you can't allow anyone to just download and loopback mount some random dm-verity image they control. It has to be in the trusted path. But, other than that, yeah. |
(Oh, and you have to also allow modules from the initrd obviously) |
d764186
to
912fdee
Compare
This form of signatures has been (build-time-optionally) supported since ostree 2020.4 as an alternative to the old gpg signatures. With the current work on composefs[1] they are becomming more important, as they will allow verification of the commit (and thus the composefs image) during boot, giving us a full trusted boot chain all the way into the ostree userspace. Note: `ostree sign` used to require libsodium and was thus disabled in e.g. the Fedora build of ostree. However, recently[2] it is also supported with openssl, which will let it be more widely used. [1] ostreedev/ostree#2921 [2] ostreedev/ostree#2922
Ok, I managed to boot an automotive system with this: The resulting initrd boot debug output:
|
This form of signatures has been (build-time-optionally) supported since ostree 2020.4 as an alternative to the old gpg signatures. With the current work on composefs[1] they are becomming more important, as they will allow verification of the commit (and thus the composefs image) during boot, giving us a full trusted boot chain all the way into the ostree userspace. Note: `ostree sign` used to require libsodium and was thus disabled in e.g. the Fedora build of ostree. However, recently[2] it is also supported with openssl, which will let it be more widely used. [1] ostreedev/ostree#2921 [2] ostreedev/ostree#2922
This form of signatures has been (build-time-optionally) supported since ostree 2020.4 as an alternative to the old gpg signatures. With the current work on composefs[1] they are becomming more important, as they will allow verification of the commit (and thus the composefs image) during boot, giving us a full trusted boot chain all the way into the ostree userspace. Note: `ostree sign` used to require libsodium and was thus disabled in e.g. the Fedora build of ostree. However, recently[2] it is also supported with openssl, which will let it be more widely used. [1] ostreedev/ostree#2921 [2] ostreedev/ostree#2922
This form of signatures has been (build-time-optionally) supported since ostree 2020.4 as an alternative to the old gpg signatures. With the current work on composefs[1] they are becomming more important, as they will allow verification of the commit (and thus the composefs image) during boot, giving us a full trusted boot chain all the way into the ostree userspace. Note: `ostree sign` used to require libsodium and was thus disabled in e.g. the Fedora build of ostree. However, recently[2] it is also supported with openssl, which will let it be more widely used. [1] ostreedev/ostree#2921 [2] ostreedev/ostree#2922
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK yeah, there's stuff I'd like to change but since all this is still experimental and to preserve momentum let's get this in now.
This will contain logic shared between ostree-prepare-root and libostree-1.so. It will just link to libgio.so, so as to avoid pulling in e.g. libcurl and other things. In other words, `ostree-prepare-root` will not link to `libostree-1.so`, but will pull in just what it needs from this library.
If requested, by specifying ot-composefs=signed=/path/to/pub.key then the commit object is validated against the specified ed25519 public key, and if valid, the composefs digest from the commit object is used to ensure we boot the right digest.
912fdee
to
c29f419
Compare
Ah yes, |
This form of signatures has been (build-time-optionally) supported since ostree 2020.4 as an alternative to the old gpg signatures. With the current work on composefs[1] they are becomming more important, as they will allow verification of the commit (and thus the composefs image) during boot, giving us a full trusted boot chain all the way into the ostree userspace. Note: `ostree sign` used to require libsodium and was thus disabled in e.g. the Fedora build of ostree. However, recently[2] it is also supported with openssl, which will let it be more widely used. [1] ostreedev/ostree#2921 [2] ostreedev/ostree#2922
This form of signatures has been (build-time-optionally) supported since ostree 2020.4 as an alternative to the old gpg signatures. With the current work on composefs[1] they are becomming more important, as they will allow verification of the commit (and thus the composefs image) during boot, giving us a full trusted boot chain all the way into the ostree userspace. Note: `ostree sign` used to require libsodium and was thus disabled in e.g. the Fedora build of ostree. However, recently[2] it is also supported with openssl, which will let it be more widely used. [1] ostreedev/ostree#2921 [2] ostreedev/ostree#2922
This form of signatures has been (build-time-optionally) supported since ostree 2020.4 as an alternative to the old gpg signatures. With the current work on composefs[1] they are becomming more important, as they will allow verification of the commit (and thus the composefs image) during boot, giving us a full trusted boot chain all the way into the ostree userspace. Note: `ostree sign` used to require libsodium and was thus disabled in e.g. the Fedora build of ostree. However, recently[2] it is also supported with openssl, which will let it be more widely used. [1] ostreedev/ostree#2921 [2] ostreedev/ostree#2922
This form of signatures has been (build-time-optionally) supported since ostree 2020.4 as an alternative to the old gpg signatures. With the current work on composefs[1] they are becomming more important, as they will allow verification of the commit (and thus the composefs image) during boot, giving us a full trusted boot chain all the way into the ostree userspace. Note: `ostree sign` used to require libsodium and was thus disabled in e.g. the Fedora build of ostree. However, recently[2] it is also supported with openssl, which will let it be more widely used. [1] ostreedev/ostree#2921 [2] ostreedev/ostree#2922
This form of signatures has been (build-time-optionally) supported since ostree 2020.4 as an alternative to the old gpg signatures. With the current work on composefs[1] they are becomming more important, as they will allow verification of the commit (and thus the composefs image) during boot, giving us a full trusted boot chain all the way into the ostree userspace. Note: `ostree sign` used to require libsodium and was thus disabled in e.g. the Fedora build of ostree. However, recently[2] it is also supported with openssl, which will let it be more widely used. [1] ostreedev/ostree#2921 [2] ostreedev/ostree#2922
This form of signatures has been (build-time-optionally) supported since ostree 2020.4 as an alternative to the old gpg signatures. With the current work on composefs[1] they are becomming more important, as they will allow verification of the commit (and thus the composefs image) during boot, giving us a full trusted boot chain all the way into the ostree userspace. Note: `ostree sign` used to require libsodium and was thus disabled in e.g. the Fedora build of ostree. However, recently[2] it is also supported with openssl, which will let it be more widely used. [1] ostreedev/ostree#2921 [2] ostreedev/ostree#2922
NOTE: This is WIP, I have not yet tried the boot part of this.
This is a continuation of #2891 which dropped the use of fs-verity built-in signatures in favour of user-space signatures (as discussed in containers/composefs#151).
The new approach is to the the more modern ed25519 detached signatures. We pass in a (typically one-use throwaway) secret key to commit, which is used to sign the composefs image digest. This is then put in the ostree metadata. During deploy this is extracted and stored next to the regenerated image. During boot, the initrd (ostree-prepare-root) loads the public key from a fixed place in the initrd, exctracs the digest of the composefs files and validates it according to the signature.
This way we can have a single-use public key that ties the initrd (which is validated with secureboot) and the ostree commit /usr part together (so you can't mix and match).
Note that the CLI for passing the key to commit uses the path to the key file rather than the passing the secret key in directly (as --sign does), because that seems very risky, and will likely end up in some logfile by accident.