Skip to content

Commit 671ed88

Browse files
committed
fix(oidc,security): OIDC must auth before mTLS when disabled inclusive auth
1 parent b6a42ad commit 671ed88

File tree

7 files changed

+132
-5
lines changed

7 files changed

+132
-5
lines changed

docs/src/main/asciidoc/security-authentication-mechanisms.adoc

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -602,7 +602,11 @@ quarkus.http.auth.inclusive=true
602602
If the authentication is inclusive then `SecurityIdentity` created by the first authentication mechanism can be
603603
injected into the application code.
604604
For example, if both <<mutual-tls>> and basic authentication mechanism authentications are required,
605-
the <<mutual-tls>> authentication mechanism will create `SecurityIdentity` first.
605+
the <<mutual-tls>> mechanism will create `SecurityIdentity` first.
606+
607+
NOTE: The <<mutual-tls>> mechanism has the highest priority when inclusive authentication is enabled, to ensure
608+
that an injected `SecurityIdentity` always represents <<mutual-tls>> and can be used to get access to `SecurityIdentity`
609+
identities provided by other authentication mechanisms.
606610

607611
Additional `SecurityIdentity` instances can be accessed as a `quarkus.security.identities` attribute on the first
608612
`SecurityIdentity`, however, accessing these extra identities directly may not be necessary, for example,

extensions/oidc/deployment/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@
9393
<artifactId>quarkus-elytron-security-properties-file-deployment</artifactId>
9494
<scope>test</scope>
9595
</dependency>
96+
<dependency>
97+
<groupId>io.smallrye.certs</groupId>
98+
<artifactId>smallrye-certificate-generator-junit5</artifactId>
99+
<scope>test</scope>
100+
</dependency>
96101
</dependencies>
97102

98103
<build>
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package io.quarkus.oidc.test;
2+
3+
import static org.hamcrest.Matchers.is;
4+
5+
import java.io.File;
6+
7+
import jakarta.inject.Inject;
8+
import jakarta.ws.rs.GET;
9+
import jakarta.ws.rs.Path;
10+
11+
import org.jboss.shrinkwrap.api.asset.StringAsset;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.extension.RegisterExtension;
14+
15+
import io.quarkus.oidc.BearerTokenAuthentication;
16+
import io.quarkus.security.Authenticated;
17+
import io.quarkus.security.identity.SecurityIdentity;
18+
import io.quarkus.test.QuarkusDevModeTest;
19+
import io.quarkus.test.common.QuarkusTestResource;
20+
import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager;
21+
import io.quarkus.vertx.http.runtime.security.annotation.MTLSAuthentication;
22+
import io.restassured.RestAssured;
23+
import io.restassured.specification.RequestSpecification;
24+
import io.smallrye.certs.Format;
25+
import io.smallrye.certs.junit5.Certificate;
26+
import io.smallrye.certs.junit5.Certificates;
27+
28+
/**
29+
* This test ensures OIDC runs before mTLS authentication mechanism when inclusive authentication is not enabled.
30+
*/
31+
@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class)
32+
@Certificates(baseDir = "target/certs", certificates = @Certificate(name = "mtls-test", password = "secret", formats = {
33+
Format.PKCS12, Format.PEM }, client = true))
34+
public class OidcMtlsDisabledInclusiveAuthTest {
35+
36+
private static final String BASE_URL = "https://localhost:8443/mtls-bearer/";
37+
private static final String CONFIGURATION = """
38+
quarkus.tls.key-store.pem.0.cert=server.crt
39+
quarkus.tls.key-store.pem.0.key=server.key
40+
quarkus.tls.trust-store.pem.certs=ca.crt
41+
quarkus.http.ssl.client-auth=REQUIRED
42+
quarkus.http.insecure-requests=disabled
43+
quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus
44+
quarkus.oidc.client-id=quarkus-service-app
45+
quarkus.oidc.credentials.secret=secret
46+
quarkus.http.auth.proactive=false
47+
""";
48+
49+
@RegisterExtension
50+
static final QuarkusDevModeTest config = new QuarkusDevModeTest()
51+
.withApplicationRoot((jar) -> jar
52+
.addClasses(MtlsBearerResource.class)
53+
.addAsResource(new StringAsset(CONFIGURATION), "application.properties")
54+
.addAsResource(new File("target/certs/mtls-test.key"), "server.key")
55+
.addAsResource(new File("target/certs/mtls-test.crt"), "server.crt")
56+
.addAsResource(new File("target/certs/mtls-test-server-ca.crt"), "ca.crt"));
57+
58+
@Test
59+
public void testOidcHasHighestPriority() {
60+
givenWithCerts().get(BASE_URL + "only-mtls").then().statusCode(200).body(is("CN=localhost"));
61+
givenWithCerts().auth().oauth2(getAccessToken()).get(BASE_URL + "only-bearer").then().statusCode(200).body(is("alice"));
62+
// this needs to be OIDC because when inclusive auth is disabled, OIDC has higher priority
63+
givenWithCerts().auth().oauth2(getAccessToken()).get(BASE_URL + "both").then().statusCode(200).body(is("alice"));
64+
// OIDC must run first and thus authentication fails over invalid credentials
65+
givenWithCerts().auth().oauth2("invalid-token").get(BASE_URL + "both").then().statusCode(401);
66+
// mTLS authentication mechanism still runs when OIDC doesn't produce the identity
67+
givenWithCerts().get(BASE_URL + "both").then().statusCode(200).body(is("CN=localhost"));
68+
}
69+
70+
private static RequestSpecification givenWithCerts() {
71+
return RestAssured.given()
72+
.keyStore("target/certs/mtls-test-client-keystore.p12", "secret")
73+
.trustStore("target/certs/mtls-test-client-truststore.p12", "secret");
74+
}
75+
76+
private static String getAccessToken() {
77+
return KeycloakTestResourceLifecycleManager.getAccessToken("alice");
78+
}
79+
80+
@Path("mtls-bearer")
81+
public static class MtlsBearerResource {
82+
83+
@Inject
84+
SecurityIdentity securityIdentity;
85+
86+
@GET
87+
@Authenticated
88+
@Path("both")
89+
public String both() {
90+
return securityIdentity.getPrincipal().getName();
91+
}
92+
93+
@GET
94+
@MTLSAuthentication
95+
@Path("only-mtls")
96+
public String onlyMTLS() {
97+
return securityIdentity.getPrincipal().getName();
98+
}
99+
100+
@GET
101+
@BearerTokenAuthentication
102+
@Path("only-bearer")
103+
public String onlyBearer() {
104+
return securityIdentity.getPrincipal().getName();
105+
}
106+
}
107+
}

extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/InclusiveAuthValidationTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public Set<Class<? extends AuthenticationRequest>> getCredentialTypes() {
8989

9090
@Override
9191
public int getPriority() {
92-
return MtlsAuthenticationMechanism.PRIORITY + 1;
92+
return MtlsAuthenticationMechanism.INCLUSIVE_AUTHENTICATION_PRIORITY + 1;
9393
}
9494
}
9595
}

extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/AuthConfig.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ public class AuthConfig {
4343
* authentication, for example, OIDC bearer token authentication, must succeed.
4444
* In such cases, `SecurityIdentity` created by the first mechanism, mTLS, can be injected, identities created
4545
* by other mechanisms will be available on `SecurityIdentity`.
46+
* The mTLS mechanism is always the first mechanism, because its priority is elevated when inclusive authentication
47+
* is enabled.
4648
* The identities can be retrieved using utility method as in the example below:
4749
*
4850
* <pre>

extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpAuthenticator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ public int compare(HttpAuthenticationMechanism mech1, HttpAuthenticationMechanis
160160
the highest priority. Please lower priority of the '%s' authentication mechanism under '%s'.
161161
""".formatted(MtlsAuthenticationMechanism.class.getName(),
162162
topMechanism.getClass().getName(),
163-
MtlsAuthenticationMechanism.PRIORITY));
163+
MtlsAuthenticationMechanism.INCLUSIVE_AUTHENTICATION_PRIORITY));
164164
}
165165
}
166166
}

extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/MtlsAuthenticationMechanism.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
*/
1818
package io.quarkus.vertx.http.runtime.security;
1919

20+
import static io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism.DEFAULT_PRIORITY;
21+
2022
import java.security.cert.Certificate;
2123
import java.security.cert.X509Certificate;
2224
import java.util.Collections;
@@ -25,6 +27,8 @@
2527

2628
import javax.net.ssl.SSLPeerUnverifiedException;
2729

30+
import org.eclipse.microprofile.config.inject.ConfigProperty;
31+
2832
import io.netty.handler.codec.http.HttpResponseStatus;
2933
import io.quarkus.security.credential.CertificateCredential;
3034
import io.quarkus.security.identity.IdentityProviderManager;
@@ -39,10 +43,15 @@
3943
* The authentication handler responsible for mTLS client authentication
4044
*/
4145
public class MtlsAuthenticationMechanism implements HttpAuthenticationMechanism {
42-
public static final int PRIORITY = 3000;
46+
public static final int INCLUSIVE_AUTHENTICATION_PRIORITY = 3000;
4347
private static final String ROLES_MAPPER_ATTRIBUTE = "roles_mapper";
48+
private final boolean inclusiveAuthentication;
4449
private Function<X509Certificate, Set<String>> certificateToRoles = null;
4550

51+
MtlsAuthenticationMechanism(@ConfigProperty(name = "quarkus.http.auth.inclusive") boolean inclusiveAuthentication) {
52+
this.inclusiveAuthentication = inclusiveAuthentication;
53+
}
54+
4655
@Override
4756
public Uni<SecurityIdentity> authenticate(RoutingContext context,
4857
IdentityProviderManager identityProviderManager) {
@@ -86,7 +95,7 @@ public Uni<HttpCredentialTransport> getCredentialTransport(RoutingContext contex
8695

8796
@Override
8897
public int getPriority() {
89-
return PRIORITY;
98+
return inclusiveAuthentication ? INCLUSIVE_AUTHENTICATION_PRIORITY : DEFAULT_PRIORITY;
9099
}
91100

92101
void setCertificateToRolesMapper(Function<X509Certificate, Set<String>> certificateToRoles) {

0 commit comments

Comments
 (0)