Skip to content
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 trusted-root create helper command #3876

Merged
merged 11 commits into from
Oct 29, 2024

Conversation

steiza
Copy link
Member

@steiza steiza commented Sep 11, 2024

Summary

Closes #3700

We want to help cosign users move from providing disparate verification material to a single file that contains the needed verification material.

Using a trusted root file makes it easier for users to rotate key material and specify what time period different keys were valid.

Once you create your trusted root with cosign trusted-root create ... you can then supply the output to verification commands that support it with cosign verify* --trusted-root ...

Here's an example, note that some flags can be specified more than once (for instance, if your certificate chain has rotated, or if you are supporting public good and private instance). You can specify as few or as much content as you'd like. You'll need to create or obtain certificate chains and public key files as needed:

$ go run cmd/cosign/main.go trusted-root create --certificate-chain fulcio-chain-public.pem --certificate-chain fulcio-chain-private.pem --timestamp-certificate-chain timestamp.pem --ctfe-key ctfe.pub --rekor-key rekor.pub

Release Note

  • Added cosign trusted-root create command to assist users in creating trusted root files to use in cosign verification commands

Documentation

sigstore/docs#327

To help cosign users move from providing disparate verification material
to a single file that contains the needed verification material.

This makes it easier for users to rotate key material and specify what
time period different keys were valid.

Signed-off-by: Zach Steindler <steiza@github.com>
Copy link

codecov bot commented Sep 11, 2024

Codecov Report

Attention: Patch coverage is 31.25000% with 132 lines in your changes missing coverage. Please review.

Project coverage is 36.55%. Comparing base (2ef6022) to head (1bd2b08).
Report is 231 commits behind head on main.

Files with missing lines Patch % Lines
cmd/cosign/cli/trustedroot/trustedroot.go 31.25% 76 Missing and 12 partials ⚠️
cmd/cosign/cli/options/trustedroot.go 0.00% 28 Missing ⚠️
cmd/cosign/cli/trustedroot.go 54.28% 15 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3876      +/-   ##
==========================================
- Coverage   40.10%   36.55%   -3.56%     
==========================================
  Files         155      206      +51     
  Lines       10044    13003    +2959     
==========================================
+ Hits         4028     4753     +725     
- Misses       5530     7654    +2124     
- Partials      486      596     +110     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Signed-off-by: Zach Steindler <steiza@github.com>
Signed-off-by: Zach Steindler <steiza@github.com>
steiza added a commit to steiza/sigstore-docs that referenced this pull request Sep 11, 2024
To go with sigstore/cosign#3876

Signed-off-by: Zach Steindler <steiza@github.com>
Copy link
Contributor

@cmurphy cmurphy left a comment

Choose a reason for hiding this comment

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

Love this! Generating a trusted root has been a pain for me for the last few weeks.


cmd.MarkFlagsMutuallyExclusive("ca-roots", "certificate-chain")
cmd.MarkFlagsMutuallyExclusive("ca-intermediates", "certificate-chain")

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not seeing a way to provide keys for the CT log service, is that intentional?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sort of - I was trying to mirror the flags of commands like cosign verify-blob ... as much as possible, and I didn't see a flag for specifying the public key of the CT log. If there is one, and I missed it, I'm happy to add it!

Copy link
Contributor

Choose a reason for hiding this comment

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

It is overridden via an environment variable

VariableSigstoreCTLogPublicKeyFile Variable = "SIGSTORE_CT_LOG_PUBLIC_KEY_FILE"
but by default it is fetched from the TUF targets. I don't think there is a flag to override it.

The CTFE key is used for signing, so unfortunately I don't think the flags for verify-blob are a good analog for what needs to be included in the trusted root.

Copy link
Member Author

Choose a reason for hiding this comment

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

This is a little tricky because of the diversity of private deployments of Sigstore. I added a --ignore-sct so that if the private deployment is using long-lived keys instead of a private Fulcio we don't try to add CT Log keys to their trusted root; otherwise we call cosign.GetCTLogPubs() which will make use of the environment variable or TUF target.

cmd.Flags().StringVar(&o.Out, "out", "",
"path to output trusted root")

