enhance: adds truststores to the keycloak cr (#25215)

also generally correcting the misspelling trustore

closes: #24798

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2023-12-14 09:15:06 -05:00 committed by GitHub
parent c81b533cf6
commit 08751001db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 298 additions and 32 deletions

View file

@ -450,7 +450,7 @@
<xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:, <xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
then the truststore will be obtained from the deployment's classpath instead. Used for outgoing then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
HTTPS communications to the IDP server. Client making HTTPS requests need HTTPS communications to the IDP server. Client making HTTPS requests need
a way to verify the host of the server they are talking to. This is what the trustore does. a way to verify the host of the server they are talking to. This is what the truststore does.
The keystore contains one or more trusted host certificates or certificate authorities. The keystore contains one or more trusted host certificates or certificate authorities.
You can create this truststore by extracting the public certificate of the IDP's SSL keystore. You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
</xs:documentation> </xs:documentation>

View file

@ -455,7 +455,7 @@
<xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:, <xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
then the truststore will be obtained from the deployment's classpath instead. Used for outgoing then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
HTTPS communications to the IDP server. Client making HTTPS requests need HTTPS communications to the IDP server. Client making HTTPS requests need
a way to verify the host of the server they are talking to. This is what the trustore does. a way to verify the host of the server they are talking to. This is what the truststore does.
The keystore contains one or more trusted host certificates or certificate authorities. The keystore contains one or more trusted host certificates or certificate authorities.
You can create this truststore by extracting the public certificate of the IDP's SSL keystore. You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
</xs:documentation> </xs:documentation>

View file

@ -503,7 +503,7 @@
<xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:, <xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
then the truststore will be obtained from the deployment's classpath instead. Used for outgoing then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
HTTPS communications to the IDP server. Client making HTTPS requests need HTTPS communications to the IDP server. Client making HTTPS requests need
a way to verify the host of the server they are talking to. This is what the trustore does. a way to verify the host of the server they are talking to. This is what the truststore does.
The keystore contains one or more trusted host certificates or certificate authorities. The keystore contains one or more trusted host certificates or certificate authorities.
You can create this truststore by extracting the public certificate of the IDP's SSL keystore. You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
</xs:documentation> </xs:documentation>

View file

@ -503,7 +503,7 @@
<xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:, <xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
then the truststore will be obtained from the deployment's classpath instead. Used for outgoing then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
HTTPS communications to the IDP server. Client making HTTPS requests need HTTPS communications to the IDP server. Client making HTTPS requests need
a way to verify the host of the server they are talking to. This is what the trustore does. a way to verify the host of the server they are talking to. This is what the truststore does.
The keystore contains one or more trusted host certificates or certificate authorities. The keystore contains one or more trusted host certificates or certificate authorities.
You can create this truststore by extracting the public certificate of the IDP's SSL keystore. You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
</xs:documentation> </xs:documentation>

View file

@ -435,7 +435,7 @@
<xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:, <xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
then the truststore will be obtained from the deployment's classpath instead. Used for outgoing then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
HTTPS communications to the IDP server. Client making HTTPS requests need HTTPS communications to the IDP server. Client making HTTPS requests need
a way to verify the host of the server they are talking to. This is what the trustore does. a way to verify the host of the server they are talking to. This is what the truststore does.
The keystore contains one or more trusted host certificates or certificate authorities. The keystore contains one or more trusted host certificates or certificate authorities.
You can create this truststore by extracting the public certificate of the IDP's SSL keystore. You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
</xs:documentation> </xs:documentation>

View file

@ -440,7 +440,7 @@
<xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:, <xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
then the truststore will be obtained from the deployment's classpath instead. Used for outgoing then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
HTTPS communications to the IDP server. Client making HTTPS requests need HTTPS communications to the IDP server. Client making HTTPS requests need
a way to verify the host of the server they are talking to. This is what the trustore does. a way to verify the host of the server they are talking to. This is what the truststore does.
The keystore contains one or more trusted host certificates or certificate authorities. The keystore contains one or more trusted host certificates or certificate authorities.
You can create this truststore by extracting the public certificate of the IDP's SSL keystore. You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
</xs:documentation> </xs:documentation>

View file

@ -445,7 +445,7 @@
<xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:, <xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
then the truststore will be obtained from the deployment's classpath instead. Used for outgoing then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
HTTPS communications to the IDP server. Client making HTTPS requests need HTTPS communications to the IDP server. Client making HTTPS requests need
a way to verify the host of the server they are talking to. This is what the trustore does. a way to verify the host of the server they are talking to. This is what the truststore does.
The keystore contains one or more trusted host certificates or certificate authorities. The keystore contains one or more trusted host certificates or certificate authorities.
You can create this truststore by extracting the public certificate of the IDP's SSL keystore. You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
</xs:documentation> </xs:documentation>

View file

@ -531,7 +531,7 @@
<xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:, <xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
then the truststore will be obtained from the deployment's classpath instead. Used for outgoing then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
HTTPS communications to the IDP server. Client making HTTPS requests need HTTPS communications to the IDP server. Client making HTTPS requests need
a way to verify the host of the server they are talking to. This is what the trustore does. a way to verify the host of the server they are talking to. This is what the truststore does.
The keystore contains one or more trusted host certificates or certificate authorities. The keystore contains one or more trusted host certificates or certificate authorities.
You can create this truststore by extracting the public certificate of the IDP's SSL keystore. You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
</xs:documentation> </xs:documentation>

View file

@ -531,7 +531,7 @@
<xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:, <xs:documentation>The value is the file path to a keystore file. If you prefix the path with classpath:,
then the truststore will be obtained from the deployment's classpath instead. Used for outgoing then the truststore will be obtained from the deployment's classpath instead. Used for outgoing
HTTPS communications to the IDP server. Client making HTTPS requests need HTTPS communications to the IDP server. Client making HTTPS requests need
a way to verify the host of the server they are talking to. This is what the trustore does. a way to verify the host of the server they are talking to. This is what the truststore does.
The keystore contains one or more trusted host certificates or certificate authorities. The keystore contains one or more trusted host certificates or certificate authorities.
You can create this truststore by extracting the public certificate of the IDP's SSL keystore. You can create this truststore by extracting the public certificate of the IDP's SSL keystore.
</xs:documentation> </xs:documentation>

View file

@ -6,6 +6,24 @@ The Keycloak JS adapter now uses the https://webpack.js.org/guides/package-expor
Keycloak introduces an improved truststores configuration options. The Keycloak truststore is now used across the server: for outgoing connections, mTLS, database drivers and more. It's no longer needed to configure separate truststores for individual areas. To configure the truststore, you can put your truststores files or certificates in the default `conf/truststores`, or use the new `truststore-paths` config option. For details refer to the relevant https://www.keycloak.org/server/keycloak-truststore[guide]. Keycloak introduces an improved truststores configuration options. The Keycloak truststore is now used across the server: for outgoing connections, mTLS, database drivers and more. It's no longer needed to configure separate truststores for individual areas. To configure the truststore, you can put your truststores files or certificates in the default `conf/truststores`, or use the new `truststore-paths` config option. For details refer to the relevant https://www.keycloak.org/server/keycloak-truststore[guide].
== Keycloak CR Truststores
You may also take advantage of the new server-side handling of truststores via the Keycloak CR, for example:
[source,yaml]
----
spec:
truststores:
mystore:
secret:
name: mystore-secret
myotherstore:
secret:
name: myotherstore-secret
----
Currently only Secrets are supported.
= Automatic certificate management for SAML identity providers = Automatic certificate management for SAML identity providers
The SAML identity providers can now be configured to automatically download the signing certificates from the IDP entity metadata descriptor endpoint. In order to use the new feature the option `Metadata descriptor URL` should be configured in the provider (URL where the IDP metadata information with the certificates is published) and `Use metadata descriptor URL` needs to be `ON`. The certificates are automatically downloaded and cached in the `public-key-storage` SPI from that URL. The certificates can also be reloaded or imported from the admin console, using the action combo in the provider page. The SAML identity providers can now be configured to automatically download the signing certificates from the IDP entity metadata descriptor endpoint. In order to use the new feature the option `Metadata descriptor URL` should be configured in the provider (URL where the IDP metadata information with the certificates is published) and `Use metadata descriptor URL` needs to be `ON`. The certificates are automatically downloaded and cached in the `public-key-storage` SPI from that URL. The certificates can also be reloaded or imported from the admin console, using the action combo in the provider page.

View file

@ -69,5 +69,7 @@ public final class Constants {
public static final String INSECURE_DISABLE = "INSECURE-DISABLE"; public static final String INSECURE_DISABLE = "INSECURE-DISABLE";
public static final String CERTIFICATES_FOLDER = "/mnt/certificates"; public static final String CERTIFICATES_FOLDER = "/mnt/certificates";
public static final String TRUSTSTORES_FOLDER = "/opt/keycloak/conf/truststores";
public static String KEYCLOAK_HTTP_RELATIVE_PATH_KEY = "http-relative-path"; public static String KEYCLOAK_HTTP_RELATIVE_PATH_KEY = "http-relative-path";
} }

View file

@ -42,6 +42,8 @@ import org.keycloak.operator.Utils;
import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak; import org.keycloak.operator.crds.v2alpha1.deployment.Keycloak;
import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpec; import org.keycloak.operator.crds.v2alpha1.deployment.KeycloakSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret; import org.keycloak.operator.crds.v2alpha1.deployment.ValueOrSecret;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.Truststore;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TruststoreSource;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec; import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -85,10 +87,16 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
@Override @Override
public StatefulSet desired(Keycloak primary, Context<Keycloak> context) { public StatefulSet desired(Keycloak primary, Context<Keycloak> context) {
StatefulSet baseDeployment = createBaseDeployment(primary, context); StatefulSet baseDeployment = createBaseDeployment(primary, context);
TreeSet<String> allSecrets = new TreeSet<>();
if (isTlsConfigured(primary)) { if (isTlsConfigured(primary)) {
configureTLS(primary, baseDeployment); configureTLS(primary, baseDeployment, allSecrets);
}
addTruststores(primary, baseDeployment, allSecrets);
addEnvVars(baseDeployment, primary, allSecrets);
if (!allSecrets.isEmpty()) {
watchedSecrets.annotateDeployment(new ArrayList<>(allSecrets), primary, baseDeployment);
} }
addEnvVarsAndWatchSecrets(baseDeployment, primary);
StatefulSet existingDeployment = context.getSecondaryResource(StatefulSet.class).orElse(null); StatefulSet existingDeployment = context.getSecondaryResource(StatefulSet.class).orElse(null);
if (existingDeployment == null) { if (existingDeployment == null) {
@ -109,7 +117,32 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
return baseDeployment; return baseDeployment;
} }
void configureTLS(Keycloak keycloakCR, StatefulSet deployment) { private void addTruststores(Keycloak keycloakCR, StatefulSet deployment, TreeSet<String> allSecrets) {
var kcContainer = deployment.getSpec().getTemplate().getSpec().getContainers().get(0);
for (Truststore truststore : keycloakCR.getSpec().getTruststores().values()) {
// for now we'll assume only secrets, later we can support configmaps
TruststoreSource source = truststore.getSecret();
String secretName = source.getName();
var volume = new VolumeBuilder()
.withName("truststore-secret-" + secretName)
.withNewSecret()
.withSecretName(secretName)
.withOptional(source.getOptional())
.endSecret()
.build();
var volumeMount = new VolumeMountBuilder()
.withName(volume.getName())
.withMountPath(Constants.TRUSTSTORES_FOLDER + "/secret-" + secretName)
.build();
deployment.getSpec().getTemplate().getSpec().getVolumes().add(0, volume);
kcContainer.getVolumeMounts().add(0, volumeMount);
allSecrets.add(secretName);
}
}
void configureTLS(Keycloak keycloakCR, StatefulSet deployment, TreeSet<String> allSecrets) {
var kcContainer = deployment.getSpec().getTemplate().getSpec().getContainers().get(0); var kcContainer = deployment.getSpec().getTemplate().getSpec().getContainers().get(0);
var volume = new VolumeBuilder() var volume = new VolumeBuilder()
@ -127,6 +160,7 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
deployment.getSpec().getTemplate().getSpec().getVolumes().add(0, volume); deployment.getSpec().getTemplate().getSpec().getVolumes().add(0, volume);
kcContainer.getVolumeMounts().add(0, volumeMount); kcContainer.getVolumeMounts().add(0, volumeMount);
allSecrets.add(keycloakCR.getSpec().getHttpSpec().getTlsSecret());
} }
@Override @Override
@ -280,7 +314,7 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
return JGROUPS_DNS_QUERY_PARAM + KeycloakDiscoveryServiceDependentResource.getName(keycloakCR) +"." + keycloakCR.getMetadata().getNamespace(); return JGROUPS_DNS_QUERY_PARAM + KeycloakDiscoveryServiceDependentResource.getName(keycloakCR) +"." + keycloakCR.getMetadata().getNamespace();
} }
private void addEnvVarsAndWatchSecrets(StatefulSet baseDeployment, Keycloak keycloakCR) { private void addEnvVars(StatefulSet baseDeployment, Keycloak keycloakCR, TreeSet<String> allSecrets) {
var firstClasssEnvVars = distConfigurator.configureDistOptions(keycloakCR); var firstClasssEnvVars = distConfigurator.configureDistOptions(keycloakCR);
String adminSecretName = KeycloakAdminSecretDependentResource.getName(keycloakCR); String adminSecretName = KeycloakAdminSecretDependentResource.getName(keycloakCR);
@ -301,14 +335,7 @@ public class KeycloakDeploymentDependentResource extends CRUDKubernetesDependent
Log.infof("Found config secrets names: %s", serverConfigSecretsNames); Log.infof("Found config secrets names: %s", serverConfigSecretsNames);
// add secrets from volume mounts (currently just the tls secret) allSecrets.addAll(serverConfigSecretsNames);
if (isTlsConfigured(keycloakCR)) {
serverConfigSecretsNames.add(keycloakCR.getSpec().getHttpSpec().getTlsSecret());
}
if (!serverConfigSecretsNames.isEmpty()) {
watchedSecrets.annotateDeployment(new ArrayList<>(serverConfigSecretsNames), keycloakCR, baseDeployment);
}
} }
private List<EnvVar> getDefaultAndAdditionalEnvVars(Keycloak keycloakCR, String adminSecretName) { private List<EnvVar> getDefaultAndAdditionalEnvVars(Keycloak keycloakCR, String adminSecretName) {

View file

@ -25,10 +25,13 @@ import org.keycloak.operator.crds.v2alpha1.deployment.spec.HostnameSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec; import org.keycloak.operator.crds.v2alpha1.deployment.spec.HttpSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec; import org.keycloak.operator.crds.v2alpha1.deployment.spec.IngressSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec; import org.keycloak.operator.crds.v2alpha1.deployment.spec.TransactionsSpec;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.Truststore;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec; import org.keycloak.operator.crds.v2alpha1.deployment.spec.UnsupportedSpec;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
@ -84,6 +87,9 @@ public class KeycloakSpec {
@JsonPropertyDescription("In this section you can configure Keycloak hostname and related properties.") @JsonPropertyDescription("In this section you can configure Keycloak hostname and related properties.")
private HostnameSpec hostnameSpec; private HostnameSpec hostnameSpec;
@JsonPropertyDescription("In this section you can configure Keycloak truststores.")
private Map<String, Truststore> truststores = new LinkedHashMap<>();
public HttpSpec getHttpSpec() { public HttpSpec getHttpSpec() {
return httpSpec; return httpSpec;
} }
@ -182,4 +188,16 @@ public class KeycloakSpec {
public void setStartOptimized(Boolean optimized) { public void setStartOptimized(Boolean optimized) {
this.startOptimized = optimized; this.startOptimized = optimized;
} }
public Map<String, Truststore> getTruststores() {
return truststores;
}
public void setTruststores(Map<String, Truststore> truststores) {
if (truststores == null) {
truststores = new LinkedHashMap<>();
}
this.truststores = truststores;
}
} }

View file

@ -0,0 +1,50 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.operator.crds.v2alpha1.deployment.spec;
import io.fabric8.generator.annotation.Required;
import io.sundr.builder.annotations.Buildable;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Buildable(editableEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder")
public class Truststore {
@Required
private String name;
@Required
private TruststoreSource secret;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public TruststoreSource getSecret() {
return secret;
}
public void setSecret(TruststoreSource secret) {
this.secret = secret;
}
}

View file

@ -0,0 +1,49 @@
/*
* Copyright 2023 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.operator.crds.v2alpha1.deployment.spec;
import io.fabric8.generator.annotation.Required;
import io.sundr.builder.annotations.Buildable;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Buildable(editableEnabled = false, builderPackage = "io.fabric8.kubernetes.api.builder")
public class TruststoreSource {
@Required
private String name;
private Boolean optional;
public Boolean getOptional() {
return optional;
}
public void setOptional(Boolean optional) {
this.optional = optional;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}

View file

@ -0,0 +1,73 @@
/*
* Copyright 2022 Red Hat, Inc. and/or its affiliates
* and other contributors as indicated by the @author tags.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.operator.testsuite.integration;
import io.fabric8.kubernetes.api.model.Secret;
import io.fabric8.kubernetes.api.model.apps.StatefulSet;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.quarkus.test.junit.QuarkusTest;
import org.awaitility.Awaitility;
import org.junit.jupiter.api.Test;
import org.keycloak.operator.Constants;
import org.keycloak.operator.crds.v2alpha1.deployment.spec.TruststoreBuilder;
import org.keycloak.operator.testsuite.utils.K8sUtils;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.keycloak.operator.testsuite.utils.K8sUtils.deployKeycloak;
import static org.keycloak.operator.testsuite.utils.K8sUtils.getResourceFromFile;
@QuarkusTest
public class KeycloakTruststoresTests extends BaseOperatorTest {
@Test
public void testTruststoreMissing() {
var kc = getTestKeycloakDeployment(true);
var deploymentName = kc.getMetadata().getName();
kc.getSpec().getTruststores().put("xyz", new TruststoreBuilder().withName("xyz").withNewSecret().withName("xyz").endSecret().build());
deployKeycloak(k8sclient, kc, false);
Resource<StatefulSet> stsResource = k8sclient.resources(StatefulSet.class).withName(deploymentName);
Awaitility.await().ignoreExceptions().untilAsserted(() -> {
StatefulSet statefulSet = stsResource.get();
assertEquals("true",
statefulSet.getMetadata().getAnnotations().get(Constants.KEYCLOAK_MISSING_SECRETS_ANNOTATION));
assertTrue(statefulSet.getMetadata().getAnnotations().get(Constants.KEYCLOAK_WATCHING_ANNOTATION)
.contains("xyz"));
});
}
@Test
public void testTrustroreExists() {
var kc = getTestKeycloakDeployment(true);
var deploymentName = kc.getMetadata().getName();
K8sUtils.set(k8sclient, getResourceFromFile("example-truststore-secret.yaml", Secret.class));
kc.getSpec().getTruststores().put("example", new TruststoreBuilder().withName("example").withNewSecret().withName("example-truststore-secret").endSecret().build());
deployKeycloak(k8sclient, kc, true);
Resource<StatefulSet> stsResource = k8sclient.resources(StatefulSet.class).withName(deploymentName);
StatefulSet statefulSet = stsResource.get();
assertEquals("false",
statefulSet.getMetadata().getAnnotations().get(Constants.KEYCLOAK_MISSING_SECRETS_ANNOTATION));
assertTrue(statefulSet.getSpec().getTemplate().getSpec().getContainers().get(0).getVolumeMounts().stream()
.anyMatch(v -> v.getMountPath()
.equals("/opt/keycloak/conf/truststores/secret-example-truststore-secret")));
}
}

View file

@ -0,0 +1,29 @@
apiVersion: v1
kind: Secret
metadata:
name: example-truststore-secret
stringData:
cert.pem: |
-----BEGIN CERTIFICATE-----
MIIDezCCAmOgAwIBAgIEM0VH1DANBgkqhkiG9w0BAQsFADBtMQswCQYDVQQGEwJL
QzERMA8GA1UECBMIa2V5Y2xvYWsxETAPBgNVBAcTCGtleWNsb2FrMREwDwYDVQQK
EwhrZXljbG9hazERMA8GA1UECxMIa2V5Y2xvYWsxEjAQBgNVBAMTCUtleSBDbG9h
azAgFw0yMzExMDkxMjMxNDZaGA8yMDUxMDMyNjEyMzE0NlowbTELMAkGA1UEBhMC
S0MxETAPBgNVBAgTCGtleWNsb2FrMREwDwYDVQQHEwhrZXljbG9hazERMA8GA1UE
ChMIa2V5Y2xvYWsxETAPBgNVBAsTCGtleWNsb2FrMRIwEAYDVQQDEwlLZXkgQ2xv
YWswggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCv1M8ZiKSONnryBcTy
u6u0d/cJ9s0kZAvO+Q62I5W6e6r0zFYlUjheRSTF7PdypAxhJPMRVGZoE0WCKUMn
4D1E43ZC8bl8IgzpC8m2hbtJsVcbZs0H4D9jHjHvBiPgdkmTc7F/pU8jk8oD3L+Y
olzOFLI/wpwC7kx9O1bzXVpTKOg12JqrKRHIfdvU4mbMlRTRX+AX6Iw5QMiy8vfA
77uCkkxPInx18Z4MU6vqjvAWXos5gQE1fVUyHsmWlBp8Eis7jD7d52Th1uK3nJLo
4wnKzNXDXq34o25xR6j1BpU+v8iqkcDdpJncQHuo/iHK2WZh8xgQMxGoc33nslxB
JvuPAgMBAAGjITAfMB0GA1UdDgQWBBTp2UwAeIxtlKqNFrW4jUVnJ8CgAjANBgkq
hkiG9w0BAQsFAAOCAQEANfkQnompu7woA7fCJb5O+WCNTGoAdFO36tjyRczbRO2G
W7GqPFupObQhAQTtZ55pBfhZTz30lrM9C6vAMDIhfqjYfdATeflALCZH4lbLRcxf
bMq/FVRz1p+hEUGwNeguxCiZ7ZqfqchwiW8oeEeG4MYdYOfdR1pglF6Ak0pkWwqE
KqfuTjllGJRkLJco6ApDA6viBnGUX9T4Xb+xsfzTiOj93k9Z1aWzLB4MCupS+Z1h
VC39DBEUe04OEczu8ZT4A20hiOp+wA2YuToxVVncpnMdZhe1hbzKrTTflM22mcs1
6Fqjo2BBJQ/sfhWMB7KYpnI78n/bkVP2S4hE3JChXA==
-----END CERTIFICATE-----
type: Opaque

View file

@ -219,11 +219,11 @@ public class FileTruststoreProviderFactory implements TruststoreProviderFactory
if (isSelfSigned(cax509cert)) { if (isSelfSigned(cax509cert)) {
X500Principal principal = cax509cert.getSubjectX500Principal(); X500Principal principal = cax509cert.getSubjectX500Principal();
trustedRootCerts.put(principal, cax509cert); trustedRootCerts.put(principal, cax509cert);
log.debug("Trusted root CA found in trustore : alias : " + alias + " | Subject DN : " + principal); log.debug("Trusted root CA found in truststore : alias : " + alias + " | Subject DN : " + principal);
} else { } else {
X500Principal principal = cax509cert.getSubjectX500Principal(); X500Principal principal = cax509cert.getSubjectX500Principal();
intermediateCerts.put(principal, cax509cert); intermediateCerts.put(principal, cax509cert);
log.debug("Intermediate CA found in trustore : alias : " + alias + " | Subject DN : " + principal); log.debug("Intermediate CA found in truststore : alias : " + alias + " | Subject DN : " + principal);
} }
} else } else
log.info("Skipping certificate with alias [" + alias + "] from truststore, because it's not an X509Certificate"); log.info("Skipping certificate with alias [" + alias + "] from truststore, because it's not an X509Certificate");

View file

@ -419,7 +419,7 @@ public interface TestingResource {
void disableTruststoreSpi(); void disableTruststoreSpi();
/** /**
* Temporarily changes the trustore SPI with another hostname verification policy. Call reenableTruststoreSpi to revert. * Temporarily changes the truststore SPI with another hostname verification policy. Call reenableTruststoreSpi to revert.
* @param hostnamePolicy The hostname verification policy to set * @param hostnamePolicy The hostname verification policy to set
*/ */
@GET @GET

View file

@ -119,10 +119,10 @@ public class AdminClientUtil {
ResteasyClientBuilder resteasyClientBuilder = (ResteasyClientBuilder) ResteasyClientBuilder.newBuilder(); ResteasyClientBuilder resteasyClientBuilder = (ResteasyClientBuilder) ResteasyClientBuilder.newBuilder();
if ("true".equals(System.getProperty("auth.server.ssl.required"))) { if ("true".equals(System.getProperty("auth.server.ssl.required"))) {
File trustore = new File(PROJECT_BUILD_DIRECTORY, "dependency/keystore/keycloak.truststore"); File truststore = new File(PROJECT_BUILD_DIRECTORY, "dependency/keystore/keycloak.truststore");
resteasyClientBuilder.sslContext(getSSLContextWithTrustore(trustore, "secret")); resteasyClientBuilder.sslContext(getSSLContextWithTruststore(truststore, "secret"));
System.setProperty("javax.net.ssl.trustStore", trustore.getAbsolutePath()); System.setProperty("javax.net.ssl.trustStore", truststore.getAbsolutePath());
} }
// We need to ignore unknown JSON properties e.g. in the adapter configuration representation // We need to ignore unknown JSON properties e.g. in the adapter configuration representation
@ -145,7 +145,7 @@ public class AdminClientUtil {
return resteasyClientBuilder.build(); return resteasyClientBuilder.build();
} }
private static SSLContext getSSLContextWithTrustore(File file, String password) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException { private static SSLContext getSSLContextWithTruststore(File file, String password) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, KeyManagementException {
if (!file.isFile()) { if (!file.isFile()) {
throw new RuntimeException("Truststore file not found: " + file.getAbsolutePath()); throw new RuntimeException("Truststore file not found: " + file.getAbsolutePath());
} }
@ -191,5 +191,5 @@ public class AdminClientUtil {
return engine; return engine;
} }
} }
} }

View file

@ -53,7 +53,7 @@ public class MutualTLSUtils {
public static CloseableHttpClient newCloseableHttpClientWithOBBKeyStoreAndTrustStore() { public static CloseableHttpClient newCloseableHttpClientWithOBBKeyStoreAndTrustStore() {
return newCloseableHttpClient(OBB_KEYSTOREPATH, OBB_KEYSTOREPASSWORD, DEFAULT_TRUSTSTOREPATH, DEFAULT_TRUSTSTOREPASSWORD); return newCloseableHttpClient(OBB_KEYSTOREPATH, OBB_KEYSTOREPASSWORD, DEFAULT_TRUSTSTOREPATH, DEFAULT_TRUSTSTOREPASSWORD);
} }
public static CloseableHttpClient newCloseableHttpClientWithoutKeyStoreAndTrustStore() { public static CloseableHttpClient newCloseableHttpClientWithoutKeyStoreAndTrustStore() {
return newCloseableHttpClient(null, null, null, null); return newCloseableHttpClient(null, null, null, null);
} }
@ -70,7 +70,7 @@ public class MutualTLSUtils {
} }
} }
// load the trustore // load the truststore
KeyStore truststore = null; KeyStore truststore = null;
if (trustStorePath != null) { if (trustStorePath != null) {
try { try {

View file

@ -446,7 +446,7 @@ public class OAuthClient {
e.printStackTrace(); e.printStackTrace();
} }
// load the trustore // load the truststore
KeyStore truststore = null; KeyStore truststore = null;
try { try {
truststore = KeystoreUtil.loadKeyStore(trustStorePath, trustStorePassword); truststore = KeystoreUtil.loadKeyStore(trustStorePath, trustStorePassword);