-
Notifications
You must be signed in to change notification settings - Fork 4.2k
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
Adding support for chained intermediate CAs in pki backend #1694
Conversation
Hi @chrishoffman , It doesn't seem to me like this needs to be separate endpoints. Is there any reason we don't simply accept a chain in the normal CA upload endpoint? |
I think you are right, we don't need the endpoints. It works in both places but I thought it was still required when you generated your private key using the I'll remove the |
I think it doesn't now, but ought to be :-D Hence the point of this PR. |
The endpoints are out. I need add some documentation to the two upload endpoints but will wait to see if there are any major changes that need to be made. |
func fetchCAInfo(req *logical.Request) (*caInfoBundle, error) { | ||
caBundle, err := fetchCABundle(req) | ||
if err != nil { | ||
return nil, errutil.InternalError{Err: fmt.Sprintf("unable to fetch CA bundle: %v", 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.
Just pass this through; the error might be Internal or User, and we don't want to lose that. The error message here isn't adding much.
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.
Ping^ :-)
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 waiting for more feedback since this was a simple one :), I knew there would be more.
result.IssuingCA = creationInfo.SigningBundle.Certificate | ||
certPath := creationInfo.SigningBundle.GetCertificatePath() | ||
result.IssuingCABytes = certPath[0].Bytes | ||
result.IssuingCA = certPath[0].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.
For safety, can you ensure that the length of certPath
is at least one here? Better to return an error than panic.
Overall this is looking pretty good. Most of my comments are cleanup, a few potential behavioral changes. |
I updated the DER format logic to return the CA chain certificate when there is only one certificate in the chain. If we decide to keep the issuing CA out of the chain, this will still add one level to the DER format and unlimited for PEM. @jefferai What do you think about the prepending the issuing CA to the chain? I'm not sure what the expectation should be. |
Weeeeeeeeeeeeeeeel... Before Apache gained support for just reading appended PEM files containing the full bundle, it had SSLCertificateChainFile, which:
Of course, that's deprecated. Then you have things like the Java keystore which form chains internally based on key relationships but you only add in one cert at a time. Generally, non-Java services these days seem to trend towards full chains specified in the file, starting with the actual server cert (and maybe with the key elsewhere). Although if you have the root CA in the file, Qualsys will ding you. I think, maybe, the right thing to do is the following. This would work across formats, with
Having it be an array makes it harder to parse on the command line, but still very easy to deal with via the native JSON API; makes it simple to reason about what to do in the DER case; and anyone not wanting the immediate CA can just start indexing at 1 instead of 0. And, I think the 99% case generally will be satisfied by I'm totally open for debate here, let me know what you think. |
Let's go with your suggestion. I can't disagree with anything you said and also believe that the pem_bundle is the majority use case. I'll try and finish this up today. |
85ae728
to
88e5e85
Compare
I feel good about the current state and now that the major changes have stabilized, I may look at adding a few more tests. Let's let it burn in for a few days. |
Sounds good. We're looking to close 0.6.2 soonish so I'll give a ping in a few days. |
Hi, this looks great, thanks, pretty much exactly what I was missing in Vault. I have one suggestion:
|
We don't support parameters on GET but this could be a special-cased name
for the endpoint (just as 'ca' currently is).
|
This also probably only makes sense as a concatenated pem bundle. We can't really support the chain with der certs due to the limitation of the format. @jefferai Should we try and get this in for 0.6.2? How do you like I did review the tests and we seem reasonably covered. I may add a few minor tweaks. |
@jefferai Ah, I was just looking at |
@chrishoffman If you can get it in for 0.6.2 that'd be neat! I think |
@stepanstipl That's handled waaaay up in the chain; it's that way because we weren't sure if all software would be happy with using the HTTP But down at the level of backend endpoints |
I settled on |
logical.ReadOperation: b.pathFetchReadCAChain, | ||
}, | ||
|
||
HelpSynopsis: pathFetchHelpSyn, |
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 text for this probably needs some changes to accommodate the new info.
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 think I need to add the ca_chain to a few other documents too.
errutil.InternalError{Err: fmt.Sprintf("Error fetching CA certificate: %s", err)} | ||
} | ||
|
||
caChain := caInfo.GetCAChain() |
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.
Should this return nil if there is no certs? Then you can easily fast-path that with a nil, nil
response which will 204
.
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 may make sense. This is only used in two places right now and should be pretty easy to transition to this 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.
Returning nil here bypasses the Response where we want to provide raw output (this is currently a raw endpoint). When I returned nil here, it fast-path to a default response of 404 with an empty error array. I think we should just keep it the way it is.
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.
Oh, you're right -- a GET will return a 404 on no data returned, unlike a POST or DELETE.
Keeping the way it is sounds fine but if there's no data we should return 204, not 200.
certs = append(certs, pem.EncodeToMemory(&block)...) | ||
} | ||
|
||
response := &logical.Response{ |
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 tweaks me a little in a consistency sense.
ca
returns a Vault response; ca/pem
returns a raw response.
crl
returns a Vault response; crl/pem
returns a raw response.
It feels like ca/chain
should return a Vault response and ca/chain/pem
should return a raw response. Although in that scenario it's probably better to just use ca_chain
and ca_chain/pem
.
The reason for having both is so that the Vault CLI (and API clients, generally) can do a read against one of the endpoints and understand how to parse it.
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.
Both ca
and ca/pam
return a raw response, ca
being in DER format. I think you were thinking about cert/ca
which returns a Vault response. Same for crl
. The reason I didn't store the ca_chain by a special serial in the cert store was because it expects the bytes of a single certificate so there was no easy way to store the slice of []byte. I also can't support the DER format since it could be multiple certificates and that is why I left the /pem
off the end.
You do bring up a valid point about API clients, which I didn't fully consider. I'll think about the best way to support the cert/ca_chain
endpoint which would be more suitable for API clients.
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 think you were thinking about cert/ca which returns a Vault response.
I was! Mea culpa. So this seems fine as-is, but it would be nice to supportcert/ca_chain
. It could just build up the chain rather than attempt to fetch one from the backend, unless this is difficult for a reason I'm not thinking of right now.
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 able to merge the fetching of the ca chain into the standard fetch method. It is a little different in how this is generated vs. the rest of the certs but I think it is 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.
Neet-o!
I know I'm requesting more, but this is close...I promise! |
Looking pretty good to me. Want to give it a couple of days to bake in your mind, or merge? |
I had a thought about the The raw endpoints can stay as they are. |
...and maybe we should return the chain for all certificates. |
I think this should be kept simple for now. Fetching is not generally (so far as I know) a common operation, it's mostly a nicety. The CA chain has the full chain, including the CA cert set in the backend, so it's really simple to just make two queries if you really need to -- and chances are if you already have the serial of the certificate, given that certificates are public, you only need one or the other. |
OK. Let me know when you want me to pull the trigger! |
I think it is ready to go. |
💥 |
Woohoo! |
Great job and many thanks! It is also now officially in the changelog as a |
Thanks! There may be a small tweak that is needed for the changelog. I've built full chains using internally generated certificates since you can |
Oh neato, so set-signed also handles chains? I didn't realize that. Will adjust! |
The current pki backend does not have the ability to provide the full trust chain for the certificate authority. The changes that were made were all additive and should maintain backwards compatibility for everyone who does not provide this information to their backend.
This pull request includes a rewrite portions of the PEM parser to support reading, storing, and verifying the entire trust chain. In doing so, the parser is a little more prescriptive about how the PEM bundles should be constructed. Previously, the parser used some assumptions to populate the certificate bundle regardless of order. Now, certificates are required to be provided in order with the leaf certificate first. This follows what the tls.Certificate package does. As a benefit to this change, some of the code relating to incorrect assumptions the parser was making have been removed.
This PR relates to this original thread.