cmd.Flags().StringVar(&o.RekorURL, "rekor-url", "",
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the rationale for using the rekor URL here instead of an on-disk artifact? How do we know we can trust the key being served at this URL?

Copy link
Member Author

Choose a reason for hiding this comment

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

Another great question, with a similar answer - as much as possible I'm trying to mirror the flags and behavior of the cosign verify* ... commands.

The idea being when we go to deprecate these flags from cosign verify* ... commands, you will generate the trusted root you need by calling cosign trusted-root create ... with the deprecated flags you were passing to cosign verify* ....

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think the flags for verify* are an ideal analog for what needs to be included in the trusted root. The --rekor-url flag used for the verify command is used in an online API call to verify the transparency log inclusion, but the trusted keys for the transparency log are fetched separately using the TUF configuration. We couldn't deprecate --rekor-url, or if we do it would be in favor of offline verification.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fair enough! I replaced --rekor-url with --ignore-tlog, with a similar implementation to --ignore-sct.

cmd.Flags().StringVar(&o.TSACertChainPath, "timestamp-certificate-chain", "",
"path to PEM-encoded certificate chain file for the RFC3161 timestamp authority. Must contain the root CA certificate. "+
"Optionally may contain intermediate CA certificates")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

It's not uber-critical, but with the options here there's no way to provide a uri for either the CA or the TSA. Other tools like trtool allow the user to manually provide that information.

Copy link
Member Author

@steiza steiza Sep 12, 2024

Choose a reason for hiding this comment

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

Yeah, I'm leaning towards keeping this tool more minimal and mirroring the flags of existing cosign verify* ... commands, but people are of course welcome to edit the output to add additional context (including setting URIs or more sane valid timestamps!), or use more complete tooling like trtool.

cmd/cosign/cli/trustedroot/trustedroot.go Outdated Show resolved Hide resolved
Signed-off-by: Zach Steindler <steiza@github.com>
With `--ignore-sct` to support if you are using keys instead of Fulcio.

Signed-off-by: Zach Steindler <steiza@github.com>
Similar to `--ignore-sct`

Signed-off-by: Zach Steindler <steiza@github.com>
"when set, do not include key for verifying certificate transparency "+
"log. Set this if you signed with a key instead of using Fulcio.")

