fix: adding the ability to set the ingress tlsSecret (#41426)

* fix: adding the ability to set the ingress tlsSecret

closes: #34777

Signed-off-by: Steve Hawkins <shawkins@redhat.com>

* Apply suggestions from code review

Co-authored-by: Martin Bartoš <mabartos@redhat.com>
Signed-off-by: Steven Hawkins <shawkins@redhat.com>

---------

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
Signed-off-by: Steven Hawkins <shawkins@redhat.com>
Co-authored-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
Steven Hawkins 2025-08-04 08:28:46 -04:00 committed by GitHub
parent db01ff742b
commit f5f93ef6e1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 102 additions and 6 deletions

View File

@ -21,6 +21,10 @@ This helps you to track down a warning or error message in the log to a specific
For more details on this opt-in feature, see the https://www.keycloak.org/server/logging[Logging guide].
= Ability to specify a `tlsSecret` on the Keycloak CR `ingress` spec
In order to support basic TLS termination (edge) deployments via the operator, you may now set the Keycloak CR `spec.ingress.tlsSecret` field to a TLS Secret name in the namespace.
= HTTP Access logging
{project_name} supports HTTP access logging to record details of incoming HTTP requests.

View File

@ -103,7 +103,7 @@ NOTE: The name format of options defined in this way is identical to the key fo
=== Secret References
Secret References are used by some dedicated options in the Keycloak CR, such as `tlsSecret`, or as a value in `additionalOptions`.
Secret References are used by some dedicated options in the Keycloak CR, such as a `tlsSecret`, or as a value in `additionalOptions`.
Similarly ConfigMap References are used by options such as the `configMapFile`.
@ -290,7 +290,7 @@ spec:
----
NOTE: If you are using a custom image, the Operator is *unaware* of any configuration options that might've been specified there.
For instance, it may cause that the management interface uses the `https` schema, but the Operator accesses it via `http` when the TLS settings is specified in the custom image.
For instance, the management interface may use `https`, but the Operator accesses it via `http` when the TLS settings are specified in the custom image.
To ensure proper TLS configuration, use the `tlsSecret` and `truststores` fields in the Keycloak CR so that the Operator can reflect that.
For more details, see <@links.server id="management-interface" />.

View File

@ -211,6 +211,50 @@ spec:
className: openshift-default
----
NOTE: The operator annotates the Ingress to match expectations for TLS passthrough or TLS termination on OpenShift with the default IngressClass.
See below for more on TLS termination.
==== Proxy modes with the basic Ingress
The operator annotates the Ingress to match expectations for TLS termination or passthrough on OpenShift with the default IngressClass.
For this reason TLS reencryption is not yet considered supported by basic Ingress, but you may be able to specify the `tlsSecret` on both the `http` and `ingress` specs as a starting point.
You should double check the requirements of your IngressClass and platform to see if additional Ingress or Service annotations are needed in your desired scenario.
TLS passthrough is shown in the preceding `example-kc` example. It is enabled when you associate a `tlsSecret` with the `http` configuration and leave Ingress enabled without specifying a `tlsSecret` on it.
TLS termination, or edge mode, is enabled by associating a `tlsSecret` with the `ingress` spec and by enabling HTTP access.
Example TLS Termination YAML:
[source,yaml]
----
apiVersion: k8s.keycloak.org/v2alpha1
kind: Keycloak
metadata:
name: example-kc
spec:
instances: 1
db:
vendor: postgres
host: postgres-db
usernameSecret:
name: keycloak-db-secret
key: username
passwordSecret:
name: keycloak-db-secret
key: password
http:
httpEnabled: true
ingress:
tlsSecret: example-tls-secret
hostname:
hostname: test.keycloak.org
proxy:
headers: xforwarded # double check your reverse proxy sets and overwrites the X-Forwarded-* headers
----
==== Custom Access
If the default ingress does not fit your use case, disable it by setting `ingress` spec with `enabled` property to `false` value:
Edit YAML file `example-kc.yaml`:

View File

@ -18,6 +18,7 @@ package org.keycloak.operator.controllers;
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
import io.fabric8.kubernetes.api.model.networking.v1.IngressBuilder;
import io.fabric8.kubernetes.api.model.networking.v1.IngressTLSBuilder;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.javaoperatorsdk.operator.api.config.informer.Informer;
import io.javaoperatorsdk.operator.api.reconciler.Context;
@ -146,8 +147,9 @@ public class KeycloakIngressDependentResource extends CRUDKubernetesDependentRes
.build();
final var hostnameSpec = keycloak.getSpec().getHostnameSpec();
String hostname = null;
if (hostnameSpec != null && hostnameSpec.getHostname() != null) {
String hostname = hostnameSpec.getHostname();
hostname = hostnameSpec.getHostname();
try {
hostname = new URL(hostname).getHost();
@ -160,6 +162,13 @@ public class KeycloakIngressDependentResource extends CRUDKubernetesDependentRes
ingress.getSpec().getRules().get(0).setHost(hostname);
}
if (hostname != null) {
String[] hosts = new String[] {hostname};
optionalSpec.map(IngressSpec::getTlsSecret).ifPresent(tlsSecret ->
ingress.getSpec().getTls().add(new IngressTLSBuilder().addToHosts(hosts).withSecretName(tlsSecret).build())
);
}
return ingress;
}

View File

@ -30,7 +30,7 @@ public class IngressSpec {
@JsonProperty("enabled")
private boolean ingressEnabled = true;
@JsonProperty("className")
private String ingressClassName;
@ -38,6 +38,9 @@ public class IngressSpec {
@JsonPropertyDescription("Additional annotations to be appended to the Ingress object")
Map<String, String> annotations;
@JsonPropertyDescription("A secret containing the TLS configuration for re-encrypt or TLS termination scenarios. Reference: https://kubernetes.io/docs/concepts/configuration/secret/#tls-secrets.")
private String tlsSecret;
public boolean isIngressEnabled() {
return ingressEnabled;
}
@ -45,11 +48,11 @@ public class IngressSpec {
public void setIngressEnabled(boolean enabled) {
this.ingressEnabled = enabled;
}
public String getIngressClassName() {
return ingressClassName;
}
public void setIngressClassName(String className) {
this.ingressClassName = className;
}
@ -61,4 +64,12 @@ public class IngressSpec {
public void setAnnotations(Map<String, String> annotations) {
this.annotations = annotations;
}
public String getTlsSecret() {
return tlsSecret;
}
public void setTlsSecret(String tlsSecret) {
this.tlsSecret = tlsSecret;
}
}

View File

@ -109,6 +109,34 @@ public class KeycloakIngressTest extends BaseOperatorTest {
.anyMatch(e -> "KC_PROXY_HEADERS".equals(e.getName()) && "xforwarded".equals(e.getValue()));
}
@DisabledIfApiServerTest
@Test
public void testIngressTLSTermination() {
var kc = getTestKeycloakDeployment(false);
var hostnameSpecBuilder = new HostnameSpecBuilder()
.withStrict(false)
.withStrictBackchannel(false);
if (isOpenShift) {
kc.getSpec().setIngressSpec(new IngressSpecBuilder().withIngressClassName(KeycloakController.OPENSHIFT_DEFAULT).build());
}
kc.getSpec().setHostnameSpec(hostnameSpecBuilder.build());
String secret = kc.getSpec().getHttpSpec().getTlsSecret();
kc.getSpec().getHttpSpec().setHttpEnabled(true);
kc.getSpec().getHttpSpec().setTlsSecret(null);
kc.getSpec().setIngressSpec(new IngressSpecBuilder().withTlsSecret(secret).build());
K8sUtils.deployKeycloak(k8sclient, kc, true);
String testHostname;
if (isOpenShift) {
testHostname = k8sclient.resource(kc).get().getSpec().getHostnameSpec().getHostname();
} else {
testHostname = kubernetesIp;
}
testIngressURLs("https://" + testHostname + ":443");
}
private void testIngressURLs(String baseUrl) {
Awaitility.await()
.ignoreExceptions()