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 pattern based from/to validity for keys #193

Closed
wants to merge 10 commits into from
Closed

Add pattern based from/to validity for keys #193

wants to merge 10 commits into from

Conversation

kipz
Copy link
Contributor

@kipz 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

Copy link

codecov bot commented Oct 8, 2024

Codecov Report

Attention: Patch coverage is 68.75000% with 30 lines in your changes missing coverage. Please review.

Project coverage is 69.90%. Comparing base (2d7f6ca) to head (ca5f535).

Files with missing lines Patch % Lines
attestation/verify.go 65.21% 12 Missing and 12 partials ⚠️
tlog/mock.go 77.77% 2 Missing and 2 partials ⚠️
signerverifier/parse.go 0.00% 2 Missing ⚠️
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     
Flag Coverage Δ
unittests 69.90% <68.75%> (-0.17%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

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

@kipz kipz changed the title Add Rego Input parameter and per repo key validity support Add pattern based from/to validity for keys Oct 9, 2024
@kipz kipz marked this pull request as ready for review October 9, 2024 11:33
@kipz kipz requested a review from a team as a code owner October 9, 2024 11:33
Comment on lines 141 to 154
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
}
}
Copy link
Member

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?

Copy link
Contributor Author

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
Comment on lines 328 to 329
for i := range opts.Keys {
key := opts.Keys[i]
Copy link
Member

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

Suggested change
for i := range opts.Keys {
key := opts.Keys[i]
for _, key := range opts.Keys {

Is this a linting rule?

policy/rego.go Outdated
Comment on lines 365 to 368
if parsedPlatform.Equals(*platform) {
expiries = append(expiries, expiry)
break
}
Copy link
Member

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?

Copy link
Contributor Author

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 :)

// 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
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// 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)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed.

policy/types.go Show resolved Hide resolved
Copy link
Contributor

@mrjoelkamp mrjoelkamp left a 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.

Comment on lines -120 to 128
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",
},
Copy link
Contributor

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?

Copy link
Contributor Author

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.

publicKey crypto.PublicKey
expired bool
Expiries []*KeyExpiry `json:"expiries,omitempty" yaml:"expiries,omitempty"`
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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....

Comment on lines 132 to 133
// if we get here, and no expirey match the image, the key is expired
km.expired = true
Copy link
Contributor

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?

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

if len(expiry.Platforms) == 0 {
km.To = expiry.To
km.From = expiry.From
km.expired = false
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 we know that expired = false here without calculating if the expiry.To time is less than the current time?

Copy link
Contributor Author

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....

@mrjoelkamp
Copy link
Contributor

I think there should probably be updates to README.md to cover this

@kipz kipz marked this pull request as draft October 10, 2024 10:41
@kipz
Copy link
Contributor Author

kipz commented Oct 10, 2024

@jonnystoten @mrjoelkamp moved back to draft while we settle on the approach.

Changed:

  • move logic into VerifyOptions and KeyMetadata receivers
  • Delay range matching until log entry integration time checking (cleans up the abstraction - e.g. removes that expired flag whic was confusing)
  • expiries -> validity (a bit better, but maybe not there yet). validity_ranges?
  • much more pre-checking of the to/from ranges to ensure that each range is within the key's to/from
  • allow only one matching validity range for image (or fail)

TODO:

  • many more tests around date ranges
  • README update as requested

@kipz
Copy link
Contributor Author

kipz commented Oct 10, 2024

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.

@kipz kipz closed this Oct 10, 2024
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.

3 participants