-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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 support for forwarded Tls-Client-Cert #17272
Conversation
Greetings, @JasonN3! Thanks for your submission. Have you had a chance to look at our Contribution Guidelines? Please make sure to review and update the PR to be in compliance with these guidelines. I will follow up with a more thorough review of the PR, but figured I'd flag the minor things to get the ball rolling on this. In order to kick off CI, you'll need a changelog entry. The CATEGORY seems like Other than that, a bit more description would help provide a bit more context for a review. Particularly interested in:
Please let us know if you have any questions. |
@JasonN3 To clarify @mpalmi questions a little (if I may...) I think I read #12178 as applying to the CertAuth plugin, whereas this appears to help Agents' mutual TLS. I think the former only needs to have the mount tuning enabled, and then read the request headers rather than the connection TLS cert. It'd be great to get clarity as to use case. The other question I have is of precedence. Sometimes you'll want the LB to do mutual TLS on both ends of the connection (mTLS to client and mTLS to Vault)... But obviously there's issues with defaulting to the header by default over the mTLS value (if it is end-client supplied, this is bad). It might also warrant a discussion of if the header-provided cert should be re-verified by Vault rather than assumed to be already validated by the LB/... |
Deployment failed with the following error:
|
@mpalmi, I hadn't read that. I've updated my description and added the changelog entry. Let me know if I need to add more detail to either one or if there's anything else I missed. @cipherboy, the intent for that issue was to allow client certificate authentication through a reverse proxy. To expand on the use case and the reason I submitted that issue initially, I have a CA server that each of my servers gets a certificate from. I want my servers to be able to query Vault to get a secret automatically using that certificate as authentication. Prior to this change, I could not do that because my Vault server is behind a LB. The LB would terminate the SSL connection and start a new connection. This would cause the client cert to be in the header X-Forwarded-Tls-Client-Cert, which wasn't being read by the cert authentication method. |
Ah, so I see, this does indeed do client cert now that I trace it. A test might be good in that regard ;-) I was thinking https://www.vaultproject.io/docs/agent/autoauth/methods/cert was it, but I believe this just allows Agent to interpose and provide its own cert for cert auth, rather than using the client's (and passing that through for Vault). |
I'm not sure what test can be added. In order to test just the header being read and moved, it would need a complete certificate since the certificate is being parsed and I'm not sure if there's one available during the testing. Since this change enables cert auth to function with the Vault server behind a LB, it would allow for auto-auth using the cert method as well. Nothing would change on the client side. When the client makes the request to the LB, the LB rewrites the request. This change is just to allow the Vault server to rewrite the request back to what the client originally made before being processed. |
@JasonN3 I'd probably add one in the cert auth tests: https://github.com/hashicorp/vault/blob/main/builtin/credential/cert/backend_test.go There's a lot of test certificates available to use, and when you call Line 1143 in 87801ec
Line 1472 in 87801ec
You'll have to add a header to the client and invalidate the token, e.g.: vault/builtin/logical/pki/backend_test.go Lines 5214 to 5215 in 87801ec
Line 970 in 87801ec
And then re-auth with the header, probably by direct My 2c. :-) |
I ended up just copying the test certificate from those test and creating 2 tests that are purely focused on the modified code. Verification that the certificate is valid was not part of this modification as that is done elsewhere using the certificates normally provided in r.TLS.PeerCertificates by the client. |
Thanks @JasonN3! I'll take another look by Friday, feel free to ping me if I haven't gotten around to it... |
I found a minor logic error that shouldn't affect anything, but I fixed it anyway. Also, @cipherboy, ping |
\o sorry about the delay @JasonN3. I think we're going to need to discuss this a little more internally first. We've definitely talked about the idea before and would like to include the feature, but I think we need to have a few more conversations about implementation first. In particular, I think we'd worried about header use vs connection use, and what the final deployment architecture looks like (how would you ensure clients can't directly connect to the Vault system and provide header values, would you do a separate mTLS to the LB, &c). I'll let you know what comes of those discussions, they'll hopefully occur in the next couple of weeks. |
I understand the need to discuss it further. If there's any concerns, feel free to post them. I might have an answer for you. As for a client directly connecting to the Vault server and providing the header, that's not possible unless the server owner specifies XForwardedForAuthorizedAddrs to include the client source addresses or the LB is configured to forward that header when received. This is the same limitations that exist for overriding the source address using X-Forwarded-For. Using the provided change, a TLS connection between the LB and the server is still required as the client certificate auth method mandates a TLS connection. It just won't use mTLS because the LB doesn't have a client certificate. Even if you were to add in mTLS between the LB and the Vault server, it wouldn't change anything the client could do. All that mTLS connection would do is verify the LB but then you still need to verify the client certificate that was passed in the header. The LB can't use the client certificate that was passed to it by the client because the LB doesn't have the private key. The process it goes through:
|
@cipherboy, how'd the internal discussions go? Are there any changes you guys would like me to make in order to better meet any requirements? |
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 about the delay. We were initially worried about whether or not useful validation would occur on the chain, but we've been unable to reproduce the attack we were thinking of. (Could you send an unrelated chain in addition to a cert you control, wherein the unrelated chain was a valid trusted chain by Vault, but the cert you controlled would sign the TLS handshake).
I think ideally, we'd like to see the LB's credentials persisted rather than potentially overwritten here, to allow us to do LB credential pinning in the future. I think as such, it makes sense to put this directly on the request, rather than on the req.TLS.PeerCertificates
object, to allow plugins and operators to decide which they want to use.
http/handler.go
Outdated
client_certs = append(client_certs, cert) | ||
} | ||
} | ||
r.TLS.PeerCertificates = client_certs |
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.
Note that, if no TLS was used between the LB and us, r.TLS
will be nil.
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.
Cert authentication still requires TLS to be used even between the LB and server. That case shouldn't occur, but I added I added a condition for that anyway in 7396430
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.
Right, it does currently, but if we were to implement this, we wouldn't necessarily need to require it if the LB and Vault were colocated on the same machine (e.g., over localhost). This is a useful deployment scenario with Vault Enterprise's Seal Wrap FIPS compliance, wherein Vault's TLS isn't necessarily FIPS compliant, but putting a FIPS-validated LB in front of it would be useful.
Edit to add: as it is right now, this simply panics if HTTP is used, which is less than ideal. :-)
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 condition I added in 7396430 should keep it from panicking. It will just return an error now.
Allowing non-secured traffic between the LB and the server (even if it is on localhost) would open it up to a MITM attack or traffic sniffing. Returning an error instead of allowing a potential insecure setup would be the preferred option in my opinion. Especially in situations where FIPS is involved.
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.
Allowing non-secured traffic between the LB and the server (even if it is on localhost) would open it up to a MITM attack or traffic sniffing.
Not sure this is true if traffic between the LB and server are encrypted in transit. e.g. if Vault is running in Cloud Run behind a serverless LB.
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.
Same would also apply for unix: https://developer.hashicorp.com/vault/docs/configuration/listener/unix
Having root on the box is sufficient to dump Vault's memory anyways, and so there's more interesting attack vectors than intercepting (plaintext) unix socket traffic.
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 link @joecorall referenced is for VM to VM or container to container traffic. Within a VM, no cloud is going to affect the traffic. The situation I was referencing was when you have your LB setup to offload SSL and not use SSL to the Vault process. At that point, the connection between the LB and the Vault server would be unencrypted. If either the LB or the server running Vault are compromised, you would be able to forward the traffic to your own server so you can manipulate the data or capture the unencrypted data.
As for unix sockets, that would be more difficult since you would be fighting against the Vault server to capture the data first since I'm not aware of a way for multiple processes to safely read from the same socket.
Glad to hear the attacks failed. This change shouldn't open up new forms of attacks, but it's definitely better to test it.
The certificate for the https communication is separate from the peer certificate that is used for authentication, so they can be and should be unrelated. It would be a bad idea to have a public CA trusted to validate your authentication since you can't control what certs they will sign, but you will want your https traffic to be signed by a public CA (or at least a CA the clients already trust) to validate your server. For the tests I've done, my Vault server is signed by a public CA and authentication is signed by an internal CA.
I'm confused by this. What credentials would the LB normally pass? As far as I can find, a LB can't form a mTLS connection with a backend server. It can only pass along the mTLS connection that the client tried for form with the LB or form a new TLS connection to the backend server. Do you know of any LBs that can initiate a mTLS connection to the backend server? If not, |
Right, this was the theoretical attack: can a client of the LB send a two part chain
Why can't a LB have a separate mTLS to Vault? That's a common scenario, initiating a new mTLS from LB to a backend service with its own credential, to ensure the data is still protected and authenticated (preventing non-LB from contacting backend service directly). This is perhaps more common when the service is over the network, rather than a LB running on the same instance as the backend service. See e.g., https://docs.nginx.com/nginx/admin-guide/security-controls/securing-http-traffic-upstream/ . |
I hadn't come across that situation. Normally I just see TLS between the LB and server and not mTLS. In that case, yes, it would cause some information to be lost. However, it would depend on where you plan on verifying the mTLS cert. It should probably be verified before any of the X-Forwarded headers are processed. That way there is less code run before the LB client cert would be rejected. In that case it wouldn't matter if the LB client cert is lost. Alternatively, I could prepend the client cert to the list of PeerCertificates, which is the commit I just pushed (b7a1ffe). Only the first certificate is processed for login (builtin/credential/cert/path_login.go line 230) so any additional certificates would still be available, but the current code would ignore them. |
Any update on when this might be included in a release? |
I'm looking forward to this and its good that there is support for using custom header 😄 I would like to know something, I have seen multiple guides like...
$ssl_client_escaped_cert is client certificate in PEM format url encoded. Would this work in future? I have also added... @JasonN3 Is it okay that, $ssl_client_escaped_cert was also including in the beginning I used
Here is URL encoded character list
In addition of that I used also...
I use optional_no_ca because I dont want to validate client certificate in reverse proxy, I had to use some CA cert to pass requirement of having ssl_client_certificate when using ssl_verify_client. https://nginx.org/en/docs/http/ngx_http_ssl_module.html#variables This might be useful when someone might make tutorial, how to configure this with Nginx for example. |
We are also looking forward to get this merged and would assign some developer time if needed to get this rolled. @JasonN3 nice work and thanks for keeping it up! |
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.
LGTM
Please merge? |
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.
Finally getting back around to reviewing this, sorry for the delay. Could you make one final tweak and fix the spelling around delimited in the documentation?
I just want to make sure I'm fixing what you're seeing. Is this the line you want fixed?
Do you just want it to say |
@sgmiller I think I fixed the typo you wanted. Let me know if you meant something else or if there's anything else you want changed |
I would like to make sure that this would be backported 1.14.x? |
This would be considered a new feature, and would not be backported. We're just taking one more pass on the security of this, thanks for the change @JasonN3 . |
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.
Apologize to everyone interested in this feature for the time it's taken. Approved.
@sgmiller, do you know what next steps are left? It's been a couple of weeks since your approval. |
I'm going to hit Update Branch as there was previously an issue with our I'll leave it up to @sgmiller as to when to merge this though as I've not been as close to this issue. |
@JasonN3 Do you want the honor of merging? If not I'll merge it in a couple of days. |
Doh, right. Okay, I'll merge. |
This is to allow cert auth through a reverse proxy that terminates the SSL connection.
The changes link off of the existing processes that are used to process X-Forwarded-For header and related headers. If X-Forwarded-* headers are accepted by the existing code, the existing request will be modified so the forwarded client certificate will be treated like it was the client certificate in the request. This way all existing checks still apply. This is the same process that was already being used for the remote address. Since the header that includes the forwarded certificate differs between reverse proxies, the header is configurable using the option x_forwarded_for_client_cert_header. This approach was chosen because it requires minimal changes and takes advantage of the existing code.
I was able to compile and test this using Traefik and the header X-Forwarded-Tls-Client-Cert.
Resolves: #12178