-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Add support for alternate certs with PreferredChain in ACME #3208
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
Conversation
ooops... i didn't commit any code |
cfd98f5
to
e67b97a
Compare
Fixed! |
Signed-off-by: Maartje Eyskens <maartje@eyskens.me>
e67b97a
to
90d6a54
Compare
Signed-off-by: Maartje Eyskens <maartje@eyskens.me>
Signed-off-by: Maartje Eyskens <maartje@eyskens.me>
Good e2e test seems a bit too hard as Pebble sets random CA names. Would be good to have this somehow able to do those in the future but a bit out of scope for this PR. So I added a unit test instead |
/kind feature |
/retest |
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 like you're still working on this, so sorry for the premature review, but a few comments anyway, particularly regarding the crypto fork and the go.mod replace.
@@ -4,7 +4,7 @@ go 1.12 | |||
|
|||
replace github.com/prometheus/client_golang => github.com/prometheus/client_golang v0.9.4 | |||
|
|||
replace golang.org/x/crypto => github.com/munnerz/crypto v0.0.0-20191203200931-e1844778daa5 | |||
replace golang.org/x/crypto => github.com/meyskens/crypto v0.0.0-20200821143559-6ca9aec645f0 |
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.
Sorry to be a pain, but it makes me nervous to replace this stdlib crypto library with a fork.
But if we must do it, I think:
- the fork should live in the cert-manager organization on Github
- the reason for the fork should be explained briefly here, and in more detail in the fork's README.
- make sure that the fork does not fall behind the upstream repo.
- all changes should have links to corresponding upstream issues and pull requests so that upstream can give feedback on our proposed changes.
- Ensure that we do thorough code review and testing of our changes in the fork.
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.
the fork should live in the cert-manager organization on Github
Agreed, this should have been done when we first forked this, i don't think this fork was meant to stay for long but unfortunately we have had no feedback yet on our EAB PR.
the reason for the fork should be explained briefly here, and in more detail in the fork's README.
Disagree, this causes a commit that makes it harder to sync between upstream and commit back to upstream, the fork isn't meant to stay once merged into upstream. We should add a note in the GitHub description however
make sure that the fork does not fall behind the upstream repo.
Sure thing
all changes should have links to corresponding upstream issues and pull requests so that upstream can give feedback on our proposed changes.
EAB already has this done, this change will be PRed upstream as soon as it is reviewed by our team.
Ensure that we do thorough code review and testing of our changes in the fork.
See reviewers note ;)
// if no match is found we return to the actual cert | ||
// it is a *prefered* chain after all | ||
} | ||
|
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 FetchCertAlternatives
always include the certSlice
bundle?
If not, how is a user supposed to prefer
the certSlice
chain? By omitting the PreferredChain
value? That seems unintuitive.
Also, if the user mistypes the PreferredChain
, then they get back the default chain, without any warning or errors logged.
I'd be inclined to fail the order if the PreferredChain
is not found, but at least log and /or a warning event so that the user can quickly see that the preferred chain is wrong.
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 FetchCertAlternatives always include the certSlice bundle?
If not, how is a user supposed to prefer the certSlice chain? By omitting the PreferredChain value? That seems unintuitive.
FetchCertAlternatives gives all alternative chains this can be 0 or 100. PreferredChain
picks the one with the correct root issuer. If not present it will use the "normal" one this allows the ACME server to be free to select which chain it offers in the non alternative reply
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.
Ok.
cert, err := x509.ParseCertificate(certPEM) | ||
if err != nil { | ||
return fmt.Errorf("error parsing alternate certificates: %w", err) | ||
} |
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 this return the first or the last certificate in a multi-pem bundle?
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 returns all certs in the multi-pem bundle which were in the bundle of multi multi-pem bundles... gets confusing but that's how it works
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 sorry, I didn't read the code properly. Didn't see the nested for
loop.
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 wondered if there was a helper function that could take a slice of certificates and construct a chain, with a leaf and a "root" (where root is the root of this chain rather than a global root cert).
It would return an error if the certificates in the bundle did not form a chain.
Then we could compare the CN to that of the chain root.
// PreferredChain is no guarantee that this one gets delivered by the ACME | ||
// endpoint. | ||
// For example, for Let's Encrypt's DST crosssign you would use: | ||
// "DST Root CA X3" or "ISRG Root X1" for the newer Let's Encrypt root CA. |
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.
Explain that this must match the CommonName
of the first (or is it last?) certificate in the chain, where the first certificate is the chain root CA and the last certificate is the leaf certificate.
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 returns if any cert's issuer in the chain matches it, ACME does not have to (and LE does not) give the root CA cert back only leaf and intermediate so we look at the issuer field, analog to certbot
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.
Added a note
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.
Ok. In my mind, it should only match the highest level CA certificate in the bundle and only match a CA certificate which can be chained to the leaf certificate.
Or to put it another way, you should get an error if you happen to enter the CN of the leaf certificate here.
And you should get an error if the bundle contains any certificates which are not involved in a chain of signatures leading to the leaf cert.
l.log.V(logf.TraceLevel).Info("Calling FetchCertAlternatives") | ||
|
||
ctx, cancel := context.WithTimeout(ctx, timeout) | ||
defer cancel() |
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.
Seems a bit weird to add a timeout in "logging" middleware, but I see that that is done elsewhere, so fine.
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 always has been a weird middleware... never liked it but it has it's reasons to be here
Signed-off-by: Maartje Eyskens <maartje@eyskens.me>
Signed-off-by: Maartje Eyskens <maartje@eyskens.me>
Signed-off-by: Maartje Eyskens <maartje@eyskens.me>
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.
A few more questions below.
But if you think my suggestions are overkill, or should be deferred for a later release, I'm happy to give a lgtm.
cert, err := x509.ParseCertificate(certPEM) | ||
if err != nil { | ||
return fmt.Errorf("error parsing alternate certificates: %w", err) | ||
} |
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 sorry, I didn't read the code properly. Didn't see the nested for
loop.
// PreferredChain is no guarantee that this one gets delivered by the ACME | ||
// endpoint. | ||
// For example, for Let's Encrypt's DST crosssign you would use: | ||
// "DST Root CA X3" or "ISRG Root X1" for the newer Let's Encrypt root CA. |
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.
Ok. In my mind, it should only match the highest level CA certificate in the bundle and only match a CA certificate which can be chained to the leaf certificate.
Or to put it another way, you should get an error if you happen to enter the CN of the leaf certificate here.
And you should get an error if the bundle contains any certificates which are not involved in a chain of signatures leading to the leaf cert.
cert, err := x509.ParseCertificate(certPEM) | ||
if err != nil { | ||
return fmt.Errorf("error parsing alternate certificates: %w", err) | ||
} |
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 wondered if there was a helper function that could take a slice of certificates and construct a chain, with a leaf and a "root" (where root is the root of this chain rather than a global root cert).
It would return an error if the certificates in the bundle did not form a chain.
Then we could compare the CN to that of the chain root.
// if no match is found we return to the actual cert | ||
// it is a *prefered* chain after all | ||
} | ||
|
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.
Ok.
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.
Discussed at standup where @meyskens explained that we are checking the issuer common name rather than the subject common name.
This means that the preferredChain
value will only match a bundle where there is a certificate issued by that name so it should never match the leaf certifciate.
We can improve this further by performing extra checks to ensure that we only match CA certificates and that we only match the top-level CA certificate in the chain.
I've also created an issue to document this feature #3219
/lgtm
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: meyskens, wallrj The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
What this PR does / why we need it:
The ACME API now allows for alternate certificates being fetched signed by different CAs. Let's Encrypt does this by serving both their cross signed and own CA root certificates. We currently do not support specifying a preference for one.
certbot recently added support for this with the upcoming switch to the ISRG CA, they added:
This PR adds similar behavior to cert-manager, while I didn't look at the exact checks it does and error handling it seems I wrote the exact dame thing 😺
Which issue this PR fixes:
Fixes #1700
Special notes for your reviewer:
Please also look at my fork of the Go library, we should make an upstream PR just like we did with EAB (not yet merged...)
Needs unit and e2e tests, thus WIPRelease note: