-
Notifications
You must be signed in to change notification settings - Fork 4
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 package for Tessera integration #62
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 #62 +/- ##
=======================================
Coverage ? 20.83%
=======================================
Files ? 15
Lines ? 480
Branches ? 0
=======================================
Hits ? 100
Misses ? 356
Partials ? 24 ☔ View full report in Codecov by Sentry. |
fe79d30
to
0d767f8
Compare
I guess the protobuf change could be problematic (unless unsignedness is also guaranteed in rekor v1): TransparencyLogEntry is used in places like Bundle as well so should remain compatible, right? |
pkg/signer/file.go
Outdated
@@ -0,0 +1,43 @@ | |||
/* | |||
Copyright The Rekor Authors. |
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.
It's weird addlicense isn't catching this, but I think the other files are Copyright [YYYY] The Sigstore Authors
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 was copied without modification from rekor v1 and I'm not sure what the rules are for changing the copyright. I think it wasn't caught by the linter because it's not by "The Sigstore Authors".
pkg/signer/file.go
Outdated
signature.SignerVerifier | ||
} | ||
|
||
func NewFile(keyPath, keyPass string) (*File, 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 find this naming confusing.
something like signer.FromFile(...)
(or maybe, signer.NewFromFile
) sounds nicer to me, but I'm still getting back into go conventions from Java land.
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.
Yeah it's just copied from rekor https://github.com/sigstore/rekor/blob/main/pkg/signer/file.go
pkg/signer/signer.go
Outdated
}): | ||
return kms.Get(ctx, signer, crypto.SHA256) | ||
default: | ||
return NewFile(signer, pass) |
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.
could this result in some confusing error messages? Where we don't find an expected matching KMS provider but then the default is invoked and fails. Then our error message is something about the NewFile constructor failing?
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.
Yeah that is strange, this was also copypasted from rekor but probably worth fixing.
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.
Actually no, I think this is by design. If it isn't a known KMS schema, there is no other way to interpret it except as a file path. It's also not really possible to definitively look at a string and determine if it's a file path, it could start with '/' or not, it could even just be '-' to indicate stdin (https://github.com/smallstep/crypto/blob/84f676bc58c42233dd7679c8530122f63ad8e740/internal/utils/io.go#L23). This is working in rekor v1, I think I'll leave it as is.
My rekor checkout was old and didn't include the new Tink provider, so I'll update that part.
pkg/tessera/tessera.go
Outdated
readTileFn client.TileFetcherFunc | ||
} | ||
|
||
func NewStorage(ctx context.Context, origin, bucket, spanner, keyPath, keyPass string) (*Storage, 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.
Does it make sense for this be named according to the backend type? How would we add other backend types?
Could bucket and spanner be abstracted interfaces for leaf storage and metadata storage?
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 it still makes sense for this to be Storage
and NewStorage
because the object is going to work the same no matter what the backend is. I'll change this to accept a tessera.Driver
instead of a bucket and spanner string so the caller just has to create and pass in the driver, and it will work the same.
pkg/tessera/tessera.go
Outdated
readTileFn client.TileFetcherFunc | ||
} | ||
|
||
func NewStorage(ctx context.Context, origin, bucket, spanner, keyPath, keyPass string) (*Storage, 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.
Similarly, instead of accepting keyPath and keyPass, could it just accept the signer already instantiated? (then the code can be explicit about local key or kms)
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.
Will do
Good point, it would probably be a minefield to change 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.
looks fine to me (although the tessera integration itself I can't competently evaluate), left a few questions.
WRT unit testing, can we leverage existing tests in sigstore/rekor or is the implementation so different that this makes no sense?
pkg/signer/signer.go
Outdated
default: | ||
return NewFile(signer, pass) |
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 is existing code that's being moved so not asking for changes, just making sure I understand: the signer
string is prefixed with the signer type but file signers have no prefix at all, it's just a path?
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 see you already discussed this in another thread.
// Name returns the server name associated with the key. | ||
func (n *noteSigner) Name() string { | ||
return n.name |
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 may be a noob golang question but are getters like this useful or expected?
There's definitely a cognitive load to them (while reading this I'm now keeping track of both noteSigner.name
and notesigner.Name()
).
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'll add some comments explaining. These methods are required so that noteSigner
can implement https://pkg.go.dev/golang.org/x/mod/sumdb/note#Signer which Tessera requires for signing checkpoints.
pkg/tessera/note.go
Outdated
// isValidName reports whether name is valid. | ||
// It must be non-empty and not have any Unicode spaces or pluses. |
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.
there's no context for name
usage yet so ... is there some rekor/tessera specific criteria here or where does "validity" come from?
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.
Here's the context https://github.com/C2SP/C2SP/blob/main/tlog-checkpoint.md#note-text I'll add a comment
// safeInt64 holds equivalent int64 and uint64 integers. | ||
type safeInt64 struct { | ||
u uint64 | ||
i int64 | ||
} |
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.
Can you (or one of the tessera folks) expand on why this works -- are checkpoint indices guaranteed to be below MaxInt64?
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.
It's not a guarantee by Tessera. It's an assumption in Rekor v1 and only because Rekor v1 uses signed ints. I'm using it here for compatibility with the TransparencyLogEntry definition, which uses signed ints. The operator would need to be aware of this limitation and start a new tree before the size reaches MaxInt64.
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, will wait on jku's comments to be resolved.
) | ||
|
||
// New returns a Signer for the given KMS provider, Tink, or a private key file on disk. | ||
func New(ctx context.Context, signer, pass, tinkKEKURI, tinkKeysetPath string) (signature.Signer, 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'm still a little confused by this New
. There's too many optional parameters in this method, its a bit funky. Is this just so we pass CLI options directly into the new?
Anyway, since we're just copying things over, we should probably leave as is, I guess I can go back in later and mess with 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.
Basically yeah:
https://github.com/sigstore/rekor/blob/c820fcaf3afdc91f0acf6824d55c1ac7df249df1/pkg/api/api.go#L173-L178
https://github.com/sigstore/rekor/blob/c820fcaf3afdc91f0acf6824d55c1ac7df249df1/pkg/sharding/ranges.go#L149-L150
It got more complicated when tink was added in sigstore/rekor#2228
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.
WRT unit testing, can we leverage existing tests in sigstore/rekor or is the implementation so different that this makes no sense?
For this part, there's no equivalent in sigstore/rekor because there's no Tessera, and Rekor doesn't even directly use the TLE in its API responses. Once I actually start writing the HTTP handlers I may be able to pull more in from rekor v1.
) | ||
|
||
// New returns a Signer for the given KMS provider, Tink, or a private key file on disk. | ||
func New(ctx context.Context, signer, pass, tinkKEKURI, tinkKeysetPath string) (signature.Signer, 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.
Basically yeah:
https://github.com/sigstore/rekor/blob/c820fcaf3afdc91f0acf6824d55c1ac7df249df1/pkg/api/api.go#L173-L178
https://github.com/sigstore/rekor/blob/c820fcaf3afdc91f0acf6824d55c1ac7df249df1/pkg/sharding/ranges.go#L149-L150
It got more complicated when tink was added in sigstore/rekor#2228
// Name returns the server name associated with the key. | ||
func (n *noteSigner) Name() string { | ||
return n.name |
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'll add some comments explaining. These methods are required so that noteSigner
can implement https://pkg.go.dev/golang.org/x/mod/sumdb/note#Signer which Tessera requires for signing checkpoints.
pkg/tessera/note.go
Outdated
// isValidName reports whether name is valid. | ||
// It must be non-empty and not have any Unicode spaces or pluses. |
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.
Here's the context https://github.com/C2SP/C2SP/blob/main/tlog-checkpoint.md#note-text I'll add a comment
// safeInt64 holds equivalent int64 and uint64 integers. | ||
type safeInt64 struct { | ||
u uint64 | ||
i int64 | ||
} |
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.
It's not a guarantee by Tessera. It's an assumption in Rekor v1 and only because Rekor v1 uses signed ints. I'm using it here for compatibility with the TransparencyLogEntry definition, which uses signed ints. The operator would need to be aware of this limitation and start a new tree before the size reaches MaxInt64.
This change adds the workflow to instantiate the tessera storage driver add an entry to the log. It copies most of the sigstore/rekor/pkg/signer package to enable signing checkpoints with an on-disk key file or a KMS provider. The TransparencyLogEntry structure uses signed integers for the LogIndex and TreeSize values, while tessera mostly returns unsigned integers for these values. This change incorporates a conversion mechanism. It would probably be better to update the protobuf-specs definition to use unsigned integers. Signed-off-by: Colleen Murphy <[email protected]>
Here's the script I've been using to test this, if it's of interest https://gist.github.com/cmurphy/0ae387aceba197ce0490fa75d4e2ffd6 It also shows why I wanted to put pkg/signer in this PR even though pkg/tessera isn't directly using it. |
"strings" | ||
"testing" | ||
|
||
tinkUtils "github.com/sigstore/rekor/pkg/signer/tink" |
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.
Update the import to this package?
// noteSigner uses an arbitrary sigstore signer to implement golang.org/x/mod/sumdb/note.Signer, | ||
// which is used in Tessera to sign checkpoints in the signed notes format | ||
// (https://github.com/C2SP/C2SP/blob/main/signed-note.md). | ||
type noteSigner struct { |
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.
Should we call this an ECDSA note signer? From C2SP, the key ID calculation is for the 0x02
signature type.
This is sigstore/rekor#2062.
We can use Tessera's note signer if the key is ed25519, and this signer if the key is ecdsa.
If it's RSA, we need to define our own key ID since that is not defined by C2SP. I'd suggest SHA256(key name || 0xA || 0xFF || "RSA-PKCS#1v1.5" || public key)[:4]
. 0xFF comes from the spec, and we include an additional identifier recommended by the spec.
Summary
This change adds the workflow to instantiate the tessera storage driver
add an entry to the log. It copies most of the sigstore/rekor/pkg/signer
package to enable signing checkpoints with an on-disk key file or a KMS
provider.
The TransparencyLogEntry structure uses signed integers for the LogIndex
and TreeSize values, while tessera mostly returns unsigned integers for
these values. This change incorporates a conversion mechanism. It would
probably be better to update the protobuf-specs definition to use
unsigned integers.
Release Note
Documentation
Partial #8
Fixes #9