cmd.Flags().BoolVar(&o.IgnoreTlog, "ignore-tlog", false,
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm still hesitant about the design of mirroring the flags of the verify* commands. This is a brand new command, it might as well have flag names that are intuitive in relation to its purpose. trusted_root.json is used for both signing and verification, so restricting the flags to just what is relevant to verification feels like an oversight. Using an explicit flag like --certificate-chain along with a negative flag like --ignore-tlog seems inconsistent.

It would make more sense to me if this command picked one direction and followed it for all key material: either every key is explicitly specified by a flag that points to an on-disk key (e.g. cosign trusted-root --certificate-chain=fulcio-chain.pem --rekor-key=rekor.pub --ctfe-key=ctfe.pub)
or all keys including Fulcio certs are retrieved via the TUF client (and ignored if TUF can't find them).

Copy link
Member Author

Choose a reason for hiding this comment

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

It would make more sense to me if this command picked one direction and followed it for all key material: either every key is explicitly specified by a flag that points to an on-disk key (e.g. cosign trusted-root --certificate-chain=fulcio-chain.pem --rekor-key=rekor.pub --ctfe-key=ctfe.pub)

This sounds good to me! I waffled a bit on files on disk vs. TUFv1 support, but I think supporting files on disk will be more accessible (it's easier for a TUFv1 user to download files and supply them via CLI than it is for someone using files on disk to set up a TUFv1 destination!)

Instead of clients querying remote servers

Signed-off-by: Zach Steindler <steiza@github.com>
Copy link
Contributor

@cmurphy cmurphy left a comment

Choose a reason for hiding this comment

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

lgtm!

return err
}

ctLogs[id] = &root.TransparencyLog{
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we need to set validity periods as well? Do we need to take in flags for start time, or default to now?

Copy link
Member Author

Choose a reason for hiding this comment

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

Since we aren't setting ValidityPeriodStart, it will default to "0001-01-01T00:00:00Z".

Philisophically, I was thinking of this utility more as "enough of a trusted root to get you started" instead of a flexible tool to cover all use-cases. This will be a recurring theme in my responses! And of course once you have the skeleton of a trusted root file, the user can continue editing it and adding additional content.

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed it shouldn't cover all cases, but if we're going to instruct users to go manually inspect it afterwards and update a set of values, providing flags to do that for them seems reasonable. Since validity period is a significant benefit of the trusted root, I'd prefer adding a flag for users to set the start time.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added the ability to specify the validity start time for keys

var _ Interface = (*TrustedRootCreateOptions)(nil)

func (o *TrustedRootCreateOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.CAIntermediates, "ca-intermediates", "",
Copy link
Contributor

Choose a reason for hiding this comment

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

How do cert pools work here? This seems incompatible with the trusted root, as you have to specify a chain.

I see below that you're flattening, assuming 1 intermediate per root and assuming these two lists are ordered. I think that's not always going to be accurate - i would assume a client would throw all their certs into a list without thinking about chain building.

I would recommend we drop these until we support pools in the trust root, and require that clients construct the chains themselves.

Copy link
Member Author

Choose a reason for hiding this comment

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

Initially we were trying to mirror other cosign flags, but we ended up moving away from that, so how about we specify a single root and a single intermediate file (that can have one or more intermediates)?

Again, this is in the spirit of "enough to get you started" and not a do-it-all utility.

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd lean towards just removing these flags entirely and only have --certificate-chain as an option. if you specify a root and its intermediates separately, you still have to know their order - might as well just use the chain flag at that point.

Copy link
Member Author

Choose a reason for hiding this comment

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

Only supporting chains sounds good to me! I updated this pull request to reflect that.

"when building the certificate chains for the signing certificate. Conflicts with --certificate-chain.")
_ = cmd.Flags().SetAnnotation("ca-roots", cobra.BashCompFilenameExt, []string{"cert"})

cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "",
Copy link
Contributor

Choose a reason for hiding this comment

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

Should all of these options be plural, so that we can provide multiple chains, multiple logs, and multiple TSAs?

Copy link
Member Author

Choose a reason for hiding this comment

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

At this point, you can probably anticipate my response : D but I think we should stick to the bare minimum required to get folks off the ground, and let them iterate on top of the basic trusted root we give them.

Copy link
Contributor

Choose a reason for hiding this comment

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

I feel less strongly on this one, but I think pluralizing is a small enough change that if we did that now, we'd cover future use cases (e.g. trusting both an internal deployment and public deployment)

Copy link
Member Author

Choose a reason for hiding this comment

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

This did end up being a pretty minor change! You can now use the flags multiple times to specify additional verification materials.

Copy link
Member Author

@steiza steiza left a comment

Choose a reason for hiding this comment

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

If the bare-bones approach I describe makes sense, let me know and I can update things to match!

return err
}

ctLogs[id] = &root.TransparencyLog{
Copy link
Member Author

Choose a reason for hiding this comment

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

Since we aren't setting ValidityPeriodStart, it will default to "0001-01-01T00:00:00Z".

Philisophically, I was thinking of this utility more as "enough of a trusted root to get you started" instead of a flexible tool to cover all use-cases. This will be a recurring theme in my responses! And of course once you have the skeleton of a trusted root file, the user can continue editing it and adding additional content.

var _ Interface = (*TrustedRootCreateOptions)(nil)

func (o *TrustedRootCreateOptions) AddFlags(cmd *cobra.Command) {
cmd.Flags().StringVar(&o.CAIntermediates, "ca-intermediates", "",
Copy link
Member Author

Choose a reason for hiding this comment

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

Initially we were trying to mirror other cosign flags, but we ended up moving away from that, so how about we specify a single root and a single intermediate file (that can have one or more intermediates)?

Again, this is in the spirit of "enough to get you started" and not a do-it-all utility.

"when building the certificate chains for the signing certificate. Conflicts with --certificate-chain.")
_ = cmd.Flags().SetAnnotation("ca-roots", cobra.BashCompFilenameExt, []string{"cert"})

cmd.Flags().StringVar(&o.CertChain, "certificate-chain", "",
Copy link
Member Author

Choose a reason for hiding this comment

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

At this point, you can probably anticipate my response : D but I think we should stick to the bare minimum required to get folks off the ground, and let them iterate on top of the basic trusted root we give them.

Also add ability to specify validity start time for keys

Signed-off-by: Zach Steindler <steiza@github.com>
steiza added a commit to steiza/sigstore-docs that referenced this pull request Oct 8, 2024
To go with sigstore/cosign#3876

Signed-off-by: Zach Steindler <steiza@github.com>
@dmitris
Copy link
Contributor

dmitris commented Oct 17, 2024

As discussed on the Sigstore Slack (#private-sigstore-users channel) - found a case causing a panic on nil pointer (the PR linked below has the details), raised a PR with a trivial fix (nil pointer check on block in the for loop condition): https://github.com/steiza/cosign/pull/2/files#diff-abb0ac148c9642335c919aaffc4700982c9d799bc98d528c4d7d921f2470f2b5R165

for block, contents := pem.Decode(contents); block != nil; block, contents = pem.Decode(contents) {

it would be good to add a unit test covering the case of the nil block panic, just in case. I can do it and send you another "PR-to-PR" - let me know if you'd like me to.

steiza and others added 2 commits October 17, 2024 16:00
Update tests, also fix documentation for flags that were removed.

Co-authored-by: Dmitry S <dsavints@gmail.com>
Signed-off-by: Zach Steindler <steiza@github.com>
Signed-off-by: Zach Steindler <steiza@github.com>
@steiza
Copy link
Member Author

steiza commented Oct 17, 2024

Thank you @dmitris! I added your fix, along with tests, and added you as a co-author of the commit.

@@ -36,7 +36,7 @@ func (o *TrustedRootCreateOptions) AddFlags(cmd *cobra.Command) {
"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. Conflicts with --ca-roots and --ca-intermediates.")
Copy link
Contributor

Choose a reason for hiding this comment

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

@why do we remove the notice about conflicting flags? When I run cosign verify built from this branch with both --ca-roots file.pem and --certificate-chain file.crt, I'm getting the following error:

Error: if any flags in the group [ca-roots certificate-chain] are set none of the others can be; [ca-roots certificate-chain] were all set
main.go:74: error during command execution: if any flags in the group [ca-roots certificate-chain] are set none of the others can be; [ca-roots certificate-chain] were all set

so the "mutual exclusion" of these flags is still there - why then the change of the flags documentation, here and also in doc/cosign_trusted-root_create.md?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, so cosign verify supports both --certificate-chain and --ca-roots, but later we decided that cosign trusted-root create would only support --certificate-chain. There isn't a --ca-roots option for cosign trusted-root create anymore.

Copy link
Contributor

@dmitris dmitris Oct 23, 2024

Choose a reason for hiding this comment

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

would --certificate-chain support PEM files with multiple chains, or only a single one? If you have a PEM bundle with a dozen "top-level" self-signed CA root certificates, would you be able to use cosign trusted-root create to create a trusted root JSON file or not? Would one had to "manually" split that bundle into multiple PEM files, one per certificate (or certificate chain)? Can you use --certificate-chain multiple times?

As mentioned on Slack , if you currently try to pass a PEM bundle with multiple CA root certificates as --certificate-chain, the generated JSON file is incorrect, or at least not what one would expect: it puts all of them in the same chain, with one certificate being the root, and the rest - Intermediates, even though they are all self-signed roots.
The same issue is with parsing a timestamp certificate bundle with multiple certificates.

If these are use cases that we do not intend to support, let's explicitly state it in the documentation.

Signed-off-by: Zach Steindler <steiza@github.com>
Copy link
Contributor

@dmitris dmitris left a comment

Choose a reason for hiding this comment

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

👍

Copy link
Contributor

@haydentherapper haydentherapper left a comment

Choose a reason for hiding this comment

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

Great work @steiza!

@haydentherapper haydentherapper merged commit 9b17791 into sigstore:main Oct 29, 2024
23 checks passed
@steiza steiza mentioned this pull request Oct 31, 2024
2 tasks
haydentherapper pushed a commit to sigstore/docs that referenced this pull request Nov 7, 2024
To go with sigstore/cosign#3876

Signed-off-by: Zach Steindler <steiza@github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for providing a trust root file for private deployments
5 participants