Skip to content

Parse certificate chain CA Issuer#3985

Merged
jetstack-bot merged 8 commits intocert-manager:masterfrom
JoshVanL:parse-certificate-chain-ca
May 13, 2021
Merged

Parse certificate chain CA Issuer#3985
jetstack-bot merged 8 commits intocert-manager:masterfrom
JoshVanL:parse-certificate-chain-ca

Conversation

@JoshVanL
Copy link
Copy Markdown
Contributor

@JoshVanL JoshVanL commented May 10, 2021

The CA issuer now constructs a certificate chain after signing, and populates the CertificateRequest.Status.CA with the root most certificate if available. Correctly passes down CA certificate when chaining CA Issuers together.

This PR is branched from #3982 and should be merged first. EDIT: This should say "and that PR should be merged first" instead of "and should be merged first"

Updates SignCSRTemplate to use new ParseCertificateChain func, and passes through CA certificate when chaining CA Issuers together.

/assign @maelvls @munnerz @SgtCoDFish @jakexks

JoshVanL added 3 commits May 10, 2021 19:06
a valid flat chain. Returns a chain and optional CA

Signed-off-by: joshvanl <vleeuwenjoshua@gmail.com>
vault

Signed-off-by: joshvanl <vleeuwenjoshua@gmail.com>
runtime

Signed-off-by: joshvanl <vleeuwenjoshua@gmail.com>
@jetstack-bot jetstack-bot added the release-note Denotes a PR that will be considered when it comes time to generate release notes. label May 10, 2021
@jetstack-bot jetstack-bot added the dco-signoff: yes Indicates that all commits in the pull request have the valid DCO sign-off message. label May 10, 2021
@jetstack-bot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: JoshVanL

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@jetstack-bot jetstack-bot added approved Indicates a PR has been approved by an approver from all required OWNERS files. needs-kind Indicates a PR lacks a `kind/foo` label and requires one. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels May 10, 2021
@JoshVanL
Copy link
Copy Markdown
Contributor Author

flake
/retest

Copy link
Copy Markdown
Member

@SgtCoDFish SgtCoDFish left a comment

Choose a reason for hiding this comment

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

A few suggestions and questions from me; I think it was a great idea to split the original PR up into separate ones 👍

Subject: pkix.Name{
CommonName: "root",
},
PublicKeyAlgorithm: x509.RSA,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: should this be EC not RSA, based on rootPK above?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(actually, does this need to be specified in the template? I'd imagine it would be filled in automatically when the cert is signed, although I've not tested that)

"github.com/jetstack/cert-manager/pkg/controller"
"github.com/jetstack/cert-manager/pkg/controller/certificaterequests"
"github.com/jetstack/cert-manager/pkg/controller/certificaterequests/util"
controllertest "github.com/jetstack/cert-manager/pkg/controller/test"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: is this not a re-import duplication of the line below it?

Subject: pkix.Name{
CommonName: name,
},
PublicKeyAlgorithm: x509.RSA,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: we're passed a crypto.Signer here; is it safe to just pass RSA for the key algo? (as before, is setting this field needed at all?)


// Build root RSA CA
skRSA, err := pki.GenerateRSAPrivateKey(2048)
rootPK, err := pki.GenerateRSAPrivateKey(2048)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: could we use P-256 ECDSA keys here instead if we're refactoring the variable names?

if err != nil {
t.Error(err)
t.FailNow()
t.Fatal()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: would it be better to include the error when we call t.Fatal()?

Suggested change
t.Fatal()
t.Fatal(err)

Comment on lines +239 to +245
root := mustCreateBundle(t, nil, "root")
int1 := mustCreateBundle(t, root, "int-1")
int2 := mustCreateBundle(t, int1, "int-2")
leaf := mustCreateBundle(t, int2, "leaf")
random := mustCreateBundle(t, nil, "random")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

suggestion: would it be worth adding a test for a different branch off the same root?

e.g. using some random diagram I found, we're currently testing A->B->E->H which is good. Could we also test that ParseCertificateChain works correctly with an input set of, say, {A, B, C, E, G} which should produce A->C->G?

1*wAsNx2NFmGE6OcG2Obl-8Q

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I may have misunderstood- I thought that this scenario would error- if the input was {A, B, C, E, G} from the diagram, we would end up with a list of chain nodes {A, B, E} and {C, G} that cannot be merged and would error here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(caveat: I didn't trace through the algorithm in depth with {A, B, C, E, G}, because ultimately it'll be unit tests that check this stuff and not humans 😁 )

I guess what I'm saying is that A->C->G is a valid chain, and the tree as presented in that diagram is a totally valid PKI hierarchy/tree which could be passed to the function, but we don't have a test which checks its operation.

It's reasonable IMO to do any of the following:

  1. if we can construct two (partial or complete) chains to the same root from given input, we'll error (so {A, B, C, E, G} fails because there's A->C->G and A->B->E)
  2. or if we can construct two full chains from root to leaf we'll error (so {A, B, C, E, G, H} fails because there's A->B->E->H and A->C->G but {A, B, C, E, G} succeeds with A->C->G since A->B->E doesn't end in a leaf)
  3. or we return all chains we can construct (so {A, B, C, E, G} returns {{A->C->G}, {A->B->E},{A->B},{A->C}}
  4. or we return all complete chains we can construct (so {A, B, C, E, G} returns {{A->C->G}} and {A, B, C, E, G, H} returns {{A->B->E->H}, {A->C->G}})

I think I'd prefer 2 as an implementation, but this is quite academic because in most cases we'd only ever be passed, say {A, A, B, B, E, H} which the algorithm should handle fine.

Whichever approach we take, I think my main point is that it would be super valuable to have a test case which takes an input of {A, B, C, E, G} and one which takes input of {A, B, C, E, G, H}; if they both end up erroring that's fine.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for detailed explanation. Indeed having multiple branches in the same bundle is totally valid and is likely wanted for services with multiple intermediates; they may not even share the same roots, but do trust them. The intention here though is that we expect a single chain when a certificate is signed, i.e. the issuer returns the single chain from root to the new signed certificate.

I think managing these larger bundle trees is a separate concern, and falls under the "trust distribution" planned work (whatever that may look like). Given this, I would like to continue with 1 as the implementation. In future it may be that other functions are made to cover those other cases.

To make it clear this is the intention of this function and wouldn't be appropriate for other cases, I'm going to rename it to ParseSingleCertificateChain. Open to suggestions for better names 😄

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'll also add some tests to demonstrate that this function should fail with branching chains.

Comment on lines +311 to +327
bundle, err := ParseCertificateChainPEM(test.inputBundle)
if (err != nil) != test.expErr {
t.Errorf("unexpected error, exp=%t got=%v",
test.expErr, err)
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

question: do you think we're doing enough testing that the whitespace is correct in the output PEM we expect?

I think we're relying on the PEM output from SignCertificate in mustCreateBundle having the required \n at the end; that's probably not likely to change, but given that ParseCertificateChain is so important, would it be good to make sure in these tests that if we pass a cert with no newline at the end it's handled correctly and we don't end up with a line like:

-----END CERTIFICATE----------BEGIN CERTIFICATE-----

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hmm, I don't think this is too much of a concern. Considering we do all the checks I think this should be fine.

continue
}

// attempt to add both chain together
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nitpick: spelling

Suggested change
// attempt to add both chain together
// attempt to add both chains together

}

