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
add --ca-roots and --ca-intermediates flags to 'cosign verify' #3464
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3464 +/- ##
==========================================
+ Coverage 40.10% 40.25% +0.15%
==========================================
Files 155 155
Lines 10044 10137 +93
==========================================
+ Hits 4028 4081 +53
- Misses 5530 5563 +33
- Partials 486 493 +7 ☔ View full report in Codecov by Sentry. |
@dmitris Is this ready for review? Or were you looking for more initial feedback for moving it out from draft? |
8fb3141
to
fca88dc
Compare
@haydentherapper marked as "ready for review" now. I thought about adding unit tests - but I believe the current implementation with the "big" |
323e003
to
9954fe2
Compare
8e49130
to
22350e9
Compare
e770d38
to
e7375fa
Compare
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.
Given this is a more sensitive change, cc'ing a few others for thoughts - @woodruffw @kommendorkapten PTAL.
pkg/cosign/verify.go
Outdated
// ValidateAndUnpackCertWithCertPools creates a Verifier from a certificate. Verifies that the certificate | ||
// chains up to the provided root. CheckOpts should contain a pool of CA Roots and optionally the Intermediates. | ||
// Optionally verifies the subject and issuer of the certificate. | ||
func ValidateAndUnpackCertWithCertPools(cert *x509.Certificate, co *CheckOpts) (signature.Verifier, error) { |
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.
Given this is only used in one place, can we just call ValidateAndUnpackCert
directly? I believe if no roots are provided, verification will fail so we don't need an explicit check, though it would be good to have a unit test to confirm.
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.
[FYI] I tried to "combine" the two functions but was getting test failures with cosign attach flows - likely I have missed some code paths. Backed up to the state that you reviewed for now to get into the green build zone - will try again "with smaller steps."
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 got rid of the "special" function ValidateAndUnpackCertWithCertPools
in 44e0ff5.
Do you think I should remove this check?
https://github.com/sigstore/cosign/pull/3464/files#diff-fc9a26a4caa1d8684a4bdd9102f187f44e1f7fce7c06a19f4185035f13317cc8R296-R298
if co.RootCerts == nil {
return errors.New("no CA roots provided to validate certificate")
}
I would think it can be good to fail earlier with possibly more explicit error what have gone wrong, to make for easier troubleshooting (if it ever gets triggered). Let me know if you prefer this yanked @haydentherapper. I will also look into adding or expanding the unit tests, as you mentioned above.
@@ -177,29 +179,59 @@ func (c *VerifyCommand) Exec(ctx context.Context, images []string) (err error) { | |||
} | |||
} | |||
if keylessVerification(c.KeyRef, c.Sk) { |
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.
Is there a test we could add to e2e_test.go to exercise this new code?
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.
@haydentherapper added functional tests in 62de169
@@ -60,6 +60,8 @@ type VerifyCommand struct { | |||
CertGithubWorkflowName string | |||
CertGithubWorkflowRepository string | |||
CertGithubWorkflowRef string | |||
CAIntermediates string |
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.
Unfortunately, we have a lot of duplication in cosign right now. We'll also need to add this to verify-blob, verify-attestation, and verify-blob-attestation. Before you add that, let's wrap up all comments here, and then it should be a straightforward copy-paste.
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.
yes, I know - wanted to do it in a follow-up PR to make it easier to review. Can make an issue not to forget - or add it to this PR.
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.
(TODO - add this to verify-blob, verify-attestation, and verify-blob-attestation
, after settling on the initial cosign verify
case)
44e0ff5
to
6451fc4
Compare
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.
LGTM overall, just two small comments.
So what's left is:
- Add reading the chain from the new flags when a certificate is provided
- Refactor into switch statement if you agree
- Copy-paste this for all other verification functions and add tests
If you can add these as individual commits, that should make reviewing go fast.
The last nice-to-have would be removing the testing script and having these tests as code in e2e_test.go, because we now have fully hermetic testing with its own TSA, Fulcio, Rekor, and registry. I know this might be a bigger change so happy to circle back to this in a later PR.
cmd/cosign/cli/verify/verify.go
Outdated
if err != nil { | ||
return err | ||
if c.CARoots == "" { | ||
// Verify certificate with chain |
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 we also want to read in the chain from ca-roots
and ca-intermediates
to verify the cert?
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.
@haydentherapper not sure if this is still relevant - I added in 96c2cd1 a comment to the co.CARoots != nil
case: // Verify certificate with root (and if given, intermediate) certificate
. Both with the chain and the ca-roots
/ ca-intermediates
, the validation is done with the same function ValidateAndUnpackCert , only with the certificate chain there is a step of parsing the file and loading the CheckOpts
. Let me know or resolve if this is ok or you would like some changes, please.
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.
@haydentherapper also, regarding "what's left is: * Add reading the chain from the new flags when a certificate is provided" - I think we do read the chain - parse the certificates into CheckOpts
which are then used for verifying certificates. Or do you mean something different? please let me know 😄
cmd/cosign/cli/verify/verify.go
Outdated
} | ||
} else { | ||
if co.RootCerts == nil { | ||
return errors.New("no CA roots provided to validate certificate") |
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 would restructure this as a switch statement though, for "ca roots and cert chain == nil", "ca roots provided", "cert chain provided". This last check should not be possible then, correct? Either you've provided no root and we fetch it automatically, or you provide a root.
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.
agree - done in dcefda7 (if looks good to you, please let me know, thumb up or resolve)
cmd.Flags().StringVar(&o.CAIntermediates, "ca-intermediates", "", | ||
"path to a file of intermediate CA certificates in PEM format which will be needed "+ | ||
"when building the certificate chains for the signing certificate. "+ | ||
"The flag is optional and must be used together with --ca-roots, conflicts with "+ | ||
"--certificate-chain.") | ||
_ = cmd.Flags().SetAnnotation("ca-intermediates", cobra.BashCompFilenameExt, []string{"cert"}) | ||
cmd.Flags().StringVar(&o.CARoots, "ca-roots", "", | ||
"path to a bundle file of CA certificates in PEM format which will be needed "+ | ||
"when building the certificate chains for the signing certificate. Conflicts with --certificate-chain.") | ||
_ = cmd.Flags().SetAnnotation("ca-roots", cobra.BashCompFilenameExt, []string{"cert"}) | ||
|
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'm less familiar with cosign so please take this with a grain of salt, but: I'm personally -1 on separate state flags for intermediate and root CA certificates.
My rationale:
- Technically speaking, there is no real distinction between a "root" and an "intermediate" certificate: some people use "root" to mean "self-signed," but it's common to have both non-self-signed and cross-issued roots in real PKIs. In other contexts "intermediate" means "part of the untrusted set", but it's not immediately clear to me whether intermediates are trusted or untrusted in cosign (CC @haydentherapper)
- Long term, I believe the plan is to have most Sigstore clients use a common
--trusted-root
or similar flag, which will load a shared format for the roots of trust for an "entire" Sigstore deployment (including the Rekor key, CTFE key, etc.). While this doesn't preclude that effort, it does add another set of knobs that will eventually need to be deprecated (or treated as another source of state). - Similarly long-term, I believe the plan is to have most Sigstore clients (including
cosign
) use Sigstore bundles as their interchange format. Bundles in turn support untrusted intermediates for the BYO PKI case, and in an unambiguous fashion (everything in the bundle is presumed untrusted).
So, TL;DR: if the idea behind --ca-intermediates
is for it to pass trusted intermediates, then IMO it should be removed and collapsed into --ca-roots
(which IMO could be called --trusted-certs
to minimize confusion).
On the other hand, if these certificates are meant to be untrusted I can see the value of this flag, but I think it might be somewhat short-lived given the trustroot + bundle progression. But maybe that isn't as far along as I think 😅
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.
For public good Sigstore, the intermediate is "trusted", as it is provided via the TUF trust root, and it's part of the trusted_root.json, and for public good we do not allow intermediates not provided direct via the trust root.
But if cosign
is to be used with other deployments, they can be untrusted. So with that in mind, and the bundle format is not yet supported in cosign (to carry untrusted intermediates) I think the current proposal makes sense, but it can feel a bit awkward for the public good instance.
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.
@woodruffw thanks for your review and suggestions!
Technically speaking, there is no real distinction between a "root" and an "intermediate" certificate
Well, for example looking at the Fulcio Certificate Specification, it has subsections for Root, Intermediate, and Issued Certificates, with specifications of what each type of certificate MUST
/ MUST NOT
/ SHOULD
have. Also, for better or worth, a search for intermediate
brings up over 140 hits in the sigstore/cosign
codebase, including in the existing options for cosign verify
:
doc/cosign_verify.md: --certificate-chain string path to a list of CA certificates in PEM format which will be needed when building the certificate chain for the signing certificate. Must start with the parent intermediate CA certificate of the signing certificate and end with the root certificate
also over 60 matches have both intermediate
and root
in the same line so these terms seems both well established and useful in the cosign
repo and code. Maybe it is due to my 'surrounding environment' but I don't remember any cases where I or people around would have difficulties distinguishing which (CA) certificates are root ones, and which - intermediates.
In other contexts "intermediate" means "part of the untrusted set", but it's not immediately clear to me whether intermediates are trusted or untrusted in cosign
[...]
But if cosign is to be used with other deployments, they can be untrusted.
I have to admit having difficulties understanding which certificates are (or should be) considered "trusted" and "untrusted" in cosign (there is only one search hit on untrusted
in sigstore/cosign and it is to a CHANGELOG in #2124; the only search hit on untrusted
in https://docs.sigstore.dev also is not very helpful). I think the certificate trust is so much context-dependant. For example, I'm used to think of the certificates issues by our TheCompanyCA
as both fully trusted and more trustworthy than those from Digicert & Co. 😄 - while in an environment oriented to the public websites and Certificate Authorities it would likely be considered as "Self-Signed" and untrusted.
Regarding the points 2. and 3. ("use a common --trusted-root flag" and Sigstore bundles as the interchange format) - in the long term, I have some reservations regarding this in terms of the ease of use and Developer / DevOps Experience. I worry that they would become custom requirements and make hurdles to using Sigstore for signing artifacts. PEM certificate bundles are "universal", ubiquitous and extremely familiar to everyone working with them and also deeply interwined with the existing scripts, processes etc. The TUF roots / Sigstore bundles are (IMO) in comparison relatively new, more complex (PEM -> JSON -> ...), "special purpose" and require custom handling, adoption steps / timeframe and increase the adoption and operational friction, especially for large users. Undoubtedly, the change will make life easier for us as the sigstore developers and the codebase probably nicer ("just one file/format to rule them all!") but possibly at the expense of placing some extra burden on the end users ("in addition to distributing the ca-bundle.pem
to 100,000 endpoints that you are doing now and must continue to do for many years, you also need to create the file and distribute it to all the endpoints that will or might need to verify cosign signatures"). So I would recommend and plead 😄 to consider the trade-off carefully and if decided, let the users know well in advance about the upcoming change(s) and provide for an ample transition period (maybe something like one major version during which one could use both old and new ways of supplying certificates) 🙏 /cc @haydentherapper
But as you said, this would be long-term changes - and currently we have an "acute" issue of not being able to (properly) verify cosign signatures with cosign verify
and through the cosign Go library if there is more than one CA authority issuing certificates - namely using a certificate bundle PEM file that contains multiple CA Root certificates. (And we would like to start issuing and verifying lots of Sigstore/Cosign signatures in Q1 '24! 😁).
The suggested cosign verify --ca-roots <certbundle.pem>
and the expanded CertVerifyOptions and VerifyCommand in the Go library would solve that problem. If you have other ideas how we could do this (in the near term), please let me know! (I thought about expanding the current --certificate-chain
format or adding --certificate-chains
[plural], but couldn't figure out how to handle the file format - with the single chain, it is easy to parse the file [<Intermediate>*<Root>
] but if there are multiple roots, it gets very messy.... I also didn't want to get into the business of building multiple certificate chains from a set of certificates - the Go stdlib already does it very well but it requires providing Roots
and optionally Intermediates
in the VerifyOptions mentioned above - to which the two new suggested flags would correspond.)
bd10952
to
62de169
Compare
Related to issue sigstore#3462. Current commit adds the flag to verify the CLI options. The new flag doesn't have any effect yet (will add in follow-up PRs). Signed-off-by: Dmitry S <dsavints@gmail.com>
Add --ca-roots command-line flag for 'cosign verify' to enable verifying cosign signatures using PEM bundles of CA roots. Whether to also add --ca-intermediates flag is TBD. Unit tests will be added in the next commit(s). Fixes sigstore#3462. Signed-off-by: Dmitry S <dsavints@gmail.com>
Signed-off-by: Dmitry S <dsavints@gmail.com>
Add --ca-intermediates flag to enable to pass a PEM file with intermediate CA certificates. One can use either --ca-roots, optionally together with --ca-intermediates - or --certificate-chain, which contains zero, one or several intermediate CA certificate followed by the root CA certificate. Expand the helper Go program test/gencert/main.go to allow to generate root and intermediate CA certificates, and a certificate signed by the intermediate CA. Expand the functional test e2e_tsa_certbundle.sh to test the --ca-intermediates flag (together with --ca-roots). Fixed sigstore#3462. Signed-off-by: Dmitry S <dsavints@gmail.com>
* simplify switch statements (remove unnecessary brackets) * reword help text for '--ca-roots' and '--ca-intermediates' flags for clarity Signed-off-by: Dmitry S <dsavints@gmail.com>
Signed-off-by: Dmitry S <dsavints@gmail.com>
Signed-off-by: Dmitry S <dsavints@gmail.com>
Signed-off-by: Dmitry S <dsavints@gmail.com>
Signed-off-by: Dmitry S <dsavints@gmail.com>
Signed-off-by: Dmitry S <dsavints@gmail.com>
Summary
Add new
--ca-roots
and--ca-intermediates
flags to allow pass a certificate bundle PEM file with multiple CA roots and optionally a file with the intermediate certificates. Related to issue #3462. Current commit adds the two flags to verify the CLI options. There is a new functional test filetest/e2e_tsa_certbundle.sh
for the new flags tocosign verify
.Release Note
added --ca-roots and
--ca-intermediates
flags to take a certificate bundle PEM file with one or multiple CA roots and optionally a PEM file with intermediate certificates. Both flags are exclusive with--certificate-chain
.Documentation
sigstore/docs#291