quarkus
quarkus copied to clipboard
Add RoutingContext to SecurityIdentity for mTLS authentication
Description
Hi,
I'm trying to access the current request in a SecurityIdentityAugmentor. Since injecting RoutingContext doesn't seem to work (I tried with and without @ActivateRequestContext), I'm wondering if RoutingContext can be passed in a SecurityIdentity attribute. I see it's already done in OidcIdentityProvider (here) and MpJwtValidator (here). For my use case, I'd like to have the same behavior for mTLS authentication (X509IdentityProvider). If you think it's valuable, I can try to put an MR together.
Thanks,
lorenzo
Implementation ideas
MtlsAuthenticationMechanism is already adding the RequestContext to the AuthenticationRequest attributes, I think it only needs to be forwarded to the SecurityIdentity attributes in X509IdentityProvider:
X509Certificate certificate = request.getCertificate().getCertificate();
RoutingContext routingContext = HttpSecurityUtils.getRoutingContextAttribute(request);
if (routingContext != null) {
builder.addAttribute(RoutingContext.class.getName(), routingContext);
}
QuarkusSecurityIdentity.Builder builder = QuarkusSecurityIdentity.builder()
.setPrincipal(certificate.getSubjectX500Principal())
.addCredential(request.getCertificate())
return Uni.createFrom().item(builder.build());
/cc @sberyozkin
Hi @lorenzobenvenuti Sure, please do it
It turns out that implementing this feature is harder than I thought: I didn't notice that X509IdentityProvider is defined in the quarkus-security module and it doesn't have access to RoutingContext and HttpSecurityUtils. I thought I could move attribute handling to HttpAuthenticator (that way RoutingContext will be added to SecurityIdentity attributes for all the HTTP-based authentication mechanism) but since the augmentors are invoked at the IdentityProvider level, it won't solve my issue.
I can think of different solutions, but TBH I don't like any of them.
-
Option 1: define the mappings between
AuthenticationRequestandSecurityIdentitysomewhere; for example, introducing anAttributeMapperinterface that declares aMap<String, String> getAttributeMappings()method.X509IdentityProvidercould inject aInstance<AttributeMapper>and, if present, copy all the attributes defined in the mappings. Then, the HTTP extension could add anAttributeMapperimplementation to the context, mappingquarkus.http.routing.contexttoRoutingContext.class.getName(). -
Option 2: same as above, but passing the mappings in an attribute.
MtlsAuthenticationMechanismcan add aquarkus.security.attributesattribute to theAuthenticationRequest, containing aMap<String,String>, andX509IdentityProvidercan read that attribute and copy the other attributes to theSecurityIdentity` instance -
Option 3, really ugly hack: read/write the attribute using strings to avoid requiring an explicit dependency, i.e.
/* In X509IdentityProvider */ QuarkusSecurityIdentity.builder() /* ... */ .addAttribute( "io.vertx.ext.web.RoutingContext", request.getAttribute("quarkus.http.routing.context") ) /* ... */Just kidding, I refuse to write this code :-D
For options 1 and 2, attributes could also be copied in IdentityProviderManager to share the logic across all the identity providers.
Thoughts?
Thanks,
lorenzo
Another (hack-ish) option: add this bean to the vertx-http module
@Priority(1000)
public class RoutingContextAwareX509IdentityProvider extends X509IdentityProvider {
@Override
public Uni<SecurityIdentity> authenticate(CertificateAuthenticationRequest request, AuthenticationRequestContext context) {
final Uni<SecurityIdentity> authenticate = super.authenticate(request, context);
return authenticate.onItem().transform(
it -> QuarkusSecurityIdentity.builder(it)
.addAttribute(RoutingContext.class.getName(), HttpSecurityUtils.getRoutingContextAttribute(request))
.build()
);
}
}
This is an HTTP-oriented implementation of X509IdentityProvider that will be invoked before X509IdentityProvider because of the highest priority.
@lorenzobenvenuti Hi, sorry for a delay, so may be the simplest is for your application to ship such a custom provider ?
Hi @sberyozkin yes, actually that's what we're doing right now. I was just wondering if someone else could benefit from moving this class to the framework, but probably it's a pretty unique use case (mTLS + need to access an header) and it's not worth the effort: if someone else needs something similar they can just write their own IdentityProvider.
Thanks,
lorenzo