Skip to content
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 ability to obtain client certificate in Request to support implementation of Authorisation Filters via certificate principals #632

Open
adyang opened this issue Jul 14, 2021 · 3 comments

Comments

@adyang
Copy link

adyang commented Jul 14, 2021

Background

Currently, we are able to implement Mutual TLS Authentication (mTLS) via configuring the various backends (e.g. Jetty). However, once authenticated, we are not able to utilise http4k (E.g. implement our own custom Filters) to perform authorisation as the certificate (or its principal/ subject) is not passed to the Request object.

Feature Request

Add support to allow Filters to retrieve the certificate used for authentication (e.g. via the Request object, or other possible means). The certificate is available from the underlying web server abstraction, for example in Jetty, it is available via a call to the HttpServletRequest#getAttribute method, using javax.servlet.request.X509Certificate as the key.

Prior Art

Micronaut's HttpRequest has a method to retrieve the client certificate: HttpRequest#getCertificate

Future Work

With this enhancement, it will allow us to write more default Authentication Filters like CertificateAuthenticationFilter, that will allow users to configure authentication via X509Certificate principal.

@daviddenton
Copy link
Member

Thanks for the request. FYI, we have implemented MTLS on a couple of http4k projects in the past - so you do already can do it at a push. Both involve implementing custom ServerConfig to get the behaviour you want:

  1. You can use a RequestContexts and RequestContextKey to attach whatever you want to the request and then access it downstream in a Filter. This isn't particularly neat in terms of server construction because of the bleeding of the RequestContextKey between the ServerConfig and the
  2. If you just need the certificate fingerprint for auth, that can be computed and extracted from the certificate and then added to the request as a custom "X-" header.

@adyang
Copy link
Author

adyang commented Jul 15, 2021

Thanks for the suggestions!

Currently we are trying to do it by implementing our own HttpHandlerServletAdapter and then rewiring the corresponding up stream code so that we can pass our own HttpHandlerServlet to the Jetty handler, is this what you meant by 2.?

We also tried a less invasive way where we add a ServletFilter to the JettyHandler. However, the issue with this approach was that we had to deal with HttpServletRequest which does not allow us to rewrite the headers. The workaround was to return a subclass of the HttepServletRequestWrapper and override the related getHeader, getHeaders, and getHeaderNames methods to mimic adding of headers. This feels hackish, hence we are trying to find a better way.

It seems that combining this ServletFilter method with RequestContexts is what is suggested in 1 but we are not sure what to use as a key for each request (i.e. how do we obtain a RequestContextKey from a HttpServletRequest?)

Just wondering whats the best way/ workaround at the moment?

@daviddenton
Copy link
Member

daviddenton commented Jul 15, 2021

For 1 (and using SunHttp as an example) something like this works. It's not particularly pretty as it introduces some dependencies between construction of the server and the app, but it will get you there.

https://gist.github.com/daviddenton/fdc203254813f679b17dadbb5c196f1e

The key here is that you don't need to rewrite the Jetty headers - just the Http4k headers - which will be done automatically in the case above - so you just need to "import" the RequestContextKey. There is also a way to reduce the key being passed at the expense of randomness by hardcoding the identifier instead (but the store will still need to be passed):
val key = RequestContextKey.required<MyX509Cert>(store, "cert")

For 2 - a similar thing - just compute the certificate fingerprint inside your custom jetty and then add an http4k header which is passed downstream to the filters.

HTH

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants