Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions docs/src/main/asciidoc/security-authentication-mechanisms.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -352,13 +352,180 @@ quarkus.http.auth.policy.role-policy-cert.roles-allowed=user,admin <2>

Given the preceding configuration, the request is authorized if the client certificate's CN attribute is equal to `alice` or `bob` and forbidden if it is equal to `jdoe`.

[[use-cert-attrs-to-augment-identity]]
==== Using certificate attributes to augment SecurityIdentity

You can always register `SecurityIdentityAugmentor` if the automatic <<map-certificate-attributes-to-roles>> option does not suit.
Custom `SecurityIdentityAugmentor` can check the values of different client certificate attributes and augment the `SecurityIdentity` accordingly.

For more information about customizing `SecurityIdentity`, see the xref:security-customization.adoc#security-identity-customization[Security identity customization] section in the Quarkus "Security tips and tricks" guide.

[[mtls-programmatic-set-up]]
==== Set up the mutual TLS client authentication programmatically

In the <<mutual-tls>> section we configured the mutual TLS client authentication in the `application.properties` file like this:

----
quarkus.tls.tls-config-1.key-store.p12.path=server-keystore.p12
quarkus.tls.tls-config-1.key-store.p12.password=the_key_store_secret
quarkus.tls.tls-config-1.trust-store.p12.path=server-truststore.p12
quarkus.tls.tls-config-1.trust-store.p12.password=the_trust_store_secret
quarkus.http.ssl.client-auth=required <1>
quarkus.http.tls-configuration-name=tls-config-1 <2>
----
<1> Enable and require the mutual TLS client authentication.
<2> Use the `tls-config-1` TLS configuration for the HTTP server TLS communication.

The `io.quarkus.vertx.http.security.HttpSecurity` CDI event enable you to configuration the mutual TLS authentication programmatically like in the example below:

.Example TLS configuration
[source,java]
----
package org.acme.http.security;

import io.quarkus.tls.BaseTlsConfiguration;
import io.vertx.core.net.KeyCertOptions;
import io.vertx.core.net.KeyStoreOptions;
import io.vertx.core.net.SSLOptions;
import io.vertx.core.net.TrustOptions;

import java.util.Set;
import java.util.concurrent.TimeUnit;

public class MyTlsConfiguration extends BaseTlsConfiguration {

@Override
public KeyCertOptions getKeyStoreOptions() {
return new KeyStoreOptions()
.setPath("/tmp/certs/server-keystore.p12")
.setPassword("the_key_store_secret")
.setType("PKCS12");
}

@Override
public TrustOptions getTrustStoreOptions() {
return new KeyStoreOptions()
.setPath("/tmp/certs/server-truststore.jks")
.setPassword("the_trust_store_secret")
.setType("PKCS12");
}

@Override
public SSLOptions getSSLOptions() {
SSLOptions options = new SSLOptions();
options.setKeyCertOptions(getKeyStoreOptions());
options.setTrustOptions(getTrustStoreOptions());
options.setSslHandshakeTimeoutUnit(TimeUnit.SECONDS);
options.setSslHandshakeTimeout(10);
options.setEnabledSecureTransportProtocols(Set.of("TLSv1.3", "TLSv1.2"));
return options;
}

}
----

.Example mutual TLS client authentication configuration
[source,java]
----
package org.acme.http.security;

import io.quarkus.vertx.http.security.HttpSecurity;
import jakarta.enterprise.event.Observes;

public class MutualTlsClientAuthConfig {

void configure(@Observes HttpSecurity httpSecurity) {
httpSecurity.mTLS("tls-config-1", new MyTlsConfiguration()); <1>
}

}
----
<1> Enable and require the mutual TLS client authentication and use the `tls-config-1` TLS configuration for the HTTP server TLS communication.
The `tls-config-1` TLS configuration is registered in the TLS registry.

It is also possible to map certificate attributes to roles.
Let's consider the example explained in the <<map-certificate-attributes-to-roles>> section:

[source,properties]
----
quarkus.tls.tls-config-1.key-store.p12.path=server-keystore.p12
quarkus.tls.tls-config-1.key-store.p12.password=the_key_store_secret
quarkus.tls.tls-config-1.trust-store.p12.path=server-truststore.p12
quarkus.tls.tls-config-1.trust-store.p12.password=the_trust_store_secret
quarkus.http.ssl.client-auth=required
quarkus.http.tls-configuration-name=tls-config-1
quarkus.http.auth.certificate-role-properties=cert-role-mappings.properties
----

The configuration with the programmatic set up would look like this:

[source,java]
----
package org.acme.http.security;

import io.quarkus.vertx.http.security.MTLS;
import io.quarkus.vertx.http.security.HttpSecurity;
import jakarta.enterprise.event.Observes;

