diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 8b362b9429ed1..f2aca372ad0b2 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -95,8 +95,9 @@ - quay.io/keycloak/keycloak:16.1.0 - + 16.1.0 + quay.io/keycloak/keycloak:${keycloak.version} + 6.0.3 2.27.2 diff --git a/docs/src/main/asciidoc/security-openid-connect-client.adoc b/docs/src/main/asciidoc/security-openid-connect-client.adoc index 43496dff798ba..ffa95d1e26aa9 100644 --- a/docs/src/main/asciidoc/security-openid-connect-client.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-client.adoc @@ -635,6 +635,31 @@ quarkus.oidc-client.credentials.jwt.subject=${apple.subject} quarkus.oidc-client.credentials.jwt.issuer=${apple.issuer} ---- +==== Mutual TLS + +Some OpenID Connect Providers may require that a client is authenticated as part of the `Mutual TLS` (`MTLS`) authentication process. + +`quarkus-oidc-client` can be configured as follows to support `MTLS`: + +[source,properties] +---- +quarkus.oidc.tls.verification=certificate-validation + +# Keystore configuration +quarkus.oidc.client.tls.key-store-file=client-keystore.jks +quarkus.oidc.client.tls.key-store-password=${key-store-password} + +# Add more keystore properties if needed: +#quarkus.oidc.client.tls.key-store-alias=keyAlias +#quarkus.oidc.client.tls.key-store-alias-password=keyAliasPassword + +# Truststore configuration +quarkus.oidc.client.tls.trust-store-file=client-truststore.jks +quarkus.oidc.client.tls.trust-store-password=${trust-store-password} +# Add more truststore properties if needed: +#quarkus.oidc.client.tls.trust-store-alias=certAlias +---- + [[integration-testing-oidc-client]] === Testing diff --git a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc index 711b65586749d..92b8adf89be69 100644 --- a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc @@ -978,7 +978,6 @@ quarkus.oidc.credentials.jwt.subject=custom-subject quarkus.oidc.credentials.jwt.issuer=custom-issuer ---- - ==== Apple POST JWT Apple OpenID Connect Provider uses a `client_secret_post` method where a secret is a JWT produced with a `private_key_jwt` authentication method but with Apple account specific issuer and subject claims. @@ -999,6 +998,31 @@ quarkus.oidc.credentials.jwt.subject=${apple.subject} quarkus.oidc.credentials.jwt.issuer=${apple.issuer} ---- +==== Mutual TLS + +Some OpenID Connect Providers may require that a client is authenticated as part of the `Mutual TLS` (`MTLS`) authentication process. + +`quarkus-oidc` can be configured as follows to support `MTLS`: + +[source,properties] +---- +quarkus.oidc.tls.verification=certificate-validation + +# Keystore configuration +quarkus.oidc.tls.key-store-file=client-keystore.jks +quarkus.oidc.tls.key-store-password=${key-store-password} + +# Add more keystore properties if needed: +#quarkus.oidc.tls.key-store-alias=keyAlias +#quarkus.oidc.tls.key-store-alias-password=keyAliasPassword + +# Truststore configuration +quarkus.oidc.tls.trust-store-file=client-truststore.jks +quarkus.oidc.tls.trust-store-password=${trust-store-password} +# Add more truststore properties if needed: +#quarkus.oidc.tls.trust-store-alias=certAlias +---- + [[integration-testing]] === Testing diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java index 5797251f2f195..c8512c4ae35b6 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonConfig.java @@ -426,6 +426,39 @@ public enum Verification { @ConfigItem public Optional verification = Optional.empty(); + /** + * An optional key store which holds the certificate information instead of specifying separate files. + */ + @ConfigItem + public Optional keyStoreFile = Optional.empty(); + + /** + * An optional parameter to specify type of the key store file. If not given, the type is automatically detected + * based on the file name. + */ + @ConfigItem + public Optional keyStoreFileType = Optional.empty(); + + /** + * A parameter to specify the password of the key store file. If not given, the default ("password") is used. + */ + @ConfigItem(defaultValue = "password") + public String keyStorePassword; + + /** + * An optional parameter to select a specific key in the key store. When SNI is disabled, if the key store contains + * multiple + * keys and no alias is specified, the behavior is undefined. + */ + @ConfigItem + public Optional keyStoreKeyAlias = Optional.empty(); + + /** + * An optional parameter to define the password for the key, in case it's different from {@link #keyStorePassword}. + */ + @ConfigItem + public Optional keyStoreKeyPassword = Optional.empty(); + /** * An optional trust store which holds the certificate information of the certificates to trust */ @@ -444,6 +477,13 @@ public enum Verification { @ConfigItem public Optional trustStoreCertAlias = Optional.empty(); + /** + * An optional parameter to specify type of the trust store file. If not given, the type is automatically detected + * based on the file name. + */ + @ConfigItem + public Optional trustStoreFileType = Optional.empty(); + public Optional getVerification() { return verification; } diff --git a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java index faeedb3440126..e92b7881ffb38 100644 --- a/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java +++ b/extensions/oidc-common/runtime/src/main/java/io/quarkus/oidc/common/runtime/OidcCommonUtils.java @@ -130,7 +130,7 @@ public static void setHttpClientOptions(OidcCommonConfig oidcConfig, TlsConfig t .setPassword(oidcConfig.tls.getTrustStorePassword().orElse("password")) .setAlias(oidcConfig.tls.getTrustStoreCertAlias().orElse(null)) .setValue(io.vertx.core.buffer.Buffer.buffer(trustStoreData)) - .setType("JKS"); + .setType(getStoreType(oidcConfig.tls.trustStoreFileType, oidcConfig.tls.trustStoreFile.get())); options.setTrustOptions(trustStoreOptions); if (Verification.CERTIFICATE_VALIDATION == oidcConfig.tls.verification.orElse(Verification.REQUIRED)) { options.setVerifyHost(false); @@ -141,6 +141,23 @@ public static void setHttpClientOptions(OidcCommonConfig oidcConfig, TlsConfig t oidcConfig.tls.trustStoreFile.get().toString()), ex); } } + if (oidcConfig.tls.keyStoreFile.isPresent()) { + try { + byte[] keyStoreData = getFileContent(oidcConfig.tls.keyStoreFile.get()); + io.vertx.core.net.KeyStoreOptions keyStoreOptions = new KeyStoreOptions() + .setPassword(oidcConfig.tls.keyStorePassword) + .setAlias(oidcConfig.tls.keyStoreKeyAlias.orElse(null)) + .setAliasPassword(oidcConfig.tls.keyStoreKeyPassword.orElse(null)) + .setValue(io.vertx.core.buffer.Buffer.buffer(keyStoreData)) + .setType(getStoreType(oidcConfig.tls.keyStoreFileType, oidcConfig.tls.keyStoreFile.get())); + options.setKeyCertOptions(keyStoreOptions); + + } catch (IOException ex) { + throw new ConfigurationException(String.format( + "OIDC keystore file does not exist or can not be read", + oidcConfig.tls.keyStoreFile.get().toString()), ex); + } + } Optional proxyOpt = toProxyOptions(oidcConfig.getProxy()); if (proxyOpt.isPresent()) { options.setProxyOptions(proxyOpt.get()); @@ -154,6 +171,19 @@ public static void setHttpClientOptions(OidcCommonConfig oidcConfig, TlsConfig t options.setConnectTimeout((int) oidcConfig.getConnectionTimeout().toMillis()); } + private static String getStoreType(Optional fileType, Path storePath) { + if (fileType.isPresent()) { + return fileType.get().toUpperCase(); + } + final String pathName = storePath.toString(); + if (pathName.endsWith(".p12") || pathName.endsWith(".pkcs12") || pathName.endsWith(".pfx")) { + return "PKCS12"; + } else { + // assume jks + return "JKS"; + } + } + public static String getAuthServerUrl(OidcCommonConfig oidcConfig) { return removeLastPathSeparator(oidcConfig.getAuthServerUrl().get()); } diff --git a/integration-tests/oidc/pom.xml b/integration-tests/oidc/pom.xml index e4259d8420468..cfcee425cb1de 100644 --- a/integration-tests/oidc/pom.xml +++ b/integration-tests/oidc/pom.xml @@ -28,15 +28,23 @@ - io.quarkus - quarkus-test-keycloak-server + org.keycloak + keycloak-adapter-core test + + org.keycloak + keycloak-core + io.quarkus quarkus-test-security-oidc test + + org.testcontainers + testcontainers + io.quarkus quarkus-junit5 @@ -146,7 +154,7 @@ false - ${keycloak.docker.image} + ${keycloak.version} @@ -155,7 +163,7 @@ false - ${keycloak.docker.image} + ${keycloak.version} diff --git a/integration-tests/oidc/src/main/resources/application.properties b/integration-tests/oidc/src/main/resources/application.properties index 147ded03cb1c2..2dc3b881d4408 100644 --- a/integration-tests/oidc/src/main/resources/application.properties +++ b/integration-tests/oidc/src/main/resources/application.properties @@ -1,12 +1,16 @@ # Configuration file -quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/ +quarkus.oidc.auth-server-url=${quarkus.oidc.auth-server-url} quarkus.oidc.client-id=quarkus-service-app quarkus.oidc.credentials.secret=secret quarkus.oidc.token.principal-claim=email -quarkus.oidc.tls.verification=none -#quarkus.oidc.tls.verification=required -#quarkus.oidc.tls.trust-store-file=keycloak.jks -#quarkus.oidc.tls.trust-store-password=secret + +quarkus.oidc.tls.verification=certificate-validation +quarkus.oidc.tls.trust-store-file=client-truststore.jks +quarkus.oidc.tls.trust-store-password=password +quarkus.oidc.tls.key-store-file=client-keystore.jks +quarkus.oidc.tls.key-store-password=password +quarkus.native.additional-build-args=-H:IncludeResources=.*\\.jks + quarkus.http.cors=true quarkus.http.auth.basic=true diff --git a/integration-tests/oidc/src/main/resources/client-keystore.jks b/integration-tests/oidc/src/main/resources/client-keystore.jks new file mode 100644 index 0000000000000..cf6d6ba454864 Binary files /dev/null and b/integration-tests/oidc/src/main/resources/client-keystore.jks differ diff --git a/integration-tests/oidc/src/main/resources/client-truststore.jks b/integration-tests/oidc/src/main/resources/client-truststore.jks new file mode 100644 index 0000000000000..112fb9857fbd7 Binary files /dev/null and b/integration-tests/oidc/src/main/resources/client-truststore.jks differ diff --git a/integration-tests/oidc/src/main/resources/keycloak.jks b/integration-tests/oidc/src/main/resources/keycloak.jks deleted file mode 100644 index f3035286e1f32..0000000000000 Binary files a/integration-tests/oidc/src/main/resources/keycloak.jks and /dev/null differ diff --git a/integration-tests/oidc/src/main/resources/server-keystore.jks b/integration-tests/oidc/src/main/resources/server-keystore.jks new file mode 100644 index 0000000000000..d8991ddbd627d Binary files /dev/null and b/integration-tests/oidc/src/main/resources/server-keystore.jks differ diff --git a/integration-tests/oidc/src/main/resources/server-truststore.jks b/integration-tests/oidc/src/main/resources/server-truststore.jks new file mode 100644 index 0000000000000..8ec8e126507b6 Binary files /dev/null and b/integration-tests/oidc/src/main/resources/server-truststore.jks differ diff --git a/integration-tests/oidc/src/main/resources/tls.crt b/integration-tests/oidc/src/main/resources/tls.crt deleted file mode 100644 index 659ff364761e1..0000000000000 Binary files a/integration-tests/oidc/src/main/resources/tls.crt and /dev/null differ diff --git a/integration-tests/oidc/src/main/resources/tls.key b/integration-tests/oidc/src/main/resources/tls.key deleted file mode 100644 index 0cd503ce2aa4d..0000000000000 --- a/integration-tests/oidc/src/main/resources/tls.key +++ /dev/null @@ -1,32 +0,0 @@ -Bag Attributes - friendlyName: localhost - localKeyID: 54 69 6D 65 20 31 36 32 34 30 33 32 35 33 30 39 31 33 -Key Attributes: ------BEGIN PRIVATE KEY----- -MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCREYdYCTRamvG1 -/vFhAiknv8R95WV3GTMKIrJjHjYDTY8fx7kjX5JIy6y51emSBVW/jouhVCrfa12j -t+V9y66tkbEM8kQlhIldhuf3c4QTgEQHNd/4OGSGFmAYpvx+EfHILTD+5mArbhMA -FB3Cn5GfW8/4AdKotWsFEorZL2eqcbs+7qjO26CF0uV8Zk4F7vwhaenVKiWyUbaQ -8TtRPOXmtmUDaJvcBOTSJdmFxHYUsJOzojmDAMzJqu0M6sEOfoOzx8UGhYYPWLaz -kfpDOJ4Rua71usLjQGEOoeUD6xf6rpB4eKdlLPNPXlTa7JzwvBtPM6E/XXY3ZuCa -Mrx/PbA1AgMBAAECggEBAIOso2rXP/wVs9v8AmCJM43u1I1pkMWfy+IhSEYLf/9T -gNvZz0Q6VW9Z3/f2IEH4MbLj0f2nhhqxO5eFLfsWzACjw076/7wGJyELeLX01idV -P2pEDn0hwqyq1qLJv1k3NHz7+AMGXLhO+1QQ7kpfyDAbiBOWo/2aXf+Gqx0jmDbu -Ed+vRNmNpod5hOVHUjo2W500aFCcmtt2vMym713pVXfqNP6bQPAkO8VFJ7vdD63F -OIx85wcyTlTrCc0bitHaQouG3B56+T4Eg6OoVjMpFrjO4GCcqZAZyiN5QcwMEpZt -VkRCKGJfnIlNeES20I6/qURmhfkptHdJNRaD/v39cKkCgYEAzkDJ1BY+1EgyMHY0 -mRM6CiGzAbVOlW32cACYM3m6qYbM14iw2Gw2pbEmfTeuhUayRQgjOIyWEX7HzAiu -6OzI8lEXoow0ewW4E6duqHtqJy3rl2ZqYLUuVfPlhx+NcEH6cxYfknHGMCX4wUyU -pIf8Yf5qJ9zb38tqLE0bVDOt/18CgYEAtA7cy8z5g0YWmupgDKXB0n2D6XRDYl+z -8/+3PCJ9kJtNbwqREWkn7IvVQlMTCsKPME4wcvreoLedScMDIfLQNH+7F87Q6TC6 -/kOt8gvW/pkSVRXYujFK1O9KORUmuN1YHGD+rdX5T5ufA2DVWkX+Hc+5lXrHjlgY -/Eq2EnuWPOsCgYEAsgH0rxjr7ObKekzqpFqVsvzWs9i5E/qtwIii03pyAbIXxMVy -a7cpiuNTpqqR8vDLFw0o6LtdIYhcA9pSqzEBVTFrxpxfBvYuorfUp5CsU1gshqSb -lw+ICCLRrEctGP+4me80HH4ZYKDFCn9/omjDCAg9sl3JXmL/JXD+7zMTLt0CgYBD -KpQklgaxeHCwQyOnNCH0IgwWBt+oD6kyKL6yeO88BSLCfD+XLhHNhG/9+L1Oszr0 -uwYJrhlj/Hp47Hz7qfcOzmL9Q5Hcmuf2N0ro0o/Vk0YqZSbedcrDWavnVUOHjFH0 -7B20vO/uSU/s069iqF9dwYIqB43vRF+1pSz8AgwOFwKBgFGw8EkhByIzXpNX8Z9s -5nhC32vt2DgttcaSNCo0jqBUns3YgkKd1gLDppk66ZSU8xLP+TP7ge8DPpBEGERd -A/vrq2U515eqiOxu0RHOKp4cn57i+6lLpAqFz8hxkRBAPpeVNL4Yn6BHF/ouvPIW -yN6B3X4uVS/RCx4It50S8jlu ------END PRIVATE KEY----- diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java index d97629c30a20c..993d3b9ac2bd5 100644 --- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java +++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/BearerTokenAuthorizationTest.java @@ -1,7 +1,7 @@ package io.quarkus.it.keycloak; -import static io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager.getAccessToken; -import static io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager.getRefreshToken; +import static io.quarkus.it.keycloak.KeycloakXTestResourceLifecycleManager.getAccessToken; +import static io.quarkus.it.keycloak.KeycloakXTestResourceLifecycleManager.getRefreshToken; import static org.awaitility.Awaitility.await; import static org.hamcrest.Matchers.equalTo; @@ -14,14 +14,13 @@ import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager; import io.restassured.RestAssured; /** * @author Pedro Igor */ @QuarkusTest -@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) +@QuarkusTestResource(KeycloakXTestResourceLifecycleManager.class) public class BearerTokenAuthorizationTest { @Test diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java new file mode 100644 index 0000000000000..38e5901371f0d --- /dev/null +++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/KeycloakXTestResourceLifecycleManager.java @@ -0,0 +1,191 @@ +package io.quarkus.it.keycloak; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.jboss.logging.Logger; +import org.keycloak.representations.AccessTokenResponse; +import org.keycloak.representations.idm.ClientRepresentation; +import org.keycloak.representations.idm.CredentialRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.representations.idm.RoleRepresentation; +import org.keycloak.representations.idm.RolesRepresentation; +import org.keycloak.representations.idm.UserRepresentation; +import org.keycloak.util.JsonSerialization; +import org.testcontainers.containers.BindMode; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; + +import io.quarkus.test.common.QuarkusTestResourceLifecycleManager; +import io.restassured.RestAssured; +import io.restassured.specification.RequestSpecification; + +public class KeycloakXTestResourceLifecycleManager implements QuarkusTestResourceLifecycleManager { + private static final Logger LOGGER = Logger.getLogger(KeycloakXTestResourceLifecycleManager.class); + private GenericContainer keycloak; + + private static String KEYCLOAK_SERVER_URL; + private static final String KEYCLOAK_REALM = "quarkus"; + private static final String KEYCLOAK_SERVICE_CLIENT = "quarkus-service-app"; + private static final String KEYCLOAK_VERSION = System.getProperty("keycloak.version"); + + private static String CLIENT_KEYSTORE = "client-keystore.jks"; + private static String CLIENT_TRUSTSTORE = "client-truststore.jks"; + + private static String SERVER_KEYSTORE = "server-keystore.jks"; + private static String SERVER_KEYSTORE_MOUNTED_PATH = "/etc/server-keystore.jks"; + private static String SERVER_TRUSTSTORE = "server-truststore.jks"; + private static String SERVER_TRUSTSTORE_MOUNTED_PATH = "/etc/server-truststore.jks"; + + @SuppressWarnings("resource") + @Override + public Map start() { + keycloak = new GenericContainer<>("quay.io/keycloak/keycloak-x:" + KEYCLOAK_VERSION) + .withExposedPorts(8080, 8443) + .withEnv("KEYCLOAK_ADMIN", "admin") + .withEnv("KEYCLOAK_ADMIN_PASSWORD", "admin") + .waitingFor(Wait.forLogMessage(".*Keycloak.*started.*", 1)); + + keycloak = keycloak + .withClasspathResourceMapping(SERVER_KEYSTORE, SERVER_KEYSTORE_MOUNTED_PATH, BindMode.READ_ONLY) + .withClasspathResourceMapping(SERVER_TRUSTSTORE, SERVER_TRUSTSTORE_MOUNTED_PATH, BindMode.READ_ONLY) + .withCommand("build --https-client-auth=required") + .withCommand(String.format( + "start --https-client-auth=required --hostname-strict=false --hostname-strict-https=false" + + " --https-key-store-file=%s --https-trust-store-file=%s --https-trust-store-password=password", + SERVER_KEYSTORE_MOUNTED_PATH, SERVER_TRUSTSTORE_MOUNTED_PATH)); + keycloak.start(); + LOGGER.info(keycloak.getLogs()); + + KEYCLOAK_SERVER_URL = "https://localhost:" + keycloak.getMappedPort(8443); + + RealmRepresentation realm = createRealm(KEYCLOAK_REALM); + postRealm(realm); + + return Map.of("quarkus.oidc.auth-server-url", KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM); + } + + private static void postRealm(RealmRepresentation realm) { + try { + createRequestSpec().auth().oauth2(getAdminAccessToken()) + .contentType("application/json") + .body(JsonSerialization.writeValueAsBytes(realm)) + .when() + .post(KEYCLOAK_SERVER_URL + "/admin/realms").then() + .statusCode(201); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static RealmRepresentation createRealm(String name) { + RealmRepresentation realm = new RealmRepresentation(); + + realm.setRealm(name); + realm.setEnabled(true); + realm.setUsers(new ArrayList<>()); + realm.setClients(new ArrayList<>()); + realm.setAccessTokenLifespan(3); + realm.setSsoSessionMaxLifespan(3); + + RolesRepresentation roles = new RolesRepresentation(); + List realmRoles = new ArrayList<>(); + + roles.setRealm(realmRoles); + realm.setRoles(roles); + + realm.getRoles().getRealm().add(new RoleRepresentation("user", null, false)); + realm.getRoles().getRealm().add(new RoleRepresentation("admin", null, false)); + realm.getRoles().getRealm().add(new RoleRepresentation("confidential", null, false)); + + realm.getClients().add(createServiceClient(KEYCLOAK_SERVICE_CLIENT)); + + realm.getUsers().add(createUser("alice", List.of("user"))); + realm.getUsers().add(createUser("admin", List.of("user", "admin"))); + realm.getUsers().add(createUser("jdoe", List.of("user", "confidential"))); + + return realm; + } + + private static String getAdminAccessToken() { + return createRequestSpec() + .param("grant_type", "password") + .param("username", "admin") + .param("password", "admin") + .param("client_id", "admin-cli") + .when() + .post(KEYCLOAK_SERVER_URL + "/realms/master/protocol/openid-connect/token") + .as(AccessTokenResponse.class).getToken(); + } + + private static ClientRepresentation createServiceClient(String clientId) { + ClientRepresentation client = new ClientRepresentation(); + + client.setClientId(clientId); + client.setPublicClient(false); + client.setSecret("secret"); + client.setDirectAccessGrantsEnabled(true); + client.setServiceAccountsEnabled(true); + client.setEnabled(true); + + return client; + } + + private static UserRepresentation createUser(String username, List realmRoles) { + UserRepresentation user = new UserRepresentation(); + + user.setUsername(username); + user.setEnabled(true); + user.setCredentials(new ArrayList<>()); + user.setRealmRoles(realmRoles); + user.setEmail(username + "@gmail.com"); + + CredentialRepresentation credential = new CredentialRepresentation(); + + credential.setType(CredentialRepresentation.PASSWORD); + credential.setValue(username); + credential.setTemporary(false); + + user.getCredentials().add(credential); + + return user; + } + + public static String getAccessToken(String userName) { + return createRequestSpec().param("grant_type", "password") + .param("username", userName) + .param("password", userName) + .param("client_id", KEYCLOAK_SERVICE_CLIENT) + .param("client_secret", "secret") + .when() + .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token") + .as(AccessTokenResponse.class).getToken(); + } + + public static String getRefreshToken(String userName) { + return createRequestSpec().param("grant_type", "password") + .param("username", userName) + .param("password", userName) + .param("client_id", KEYCLOAK_SERVICE_CLIENT) + .param("client_secret", "secret") + .when() + .post(KEYCLOAK_SERVER_URL + "/realms/" + KEYCLOAK_REALM + "/protocol/openid-connect/token") + .as(AccessTokenResponse.class).getRefreshToken(); + } + + @Override + public void stop() { + createRequestSpec().auth().oauth2(getAdminAccessToken()) + .when() + .delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + KEYCLOAK_REALM).then().statusCode(204); + + keycloak.stop(); + } + + private static RequestSpecification createRequestSpec() { + return RestAssured.given().trustStore(CLIENT_TRUSTSTORE, "password") + .keyStore(CLIENT_KEYSTORE, "password"); + } +} diff --git a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java index a8397e06db357..50a993da34439 100644 --- a/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java +++ b/integration-tests/oidc/src/test/java/io/quarkus/it/keycloak/WebsocketOidcTestCase.java @@ -1,6 +1,6 @@ package io.quarkus.it.keycloak; -import static io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager.getAccessToken; +import static io.quarkus.it.keycloak.KeycloakXTestResourceLifecycleManager.getAccessToken; import java.net.URI; import java.util.concurrent.LinkedBlockingDeque; @@ -18,11 +18,10 @@ import io.quarkus.test.common.QuarkusTestResource; import io.quarkus.test.common.http.TestHTTPResource; import io.quarkus.test.junit.QuarkusTest; -import io.quarkus.test.keycloak.server.KeycloakTestResourceLifecycleManager; import io.quarkus.websockets.BearerTokenClientEndpointConfigurator; @QuarkusTest -@QuarkusTestResource(KeycloakTestResourceLifecycleManager.class) +@QuarkusTestResource(KeycloakXTestResourceLifecycleManager.class) public class WebsocketOidcTestCase { @TestHTTPResource("secured-hello") diff --git a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java index dd8c8eb963c43..4b1d8b4676950 100644 --- a/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java +++ b/test-framework/keycloak-server/src/main/java/io/quarkus/test/keycloak/server/KeycloakTestResourceLifecycleManager.java @@ -15,7 +15,6 @@ import org.keycloak.representations.idm.RolesRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.util.JsonSerialization; -import org.testcontainers.containers.BindMode; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; @@ -28,7 +27,6 @@ public class KeycloakTestResourceLifecycleManager implements QuarkusTestResource private GenericContainer keycloak; private static String KEYCLOAK_SERVER_URL; - private static final Boolean KEYCLOAK_TRUSTSTORE_REQUIRED = false; private static final String KEYCLOAK_REALM = System.getProperty("keycloak.realm", "quarkus"); private static final String KEYCLOAK_SERVICE_CLIENT = System.getProperty("keycloak.service.client", "quarkus-service-app"); private static final String KEYCLOAK_WEB_APP_CLIENT = System.getProperty("keycloak.web-app.client", "quarkus-web-app"); @@ -39,16 +37,8 @@ public class KeycloakTestResourceLifecycleManager implements QuarkusTestResource private static final String TOKEN_USER_ROLES = System.getProperty("keycloak.token.user-roles", "user"); private static final String TOKEN_ADMIN_ROLES = System.getProperty("keycloak.token.admin-roles", "user,admin"); - private static String KEYCLOAK_TRUSTSTORE_PATH = "keycloak.jks"; - private static String KEYCLOAK_TRUSTSTORE_SECRET = "secret"; - private static String KEYCLOAK_TLS_KEY = "tls.key"; - private static String KEYCLOAK_TLS_KEY_MOUNTED_PATH = "/etc/x509/https"; - private static String KEYCLOAK_TLS_CRT = "tls.crt"; - private static String KEYCLOAK_TLS_CRT_MOUNTED_PATH = "/etc/x509/https"; - static { - //KEYCLOAK_TRUSTSTORE_REQUIRED = Thread.currentThread().getContextClassLoader().getResource(KEYCLOAK_TLS_KEY) != null; - if (KEYCLOAK_USE_HTTPS && !KEYCLOAK_TRUSTSTORE_REQUIRED) { + if (KEYCLOAK_USE_HTTPS) { RestAssured.useRelaxedHTTPSValidation(); } } @@ -72,16 +62,6 @@ public Map start() { .withEnv("KEYCLOAK_PASSWORD", "admin") .waitingFor(Wait.forHttp("/auth").forPort(8080)); - if (KEYCLOAK_USE_HTTPS && KEYCLOAK_TRUSTSTORE_REQUIRED) { - keycloak = keycloak - .withClasspathResourceMapping(KEYCLOAK_TLS_KEY, KEYCLOAK_TLS_KEY_MOUNTED_PATH, BindMode.READ_ONLY) - .withClasspathResourceMapping(KEYCLOAK_TLS_CRT, KEYCLOAK_TLS_CRT_MOUNTED_PATH, BindMode.READ_ONLY); - //.withCopyFileToContainer(MountableFile.forClasspathResource(KEYCLOAK_TLS_KEY), - // KEYCLOAK_TLS_KEY_MOUNTED_PATH) - //.withCopyFileToContainer(MountableFile.forClasspathResource(KEYCLOAK_TLS_CRT), - // KEYCLOAK_TLS_CRT_MOUNTED_PATH); - } - keycloak.start(); if (KEYCLOAK_USE_HTTPS) { @@ -239,10 +219,6 @@ private static List getUserRoles() { } private static RequestSpecification createRequestSpec() { - RequestSpecification spec = RestAssured.given(); - if (KEYCLOAK_TRUSTSTORE_REQUIRED) { - spec = spec.trustStore(KEYCLOAK_TRUSTSTORE_PATH, KEYCLOAK_TRUSTSTORE_SECRET); - } - return spec; + return RestAssured.given(); } }