Skip to content

Commit

Permalink
TrustStore support for OIDC and OIDCClient
Browse files Browse the repository at this point in the history
  • Loading branch information
sberyozkin committed Jun 22, 2021
1 parent a5c0d77 commit 053647b
Show file tree
Hide file tree
Showing 8 changed files with 143 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ private static PolicyEnforcer createPolicyEnforcer(OidcTenantConfig oidcConfig,
if (trustAll) {
adapterConfig.setDisableTrustManager(true);
adapterConfig.setAllowAnyHostname(true);
} else if (oidcConfig.tls.trustStoreFile.isPresent()) {
adapterConfig.setTruststore(oidcConfig.tls.trustStoreFile.get().toString());
adapterConfig.setTruststorePassword(oidcConfig.tls.trustStorePassword.orElse("password"));
}
adapterConfig.setConnectionPoolSize(keycloakPolicyEnforcerConfig.connectionPoolSize);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.oidc.common.runtime;

import java.nio.file.Path;
import java.time.Duration;
import java.util.Optional;
import java.util.OptionalInt;
Expand Down Expand Up @@ -282,6 +283,18 @@ public enum Verification {
@ConfigItem
public Optional<Verification> verification = Optional.empty();

/**
* An optional trust store which holds the certificate information of the certificates to trust
*/
@ConfigItem
public Optional<Path> trustStoreFile;

/**
* A parameter to specify the password of the trust store file.
*/
@ConfigItem
public Optional<String> trustStorePassword;

public Optional<Verification> getVerification() {
return verification;
}
Expand All @@ -290,6 +303,22 @@ public void setVerification(Verification verification) {
this.verification = Optional.of(verification);
}

public Optional<Path> getTrustStoreFile() {
return trustStoreFile;
}

public void setTrustStoreFile(Optional<Path> trustStoreFile) {
this.trustStoreFile = trustStoreFile;
}

public Optional<String> getTrustStorePassword() {
return trustStorePassword;
}

public void setTrustStorePassword(Optional<String> trustStorePassword) {
this.trustStorePassword = trustStorePassword;
}

}

@ConfigGroup
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package io.quarkus.oidc.common.runtime;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.Key;
import java.security.KeyStore;
import java.security.PrivateKey;
Expand All @@ -25,6 +29,7 @@
import io.smallrye.jwt.util.ResourceUtils;
import io.vertx.core.http.HttpClientOptions;
import io.vertx.core.json.JsonObject;
import io.vertx.core.net.KeyStoreOptions;
import io.vertx.core.net.ProxyOptions;
import io.vertx.mutiny.core.MultiMap;
import io.vertx.mutiny.core.buffer.Buffer;
Expand Down Expand Up @@ -104,6 +109,19 @@ public static void setHttpClientOptions(OidcCommonConfig oidcConfig, TlsConfig t
if (trustAll) {
options.setTrustAll(true);
options.setVerifyHost(false);
} else if (oidcConfig.tls.trustStoreFile.isPresent()) {
try {
byte[] trustStoreData = getFileContent(oidcConfig.tls.trustStoreFile.get());
io.vertx.core.net.KeyStoreOptions trustStoreOptions = new KeyStoreOptions()
.setPassword(oidcConfig.tls.getTrustStorePassword().orElse("password"))
.setValue(io.vertx.core.buffer.Buffer.buffer(trustStoreData))
.setType("JKS");
options.setTrustOptions(trustStoreOptions);
} catch (IOException ex) {
throw new ConfigurationException(String.format(
"OIDC truststore file does not exist or can not be read",
oidcConfig.tls.trustStoreFile.get().toString()), ex);
}
}
Optional<ProxyOptions> proxyOpt = toProxyOptions(oidcConfig.getProxy());
if (proxyOpt.isPresent()) {
Expand Down Expand Up @@ -264,4 +282,29 @@ public static Key initClientJwtKey(OidcCommonConfig oidcConfig) {
}
return null;
}

private static byte[] getFileContent(Path path) throws IOException {
byte[] data;
final InputStream resource = Thread.currentThread().getContextClassLoader().getResourceAsStream(path.toString());
if (resource != null) {
try (InputStream is = resource) {
data = doRead(is);
}
} else {
try (InputStream is = Files.newInputStream(path)) {
data = doRead(is);
}
}
return data;
}

private static byte[] doRead(InputStream is) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int r;
while ((r = is.read(buf)) > 0) {
out.write(buf, 0, r);
}
return out.toByteArray();
}
}
6 changes: 0 additions & 6 deletions integration-tests/oidc/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,6 @@
</dependencies>

