-
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
Add constraints on the Common Name for certificate-based authentication #2595
Conversation
Thinking about this more, I almost wonder if we should resolve the "multiple matches" problem by allowing the client to specify which role they would like to authenticate against, like in the other credential backends. |
Really multiple matches should be multiple successful chains since if only one chain is successful the desired behavior is obvious. (That may be what you coded, don't have time to look now.) But I'm into the idea of the client specifying the desired cert name to auth against (although for backwards compatibility this shouldn't be required, so handling the multiple matches issue is still necessary). Probably globbing on a Name is good enough, although really you should check all DNS/Email SANs, not just CN. No full regexes, please...but you knew I was going to say that :-) |
Should I go ahead an also introduce explicit cert name selection in this PR, or would you like to keep that separate? I think it requires adding a new route and passing the specified cert name into For the constraints, here is my next idea:
|
If only one chain succeeds, there's no real question about what to do -- just use that chain's policies. If multiple succeed, you can either simply pick the first chain that succeeds opportunistically (which is what in theory happens now, except with a bug), or bail and say I don't know which one you want, so you need to specify it at login time. That would be a backwards-incompatible change in behavior from now. I think the right thing to do would be to fix the bug, and then also add the ability to specify the cert you want to match against. If you don't specify, opportunistically match. That's backwards compatible but also lets you pick if there are multiple matching paths.
It doesn't, just requires adding a parameter to the login path.
This seems complicated. Why require this split, why not just glob like with the PR you sent for the PKI backend?
General Internet behavior is to always allow alternate names. Is there a reason to ignore them? |
I presume you're thinking of #2517? The key difference is that I need to match that complex pattern from the 0.7.0 release blog post: |
I probably should have read #2517 more carefully before commenting. Nevermind that. I've fixed it to use go-glob. |
Move all the new tests to acceptance-capable tests instead of embedding in the CRL test
Pretty sure I addressed everything. I added the exact same version of go-glob as #2517, so it should merge cleanly. I'm not sure how much of the documentation is generated from the code. Is there anything I need to update? |
Oh, and I discovered that the basic_singleCert test was actually doing the exact same thing as basic_CA test, so I fixed it to do the expected thing. |
Hi @michaelansel , No documentation is generated from code, sadly. 😢 |
Docs + CLI support added |
CI failure references Azure. I'm fairly certain I didn't change anything that would break that test...
|
Rebase -- it's due to vendored deps update. |
(Or merge :-) ) |
no dice. same failure: https://travis-ci.org/hashicorp/vault/builds/222882326#L370 |
Oops. I can't test now but I think it's now fixed. |
3705c55
to
59060f2
Compare
59060f2
to
36f726f
Compare
Sweet. Looks like we are all clean 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.
Code changes are looking good to me. Feature wise, I wonder if required_name
can be changed to required_names
(TypeStringSlice/TypeCommaStringSlice) listing many globs. That way, globs of CN, Email addresses and Domain names can be specified, requiring any one to match.
// Check for client cert being explicitly listed in the config (and matching other constraints) | ||
if tCert.SerialNumber.Cmp(clientCert.SerialNumber) == 0 && | ||
bytes.Equal(tCert.AuthorityKeyId, clientCert.AuthorityKeyId) && | ||
b.matchesConstraints(connState.PeerCertificates[0], trustedNonCA.Certificates, trustedNonCA) { |
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.
s/connState.PeerCertificates[0]/clientCert
for _, chain := range trustedChains { // For each root chain that we matched | ||
for _, cCert := range chain { // For each cert in the matched chain | ||
if tCert.Equal(cCert) && // ParsedCert intersects with matched chain | ||
b.matchesConstraints(connState.PeerCertificates[0], chain, trust) { // validate client cert + matched chain against the config |
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.
s/connState.PeerCertificates[0]/clientCert. This would need increasing the scope of clientCert above.
I like the required_names suggestion, it makes sense. |
Done. I had originally shied away from multiple names because I thought it would be confusing (my instinct reaction was that |
@@ -39,9 +39,9 @@ func pathCerts(b *backend) *framework.Path { | |||
Must be x509 PEM encoded.`, | |||
}, | |||
|
|||
"required_name": &framework.FieldSchema{ | |||
"required_names": &framework.FieldSchema{ |
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.
You might want to use the new TypeCommaStringSlice type here.
@michaelansel The name |
@michaelansel Jeff and I were talking about this and he suggested that it might be a good idea to rename |
Totally missed the addition of
|
@michaelansel we're looking to release 0.7.1 next week, so please keep that in mind if you want to see this in that release! |
Thanks for the heads up, @jefferai! I've got the rename complete, but I'm struggling with the new data type. Commit: michaelansel@1c59f04 Failing to compile:
I think this means that the loop is iterating over |
@michaelansel The reason is that you changed it to iterate over a string, and each iterative step over a string is an individual rune, whereas before it was iterating over a split string (e.g. a |
Doesn't the new data type automatically do the comma-split? Or does it just validate that it is a comma-separated string? |
(I'm basing my understanding entirely off of this test case: https://github.com/hashicorp/vault/blob/master/logical/framework/field_data_test.go#L194-L202) |
It does, except you didn't change the |
Bingo. All fixed. Thanks for the help! |
Sounds good, LMK when you're ready for another look. |
Pretty sure this is all done at this point. |
LGTM! @vishalnayak can you give another once-over? |
While the signing chain gives you strong assertions of identity and validity, it doesn't always give you a strong definition of authorization. It is common to enforce constraints on the Common Name (and other fields) in the certificate at signing time, and then leverage those constrained fields at authorization time (standard website TLS is a perfect example). This change introduces one such constraint on the Common Name field: in addition to matching the signing chain (ensuring the validity of the certificate), we also require a matching Common Name before selecting the policies to assign.
This aligns the Cert auth backend more closely with the EC2 auth backend, in which you have both an assertion of validity (signed identity document) and an assertion of access (roles inside the identity document).
In the future (or now) we could also add similar matching for the O, OU, SANS, or specific required extensions (e.g. "must be a client cert, not a server cert"). It would seem beneficial to align this as close as possible with the signing constraints in the PKI backend, though CN is definitely unique (PKI backend handles CN prefix restriction by constraining the CN parameter in the signing request instead of in the role configuration).
Commits:
ParsedCert
sI'm not married to the idea of prefix/suffix matching, but it seemed to meet my needs (
host*.domain.parts
) and was an easy way to gain familiarity with the code. Other ideas I came up with that I'm happy to implement:re: adding explicit failure on multiple chains -- this is a change in behavior (previously, one entry was picked semi-randomly), but seems like a better failure case than the previous behavior. Ideally we would test for this in
pathCertWrite
, but I'm not sure how feasible that is. I feel like detecting this case is even more important as we add constraints that could be overlapping. I can remove it if you feel otherwise.