// attempt to add both chain together
chain, ok := chains[i].tryMergeChain(chains[j])
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

question: especially with larger RSA key sizes (4096 being common for roots, which we'll be using here) checking signatures can be quite an expensive operation, and we're potentially going to be doing it a lot in tryMergeChain for larger chains. [1]

we can maybe remove the need to do so many signature checks by ordering the nodes differently before we check signatures; we have access to the issuer and subject for every cert in the chain, and in most cases (i.e. when the chain is sane) we'll be able to order certs based on their issuer DN. we still need to check the signatures for correctness reasons, but ordering based on the DNs will save a lot of signature checks.

do you think this optimization is worth it now? I don't think it's needed for this PR (although we need to be careful of a potential performance regression here with longer chains). I could look at adding it in the future if you don't think it's required here.

[1] looking way into the future, quantum-resistant TLS will likely be even more computationally expensive.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yep, I think it is worth while thinking about and keeping an eye on. I'm not keen on pre optimising though given we don't know if this will be the limiting factor.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yeah totally reasonable, I'm sure this will perform fine as implemented 👍

//
// An error is returned if the passed bundle is not a valid flat tree chain,
// the bundle is malformed, or the chain is broken.
func ParseCertificateChain(certs []*x509.Certificate) (PEMBundle, error) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

praise: I think this will clean up a lot of stuff, and it seems super valuable to me.

Copy link
Copy Markdown
Contributor

@irbekrm irbekrm left a comment

Choose a reason for hiding this comment

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

Amazing code, I wish we did work more work like this!

I have added a comment where I think we should not modify the slice inside the for loop.

@JoshVanL JoshVanL added the kind/bug Categorizes issue or PR as related to a bug. label May 11, 2021
@jetstack-bot jetstack-bot removed the needs-kind Indicates a PR lacks a `kind/foo` label and requires one. label May 11, 2021
@james-w james-w added this to the v1.4 milestone May 12, 2021
JoshVanL added 5 commits May 12, 2021 14:12
intention better

Signed-off-by: joshvanl <vleeuwenjoshua@gmail.com>
target Secret

Signed-off-by: joshvanl <vleeuwenjoshua@gmail.com>
Signed-off-by: joshvanl <vleeuwenjoshua@gmail.com>
certificate down

Signed-off-by: joshvanl <vleeuwenjoshua@gmail.com>
Signed-off-by: joshvanl <vleeuwenjoshua@gmail.com>
@JoshVanL JoshVanL force-pushed the parse-certificate-chain-ca branch from c819d09 to 58a2531 Compare May 12, 2021 14:07
@JoshVanL
Copy link
Copy Markdown
Contributor Author

/retest

@jakexks
Copy link
Copy Markdown
Member

jakexks commented May 13, 2021

/lgtm

@jetstack-bot jetstack-bot added the lgtm Indicates that a PR is ready to be merged. label May 13, 2021
@JoshVanL JoshVanL added the kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. label May 13, 2021
@jakexks
Copy link
Copy Markdown
Member

jakexks commented May 13, 2021

/hold

@jetstack-bot jetstack-bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label May 13, 2021
@jakexks
Copy link
Copy Markdown
Member

jakexks commented May 13, 2021

/unhold

@jetstack-bot jetstack-bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label May 13, 2021
@jetstack-bot jetstack-bot merged commit 96ea5e5 into cert-manager:master May 13, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. dco-signoff: yes Indicates that all commits in the pull request have the valid DCO sign-off message. kind/bug Categorizes issue or PR as related to a bug. kind/cleanup Categorizes issue or PR as related to cleaning up code, process, or technical debt. lgtm Indicates that a PR is ready to be merged. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants