-
Notifications
You must be signed in to change notification settings - Fork 26
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 the ability to contruct TrustRoot from targets #247
Add the ability to contruct TrustRoot from targets #247
Conversation
2302267
to
368c2a9
Compare
Signed-off-by: Slavek Kabrda <bkabrda@redhat.com>
368c2a9
to
09d589c
Compare
pkg/root/trusted_root_create.go
Outdated
|
||
// NewTrustedRootFromTargets initializes a TrustedRoot object from a mediaType string and | ||
// a slice of targets. These targets are expected to be PEM-encoded public keys/certificate chains. | ||
// This method of constructing the TrustedRoot has some shortcomings which can be aided by manually |
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.
If we don't address these, the concern would be that one would construct a trust root with NewTrustedRootProtobuf(json), then marshal it with MarshalJSON
, and get back a different trust root.
We should have a test that demonstrates JSON-input -> TrustedRoot
struct -> JSON-output, where JSON-input == JSON-output.
What if the API takes in a TrustedRoot
struct, fully populated, and outputs JSON? And if certain fields are missing, then we can have reasonable defaults (e.g if TransparencyLog.HashFunc
is not set, then pick SHA256
)
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.
So there's couple different things to note here:
- The only thing that this new method does is it constructs a
TrustedRoot
from given targets; this does not concern the JSON representation of theTrustedRoot
. On the contrary this is most useful when there is noTrustedRoot
yet and one needs to be constructed from public keys/certs. - However, I understand your concern about having the ability to marshal
TrustedRoot
- which was also added by this PR - I think the only problem right now is the "Uri" argument which I left unfilled forCertificateAuthority
struct, so I probably need to fix that and I can also add a test that you suggest. - I think taking in a
TrustedRoot
struct fully populated defeats the purpose of this PR - that won't solve a lot of what I need in Add functionality to generate trusted_root.json by the TUF server scaffolding#1191 and most of the code will need to stay there to create theTrustedRoot
struct in the first place.
So what I propose is adding the test as you suggested, and fixing any issues that pop up, but otherwise leaving this PR as is. Does that sound good?
pkg/root/trusted_root_create.go
Outdated
} | ||
} | ||
|
||
// TODO: should this rather be the innermost 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.
That's a good point. I think it should be the most restrictive validity period of all of the CA certificates, which is likely the intermediate lowest in the 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.
Sounds good, I can fix that.
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.
Done in ab939cc
Signed-off-by: Slavek Kabrda <bkabrda@redhat.com>
b4094db
to
ab939cc
Compare
I updated the PR to add a test for deserializing and then again serializing a I don't immediately see a good way to fix the time serialization format, but I'll keep looking into it. I think this is a difference that we could potentially live with, but I also understand if you think this must be fixed before merging - let me know. Thanks! |
FWIW, it looks like the |
Signed-off-by: Slavek Kabrda <bkabrda@redhat.com>
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 don't think this needs additional documentation, the added public function has a godoc that I think is sufficient.
I think adding a short usage example in the docs/ folder would be great actually, just to make it a little more discoverable when people are wondering what different things sigstore-go can do.
Signed-off-by: Slavek Kabrda <bkabrda@redhat.com>
8d0a810
to
54d5b4a
Compare
Good point @cmurphy - I added a simple example of a binary that can construct and pretty print a |
pkg/root/trusted_root_create.go
Outdated
// - Merkle Tree hash function is hardcoded to SHA256, as this is not derivable from the public key. | ||
// - Each certificate chain is expected to be given as a single target, where there is a newline | ||
// between individual certificates. It is expected that the certificate chain is ordered (root last). | ||
func NewTrustedRootFromTargets(mediaType string, targets []RawTrustedRootTarget) (*TrustedRoot, 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.
I think there's a lot of use-cases for making it easier to construct a trusted root, and the challenge here is going to be to make an API that's flexible enough to cover those use-cases. If you're in something like cosign verify-blob
with --certificate-chain
and --timestamp-certificate-chain
, it's totally fine to throw together something quickly to verify. But if you're the public good instance (or a private instance) you're going to want something that supports rotating key material with expiry times.
In pkg/root/trusted_root.go
we already have these nice intermediate representations of CertificateAuthority
and TransparencyLog
, so what if we added there something like:
func NewTrustedRoot(
certificateAuthorities []CertificateAuthorities,
certificateTransparencyLogs []TransparencyLog,
timestampAuthorities []CertificateAuthorities,
transparencyLogs []TransparencyLog
) TrustedRoot
This API would be similar to sign.BundleOptions
, which is nice.
So if you're in cosign verify-blob
and all you have is a PEM-encoded byte string from --certificate-authority
, you could construct a minimal CertificateAuthority
to pass in to NewTrustedRoot
, but if you had a start and end time that certificate was valid for, you could also supply that information. But (crucially!) then we don't have to guess as to what the configuration should be, and we don't risk returning a half-configured object that the caller then has to go and fix up.
That would be enough if you just need a TrustedRoot
to call into sigstore-go
, but if you want to output a JSON file we might need one more step. I love the idea of adding a function like:
func (tr *TrustedRoot) MarshalJSON() ([]byte, error)
but instead of the current implementation of constructProtoTrustRoot()
, maybe we could see if TrustedRoot.trustedRoot
is already set up (like if it came from NewTrustedRootFromProtobuf()
) or not (like if it came from the proposed NewTrustedRoot()
) and only populate content in TrustedRoot.trustedRoot
if it hasn't already been set up. This would ensure that if you load from a JSON file and call MarshalJSON()
you get the same content back.
Does that make sense? It might make sense to get feedback on the API and approach from a few folks and settle on a direction before we move ahead with the implementation.
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.
Thanks for your input! I've been thinking about this and I have this alternative implementation proposal:
- Instead of having the current
NewTrustedRootFromTargets
function, I will addNewTrustedRoot
as you suggested. - I will also expose functions that can instantiate
CertificateAuthority
andTransparencyLog
from certificate/public key material. These functions already exist in this PR, but are private. I'll revisit them and ensure their API is sane, name them better etc, so that they can be exposed. This will make sure that my usecase of easily instantiating the wholeTrustedRoot
object from the certificate/public key material is still very easy using this library. - In terms of the
MarshalJSON
call, it's important to note that we publicly allow users to access and modify the structures that form theTrustedRoot
object viaTimestampingAuthorities
,FulcioCertificateAuthorities
functions etc. This means that if we just use the currenttrustedRoot
, we could be serializing an outdated representation ofTrustedRoot
.
Does that make sense? Also CC @haydentherapper, I think this should also address your original concern. Let me know if this makes sense.
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 will also expose functions that can instantiate CertificateAuthority and TransparencyLog from certificate/public key material.
I think any helper functions like this should live outside of sigstore-go. It might seem silly, but one of the design goals of sigstore-go is to have opinionated interfaces that get wrapped by things that call sigstore-go to be more user-friendly. A great example of this is cosign, where you can supply information via a URL, a path on disk, a string of encoded data, etc. We want that flexibility to live in cosign, instead of having several similar functions in sigstore-go that support slightly different data input.
In terms of the MarshalJSON call, it's important to note that we publicly allow users to access and modify the structures that form the TrustedRoot object via TimestampingAuthorities, FulcioCertificateAuthorities functions etc. This means that if we just use the current trustedRoot, we could be serializing an outdated representation of TrustedRoot.
Got it - makes sense! Like you say, we should make sure whatever we marshal reflects the current state of the object.
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 any helper functions like this should live outside of sigstore-go. It might seem silly, but one of the design goals of sigstore-go is to have opinionated interfaces that get wrapped by things that call sigstore-go to be more user-friendly. A great example of this is cosign, where you can supply information via a URL, a path on disk, a string of encoded data, etc. We want that flexibility to live in cosign, instead of having several similar functions in sigstore-go that support slightly different data input.
I certainly don't want to go against the design goal that you folks have. TBH I wasn't even aware that this was the design goal - is there a good document that outlines design goals?
The only reason why I opened this PR in here was that @haydentherapper suggested it in sigstore/scaffolding#1191 (comment). I can definitely change the PR in the way you suggested, but I would like Hayden to comment on here to ensure I'm doing changes that everybody will agree with conceptually.
Signed-off-by: Slavek Kabrda <bkabrda@redhat.com>
@steiza @haydentherapper I believe I have now addressed all your concerns and that this should be acceptable. Can I ask you for another round of reviews? FWIW I removed the example, because the only really useful added feature is the serialization, which I don't think needs an example. I will also change the description to match what the PR currently does. |
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.
This looks great - thank you!
pkg/root/trusted_root_create.go
Outdated
return fmt.Errorf("failed converting rekor log %s to protobuf: %w", logID, err) | ||
} | ||
tr.trustedRoot.Tlogs = append(tr.trustedRoot.Tlogs, tlProto) | ||
// ensure stable sorting of the slice |
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.
Very nice!
pkg/root/trusted_root_create.go
Outdated
return &caProto, nil | ||
} | ||
|
||
func transparencyLogToProtobufTL(tl TransparencyLog) (*prototrustroot.TransparencyLogInstance, 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.
func transparencyLogToProtobufTL(tl TransparencyLog) (*prototrustroot.TransparencyLogInstance, error) { | |
func transparencyLogToProtobufTL(tl *TransparencyLog) (*prototrustroot.TransparencyLogInstance, error) { |
Any reason not to receive this via a pointer to avoid excessive copying?
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.
You're absolutely right, I missed that. I will fix it.
pkg/root/trusted_root_create.go
Outdated
return nil | ||
} | ||
|
||
func certificateAuthorityToProtobufCA(ca CertificateAuthority) (*prototrustroot.CertificateAuthority, 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.
func certificateAuthorityToProtobufCA(ca CertificateAuthority) (*prototrustroot.CertificateAuthority, error) { | |
func certificateAuthorityToProtobufCA(ca *CertificateAuthority) (*prototrustroot.CertificateAuthority, error) { |
Same here on receiving via value or pointer.
if err != nil { | ||
return fmt.Errorf("failed converting fulcio cert chain to protobuf: %w", err) | ||
} | ||
tr.trustedRoot.CertificateAuthorities = append(tr.trustedRoot.CertificateAuthorities, caProto) |
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.
We probably want to sort the certificate authorities too (and TSAs) similar to what you did for the tlogs.
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.
Ah, you're right, I forgot to do that. I'll submit a fix for it as well.
Signed-off-by: Slavek Kabrda <bkabrda@redhat.com>
@kommendorkapten thanks again for your review, I just pushed a commit that addresses your points. I also refactored the sorting functionality to functions to avoid copy-paste code (and I realized it was mistakenly called in every iteration of the loops, while we just need to do it after the loops, so I fixed that as well). Let me know if this looks good now! |
@@ -51,6 +51,7 @@ type CertificateAuthority struct { | |||
Leaf *x509.Certificate | |||
ValidityPeriodStart time.Time | |||
ValidityPeriodEnd time.Time | |||
URI 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.
FYI @cmurphy, about the URI being added for TSAs
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.
Thanks!
Summary
This PR:
This was requested in sigstore/scaffolding#1191 (review) by @haydentherapper.
Release Note
NewTrusted
function to enable creatingTrustedRoot
fromCertificateAuthority
andTransparencyLogs
instancesTrustedRoot
to JSONDocumentation
I don't think this needs additional documentation, the added public function has a godoc that I think is sufficient.