public class MutualTlsClientAuthConfig {

void configure(@Observes HttpSecurity httpSecurity) {
httpSecurity.mTLS(MTLS.builder()
.tls("tls-config-1", new MyTlsConfiguration())
.rolesMapping("CN-value", "user", "admin")
.build());
}

}
----

This API also enables you to use certificate to augment `SecurityIdentity`:

[source,properties]
----
quarkus.http.tls-configuration-name=tls-config-1
quarkus.tls.tls-config-1.key-store.p12.path=server-keystore.p12
quarkus.tls.tls-config-1.key-store.p12.password=the_key_store_secret
quarkus.tls.tls-config-1.trust-store.p12.path=server-truststore.jks
quarkus.tls.tls-config-1.trust-store.p12.password=the_trust_store_secret
----

[source,java]
----
package org.acme.http.security;

import io.quarkus.vertx.http.security.HttpSecurity;
import io.quarkus.vertx.http.security.MTLS;
import jakarta.enterprise.event.Observes;

import java.util.Set;

public class MutualTlsClientAuthConfig {

void configure(@Observes HttpSecurity httpSecurity) {
httpSecurity.mTLS(MTLS.builder()
.certificateToRolesMapper(x509Certificate -> { <1>
final Set<String> securityIdentityRoles;

// replace this logic with your own certificate to roles mapping
if (x509Certificate.getIssuerX500Principal() != null
&& "CN=quarkus.io".equals(x509Certificate.getIssuerX500Principal().getName())) {
securityIdentityRoles = Set.of("admin");
} else {
securityIdentityRoles = Set.of();
}
return securityIdentityRoles;
})
.build());
}

}
----
<1> In the <<use-cert-attrs-to-augment-identity>> section we have mentioned that the `SecurityIdentityAugmentor` can be used to map the client certificate attribute values to the `SecurityIdentity` roles.
The `MTLS` API allows you to define such roles mapping as well.

IMPORTANT: If Quarkus starts the HTTP server in DEV mode after a failed start, Quarkus may need to fallback to the configuration provided in the `application.properties` file. This is a known limitation.

