[ca] Separate RootCA signing cert from root certs#2038
Conversation
fb78408 to
7c78bfa
Compare
7c78bfa to
eea8904
Compare
Codecov Report
@@ Coverage Diff @@
## master #2038 +/- ##
==========================================
+ Coverage 53.17% 53.93% +0.76%
==========================================
Files 111 109 -2
Lines 19656 19122 -534
==========================================
- Hits 10452 10314 -138
+ Misses 7928 7563 -365
+ Partials 1276 1245 -31Continue to review full report at Codecov.
|
|
(getting intermittent integration test failures - trying to track those down) |
|
Update: The test failures do not seem related - https://circleci.com/gh/docker/swarmkit/6536. PTAL cc @diogomonica? |
| // CanSign ensures that the signer has all the necessary elements needed to operate | ||
| func (rca *RootCA) CanSign() bool { | ||
| if rca.Cert == nil || rca.Pool == nil || rca.Signer == nil { | ||
| if rca.Pool == nil || rca.Signer == nil || rca.Signer.Cert == nil || rca.Signer.Signer == nil { |
There was a problem hiding this comment.
Perhaps it's better to check len(rca.Signer.Cert) == 0
eea8904 to
b40e7bd
Compare
diogomonica
left a comment
There was a problem hiding this comment.
Minor comments. LGTM.
| defer s.mu.Unlock() | ||
|
|
||
| rootCA, err := NewRootCA(cert, key, certExpiry, nil) | ||
| signingCert := cert |
There was a problem hiding this comment.
Comment on what is happening here?
| if err != nil { | ||
| return RootCA{}, err | ||
| if localSigner != nil && len(parsedIntermediates) > 0 { | ||
| // If a signer is provided and there are intermediates, then either the first intermediate would BE the signer CA |
| return nil, err | ||
| } | ||
|
|
||
| signer, err := local.NewSigner(priv, parsedCerts[0], cfsigner.DefaultSigAlgo(priv), SigningPolicy(certExpiry)) |
There was a problem hiding this comment.
Maybe we should refactor newLocalSigner to also receive priv, cert in this order? (instead of cert, priv)
| // If the key was loaded from disk unencrypted, but there is a passphrase set, | ||
| // ensure it is encrypted, so it doesn't hit raft in plain-text | ||
| // we don't have to check for nil, because if we couldn't pem-decode the bytes, then parsing above would have failed | ||
| keyBlock, _ := pem.Decode(keyBytes) |
There was a problem hiding this comment.
Can't wait to remove all of this env passphrase stuff. I'm sorry I added it in the first place. :(
There was a problem hiding this comment.
Not at all, could have been useful depending on which way we decided to go for secrets encryption.
b40e7bd to
a12d15d
Compare
|
@aaronlehmann review please ✌️ |
| case len(keyBytes) > 0 && len(certBytes) > 0: // possibly valid signer | ||
| default: | ||
| return nil, errors.New("must provide both a signing key and a signing cert, or neither") | ||
| } |
There was a problem hiding this comment.
I'd suggest moving this check out of the call to newLocalSigner, so we can avoid the return nil, nil case. By convention, a function that returns a pointer and an error will generally return a valid pointer if the error is nil. Violating this isn't the end of the world, but changing newLocalSigner to a function that we only call when we expect it to return a LocalSigner or an error would let us stick to this convention.
There was a problem hiding this comment.
You could move this to a separate function like possibleValidSigner if you expect to have multiple call sites for newLocalSigner in the future.
| Intermediates: intermediatePool, | ||
| } | ||
| if _, err := parsedCerts[0].Verify(opts); err != nil { | ||
| return nil, errors.Wrap(err, "error while validating Signing CA Certificate against roots and intermediates") |
There was a problem hiding this comment.
Why is Signing CA Certificate capitalized?
There was a problem hiding this comment.
I was starting to think of it as a proper noun. :) Fixing.
| // If we have no signing key, then we shouldn't pass a signing cert either (because we don't want a local | ||
| // signer at all) | ||
| signingCert := cert | ||
| if key == nil { |
| if err != nil { | ||
| return nil, errors.Wrap(err, "invalid signing CA cert") | ||
| } | ||
| if err := validateSignatureAlgorithm(parsedCerts[0]); err != nil { |
There was a problem hiding this comment.
I believe ParseCertificatesPEM can return an empty slice if you pass in whitespace only. So a bounds check is necessary here.
|
Non-blocking: I notice there's a pattern of calling |
|
@aaronlehmann there are situation where not being able to sign is fine though. |
|
Yeah, not saying the error should be bubbled up. Just saying that structuring the check of whether we can sign like this might prevent accidental uses of |
… which could be an intermediate CA. Signed-off-by: cyli <ying.li@docker.com>
|
@aaronlehmann It will probably help prevent panics if we try to refer to |
c22bbe3 to
cba18fd
Compare
…signer is defined. Signed-off-by: cyli <ying.li@docker.com>
cba18fd to
5454ec0
Compare
|
LGTM |
Since in theory, we could be signing with an intermediate certificate, which should not be used as a trust root.
This is useful both for supporting offline roots, and for supporting swarm root CA rotation (in which the root we trust is still the old root, but we will be signing with a cross-signed intermediate, which can easily be switched to the new root.
For more context on how this will be used in terms of root CA rotation, please see #2037. (or just the diff)
I made it a requirement that the signing cert has to chain up through the intermediates to the root pool, so that during root CA rotation the signing cert will actually be the cross-signed intermediate. This doesn't have to be the case (the signing cert could be a root instead) but I thought having direct relationship between the signing cert and the root certs would be easier to validate (can use
Certificate.Verify.Otherwise, the relationship to the root is indirect ; it has to have the same public key/subject as the first intermediate, and the first intermediate has to chain up to the root. But because it doesn't chain up to the root itself, we'd have to do certificate verification (expiry, maybe path len check) ourselves. Maybe this would be more flexible though, since we'd only be manually checking the signing cert and not the whole chain?