<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ quarkus.oidc.auth-server-url=${keycloak.url}/realms/quarkus/
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.http.cors=true

quarkus.http.auth.basic=true
Expand All @@ -18,4 +20,4 @@ quarkus.http.auth.permission.basic.auth-mechanism=basic

quarkus.http.auth.permission.bearer.paths=/bearer-only
quarkus.http.auth.permission.bearer.policy=authenticated
quarkus.http.auth.permission.bearer.auth-mechanism=bearer
quarkus.http.auth.permission.bearer.auth-mechanism=bearer
Binary file not shown.
32 changes: 32 additions & 0 deletions integration-tests/oidc/src/main/resources/tls.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
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: <No 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-----
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,20 @@
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.runtime.configuration.ConfigurationException;
import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
import io.restassured.RestAssured;
import io.restassured.specification.RequestSpecification;

public class KeycloakTestResourceLifecycleManager implements QuarkusTestResourceLifecycleManager {
private GenericContainer<?> keycloak;

private static String KEYCLOAK_SERVER_URL;
private static Boolean KEYCLOAK_TRUSTSTORE_REQUIRED;
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");
Expand All @@ -36,8 +39,16 @@ 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/http/tls.key";

static {
RestAssured.useRelaxedHTTPSValidation();
KEYCLOAK_TRUSTSTORE_REQUIRED = Thread.currentThread().getContextClassLoader().getResource(KEYCLOAK_TLS_KEY) != null;
if (KEYCLOAK_USE_HTTPS && !KEYCLOAK_TRUSTSTORE_REQUIRED) {
RestAssured.useRelaxedHTTPSValidation();
}
}

@SuppressWarnings("resource")
Expand All @@ -51,13 +62,19 @@ public Map<String, String> start() {
} else {
throw new ConfigurationException("Please set either 'keycloak.docker.image' or 'keycloak.version' system property");
}

keycloak = new GenericContainer<>(keycloakDockerImage)
.withExposedPorts(8080, 8443)
.withEnv("DB_VENDOR", "H2")
.withEnv("KEYCLOAK_USER", "admin")
.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);
}

keycloak.start();

if (KEYCLOAK_USE_HTTPS) {
Expand All @@ -75,11 +92,9 @@ public Map<String, String> start() {
return conf;
}

private void postRealm(RealmRepresentation realm) {
private static void postRealm(RealmRepresentation realm) {
try {
RestAssured
.given()
.auth().oauth2(getAdminAccessToken())
createRequestSpec().auth().oauth2(getAdminAccessToken())
.contentType("application/json")
.body(JsonSerialization.writeValueAsBytes(realm))
.when()
Expand Down Expand Up @@ -121,8 +136,7 @@ private static RealmRepresentation createRealm(String name) {
}

private static String getAdminAccessToken() {
return RestAssured
.given()
return createRequestSpec()
.param("grant_type", "password")
.param("username", "admin")
.param("password", "admin")
Expand Down Expand Up @@ -178,9 +192,7 @@ private static UserRepresentation createUser(String username, List<String> realm
}

public static String getAccessToken(String userName) {
return RestAssured
.given()
.param("grant_type", "password")
return createRequestSpec().param("grant_type", "password")
.param("username", userName)
.param("password", userName)
.param("client_id", KEYCLOAK_SERVICE_CLIENT)
Expand All @@ -191,9 +203,7 @@ public static String getAccessToken(String userName) {
}

public static String getRefreshToken(String userName) {
return RestAssured
.given()
.param("grant_type", "password")
return createRequestSpec().param("grant_type", "password")
.param("username", userName)
.param("password", userName)
.param("client_id", KEYCLOAK_SERVICE_CLIENT)
Expand All @@ -205,9 +215,7 @@ public static String getRefreshToken(String userName) {

@Override
public void stop() {
RestAssured
.given()
.auth().oauth2(getAdminAccessToken())
createRequestSpec().auth().oauth2(getAdminAccessToken())
.when()
.delete(KEYCLOAK_SERVER_URL + "/admin/realms/" + KEYCLOAK_REALM).then().statusCode(204);

Expand All @@ -221,4 +229,12 @@ private static List<String> getAdminRoles() {
private static List<String> getUserRoles() {
return Arrays.asList(TOKEN_USER_ROLES.split(","));
}

private static RequestSpecification createRequestSpec() {
RequestSpecification spec = RestAssured.given();
if (KEYCLOAK_TRUSTSTORE_REQUIRED) {
spec = spec.trustStore(KEYCLOAK_TRUSTSTORE_PATH, KEYCLOAK_TRUSTSTORE_SECRET);
}
return spec;
}
}

0 comments on commit 053647b

Please sign in to comment.