[[other-supported-authentication-mechanisms]]
== Other supported authentication mechanisms

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -595,6 +595,7 @@ The same authorization can be required with the `@PermissionsAllowed(value = { "
* xref:security-basic-authentication.adoc#basic-auth-programmatic-set-up[Set up Basic authentication programmatically]
* xref:security-openid-connect-multitenancy.adoc#programmatic-startup[Programmatic OIDC start-up for multitenant application]
* xref:security-authentication-mechanisms.adoc#form-based-auth-programmatic-set-up[Set up Form-based authentication programmatically]
* xref:security-authentication-mechanisms.adoc#mtls-programmatic-set-up[Set up the mutual TLS client authentication programmatically]

[[standard-security-annotations]]
== Authorization using annotations
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
import io.quarkus.security.spi.ClassSecurityAnnotationBuildItem;
import io.quarkus.security.spi.RegisterClassSecurityCheckBuildItem;
import io.quarkus.security.spi.runtime.MethodDescription;
import io.quarkus.tls.deployment.spi.TlsRegistryBuildItem;
import io.quarkus.vertx.core.deployment.IgnoredContextLocalDataKeysBuildItem;
import io.quarkus.vertx.http.runtime.VertxHttpBuildTimeConfig;
import io.quarkus.vertx.http.runtime.management.ManagementInterfaceBuildTimeConfig;
Expand Down Expand Up @@ -104,7 +105,7 @@ public class HttpSecurityProcessor {
private static final DotName BASIC_AUTH_ANNOTATION_NAME = DotName.createSimple(BasicAuthentication.class);
private static final String KOTLIN_SUSPEND_IMPL_SUFFIX = "$suspendImpl";

@Consume(HttpSecurityConfigSetupComplete.class)
@Consume(HttpSecurityConfigSetupCompleteBuildItem.class)
@Produce(ServiceStartBuildItem.class)
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
Expand Down Expand Up @@ -150,13 +151,11 @@ AdditionalBeanBuildItem initMtlsClientAuth(VertxHttpBuildTimeConfig buildTimeCon
return null;
}

@Consume(RuntimeConfigSetupCompleteBuildItem.class)
@Consume(HttpSecurityConfigSetupCompleteBuildItem.class)
@BuildStep
@Record(ExecutionTime.RUNTIME_INIT)
void setMtlsCertificateRoleProperties(HttpSecurityRecorder recorder, VertxHttpBuildTimeConfig httpBuildTimeConfig) {
if (isMtlsClientAuthenticationEnabled(httpBuildTimeConfig)) {
recorder.setMtlsCertificateRoleProperties();
}
void setMtlsCertificateRoleProperties(HttpSecurityRecorder recorder) {
recorder.setMtlsCertificateRoleProperties();
}

@BuildStep(onlyIf = IsApplicationBasicAuthRequired.class)
Expand Down Expand Up @@ -287,9 +286,10 @@ void createHttpAuthenticationHandler(HttpSecurityRecorder recorder, Capabilities
}
}

@Consume(TlsRegistryBuildItem.class) // we may need to register a TLS configuration for the mTLS
@Consume(RuntimeConfigSetupCompleteBuildItem.class)
@Produce(PreRouterFinalizationBuildItem.class)
@Produce(HttpSecurityConfigSetupComplete.class)
@Produce(HttpSecurityConfigSetupCompleteBuildItem.class)
@Record(ExecutionTime.RUNTIME_INIT)
@BuildStep
void initializeHttpSecurity(Optional<HttpAuthenticationHandlerBuildItem> authenticationHandler,
Expand Down Expand Up @@ -884,7 +884,7 @@ public boolean getAsBoolean() {
}
}

static final class HttpSecurityConfigSetupComplete extends EmptyBuildItem {
static final class HttpSecurityConfigSetupCompleteBuildItem extends EmptyBuildItem {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.quarkus.vertx.http.security;

import java.util.Set;
import java.util.concurrent.TimeUnit;

import jakarta.enterprise.event.Observes;

import io.quarkus.tls.BaseTlsConfiguration;
import io.quarkus.tls.TlsConfigurationRegistry;
import io.vertx.core.net.KeyCertOptions;
import io.vertx.core.net.KeyStoreOptions;
import io.vertx.core.net.SSLOptions;
import io.vertx.core.net.TrustOptions;

public class AuthMechanismConfig {

void configure(@Observes HttpSecurity httpSecurity, TlsConfigurationRegistry tlsConfigRegistry) {
httpSecurity
.mTLS(
MTLS.builder()
.tls("cert-1")
.certificateToRolesMapper(x509Certificate -> "CN=localhost".equals(
x509Certificate.getIssuerX500Principal().getName()) ? Set.of("admin") : Set.of())
.build())
.path("/public").permit()
.path("/mtls").roles("admin");

// please note that you can configure your TLS configuration in the 'application.properties' file instead
tlsConfigRegistry.register("cert-1", new BaseTlsConfiguration() {

@Override
public KeyCertOptions getKeyStoreOptions() {
return new KeyStoreOptions()
.setPath("target/certs/mtls-test-keystore.p12")
.setPassword("secret")
.setType("PKCS12");
}

@Override
public TrustOptions getTrustStoreOptions() {
return new KeyStoreOptions()
.setPath("target/certs/mtls-test-server-truststore.p12")
.setPassword("secret")
.setType("PKCS12");
}

@Override
public SSLOptions getSSLOptions() {
SSLOptions options = new SSLOptions();
options.setKeyCertOptions(getKeyStoreOptions());
options.setTrustOptions(getTrustStoreOptions());
options.setSslHandshakeTimeoutUnit(TimeUnit.SECONDS);
options.setSslHandshakeTimeout(10);
options.setEnabledSecureTransportProtocols(Set.of("TLSv1.3", "TLSv1.2"));
return options;
}
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import io.smallrye.certs.junit5.Certificate;
import io.smallrye.certs.junit5.Certificates;
import io.smallrye.mutiny.Uni;
import io.vertx.core.http.ClientAuth;
import io.vertx.ext.web.RoutingContext;

@Certificates(baseDir = "target/certs", certificates = @Certificate(name = "mtls-test", password = "secret", formats = Format.PKCS12, client = true))
Expand All @@ -52,7 +53,6 @@ public class FluentApiAuthenticationMechanismSelectionTest {
CustomSchemeAuthenticationMechanism.class, AbstractCustomAuthenticationMechanism.class)
.addAsResource(new StringAsset("""
quarkus.http.auth.form.enabled=true
quarkus.http.ssl.client-auth=request
quarkus.http.ssl.certificate.key-store-file=server-keystore.p12
quarkus.http.ssl.certificate.key-store-password=secret
quarkus.http.ssl.certificate.trust-store-file=server-truststore.p12
Expand Down Expand Up @@ -222,6 +222,7 @@ void configure(@Observes HttpSecurity httpSecurity) {
httpSecurity
.mechanism(new CustomSchemeAuthenticationMechanism())
.basic()
.mTLS(ClientAuth.REQUEST)
.get("/form/admin").form().authorization()
.policy(identity -> "admin".equals(identity.getPrincipal().getName()))
.put("/form/admin").basic().authorization()
Expand Down
Loading
Loading