-
Notifications
You must be signed in to change notification settings - Fork 2
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 pattern based from/to validity for keys #193
Conversation
kipz
commented
Oct 8, 2024
- Add support for customizing key expiry on a per repo/platform basis
- Add support for arbitrary string/string input parameters to Rego policies
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #193 +/- ##
==========================================
- Coverage 70.06% 69.90% -0.17%
==========================================
Files 44 44
Lines 2576 2655 +79
==========================================
+ Hits 1805 1856 +51
- Misses 474 488 +14
- Partials 297 311 +14
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
attestation/verifier.go
Outdated
toMatch := false | ||
fromMatch := false | ||
// must match at least one - nil is open ended | ||
for _, filter := range keyMeta.Expiries { | ||
if filter.To == nil || (filter.To != nil && integratedTime.Before(*filter.To)) { | ||
toMatch = true | ||
} | ||
if filter.From == nil || (filter.From != nil && integratedTime.After(*filter.From)) { | ||
fromMatch = true | ||
} | ||
if toMatch && fromMatch { | ||
break | ||
} | ||
} |
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 is right - this will match if the To
of one expiry matches and the From
of another matches, even if the windows don't overlap. Does it even make sense to have more than one expiry that matches the repo/platform?
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.
Now we return the first match.
policy/rego.go
Outdated
for i := range opts.Keys { | ||
key := opts.Keys[i] |
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.
Any reason not to do
for i := range opts.Keys { | |
key := opts.Keys[i] | |
for _, key := range opts.Keys { |
Is this a linting rule?
policy/rego.go
Outdated
if parsedPlatform.Equals(*platform) { | ||
expiries = append(expiries, expiry) | ||
break | ||
} |
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 the intent here to break the outer loop, like on line 358 above? You can name the loop with a label and break label
.
Assuming I've got that right, I think this answers my previous question - if every time we append to expiries
we also break the loop over key.Expiries
, the new key.Expiries
will only ever have 0 or 1 entries. Maybe we can instead store the only match in another field so that the usage is clearer?
Along the same lines, it might be better not to break
, and instead error on multiple matches. WDYT?
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've refactored this whole abstract because it stinks :)
attestation/verifier.go
Outdated
// important to allow an empty array here so that we don't fail open | ||
// search for test 'no match should fail closed' | ||
if keyMeta.Expiries != nil { | ||
// any repo expirey still on the keys must match the times |
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.
// any repo expirey still on the keys must match the times | |
// any repo expiry still on the keys must match the times |
(there are a few of this same typo in the 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.
fixed.
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.
Have the pros and cons been considered on adding complexity to key management for this vs just managing separate keys with less code and key management complexity?
I look at this and get very uneasy about the potential for bugs in selecting the appropriate key to use for signature verification.
name: "key already revoked", | ||
name: "key was expired", | ||
keyID: keyID, | ||
pem: pem, | ||
distrust: false, | ||
from: time.Time{}, | ||
to: new(time.Time), | ||
status: "revoked", | ||
expectedError: "already revoked", | ||
expectedError: "was expired", | ||
}, |
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 looks like this test case was repurposed? can we test a revoked key still?
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 just a log message, and nothing is actually testing the status
of a key. The test is the same.
attestation/types.go
Outdated
publicKey crypto.PublicKey | ||
expired bool | ||
Expiries []*KeyExpiry `json:"expiries,omitempty" yaml:"expiries,omitempty"` |
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.
expiries
is a strange word, at first I thought it was a spelling mistake!
So this becomes an additional layer of from
/to
values on top of the key's main from
/to
value. To me this seems quite complex for key management but I am guessing that it is the only way to handle key usage for platforms and repos?
It's really less of an expiry
object or list of expiry
objects as much as it is an applicability
object that determines which artifacts this key should be used for. I think that naming would also make it more clear what the purpose of it is and separate it from the key's main from
/to
expiry.
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 up for some new words here for sure, but I don't understand your comment about complexity. The problem we're solving here is complex, so the solution needs to account for that, and I think it does that in a clear and concise 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.
I was wondering if we should deprecate the to/from at the key level and only use the expiries
(or validity_ranges
?) because with empty platforms
and a wildcard for the pattern it covers the current behaviour....
attestation/verify.go
Outdated
// if we get here, and no expirey match the image, the key is expired | ||
km.expired = true |
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.
shouldn't the top-level from
/to
date determine if the key is expired and this really just let you know that the key is applicable for image/platform provided?
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.
what would happen if we set the main to
date to rotate the key completely and one of these nested "expiry" objects overwrites the to
date? It seems that it would continue to be active.
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.
to/from at the key level is not permitted when expiries
are used. I did that to simplify the whole thing. I'm happy to change it so that these set the upper and lower bounds, but I think that could get a bit complicated.
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.
On reflection, I think you're right. I'll make the key level 'to/from' be the upper and lower bounds. That's what you'd expect I think.
attestation/verify.go
Outdated
if len(expiry.Platforms) == 0 { | ||
km.To = expiry.To | ||
km.From = expiry.From | ||
km.expired = false |
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.
how do we know that expired = false
here without calculating if the expiry.To
time is less than the current time?
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.
Time is checked later. This is just about choosing the first expiry that matches and setting that on the key. Maybe there's another abstraction here that would be less confusing....
I think there should probably be updates to README.md to cover this |
@jonnystoten @mrjoelkamp moved back to draft while we settle on the approach. Changed:
TODO:
|
As discussed in the sync today, we'll assume a batched approach to signature rollout, thus removing that would be introduced here, with hopefully only the minor inconvenience of creating and expiring different keys for each batch. |