Skip to content

Implement tls support for the app #6007

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 13, 2025
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
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package io.apicurio.registry.operator;

import io.apicurio.registry.operator.api.v1.ApicurioRegistry3;
import io.fabric8.kubernetes.api.model.HTTPGetActionBuilder;
import io.fabric8.kubernetes.api.model.IntOrString;
import io.fabric8.kubernetes.api.model.Probe;
import io.fabric8.kubernetes.api.model.ProbeBuilder;
import io.fabric8.kubernetes.api.model.Quantity;
Expand All @@ -26,13 +28,16 @@ public class Constants {
public static final Map<String, Quantity> DEFAULT_LIMITS = Map.of("cpu",
new QuantityBuilder().withAmount("1").build(), "memory",
new QuantityBuilder().withAmount("1300").withFormat("Mi").build());
public static final Probe DEFAULT_READINESS_PROBE = new ProbeBuilder().withNewHttpGet()
.withPath("/health/ready").withNewPort().withValue(8080).endPort().endHttpGet()
public static final Probe DEFAULT_READINESS_PROBE = new ProbeBuilder().withHttpGet(new HTTPGetActionBuilder().withPath("/health/ready").withPort(new IntOrString(8080)).withScheme("HTTP").build()).build();
public static final Probe DEFAULT_LIVENESS_PROBE = new ProbeBuilder().withHttpGet(new HTTPGetActionBuilder().withPath("/health/live").withPort(new IntOrString(8080)).withScheme("HTTP").build()).build();

public static final Probe TLS_DEFAULT_READINESS_PROBE = new ProbeBuilder().withNewHttpGet()
.withScheme("HTTPS").withPath("/health/ready").withNewPort().withValue(8443).endPort().endHttpGet()
.withInitialDelaySeconds(15).withTimeoutSeconds(5).withPeriodSeconds(10).withSuccessThreshold(1)
.withFailureThreshold(3).build();

public static final Probe DEFAULT_LIVENESS_PROBE = new ProbeBuilder().withNewHttpGet()
.withPath("/health/live").withNewPort().withValue(8080).endPort().endHttpGet()
public static final Probe TLS_DEFAULT_LIVENESS_PROBE = new ProbeBuilder().withNewHttpGet()
.withScheme("HTTPS").withPath("/health/live").withNewPort().withValue(8443).endPort().endHttpGet()
.withInitialDelaySeconds(15).withTimeoutSeconds(5).withPeriodSeconds(10).withSuccessThreshold(1)
.withFailureThreshold(3).build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@ public class EnvironmentVariables {
public static final String QUARKUS_HTTP_ACCESS_LOG_ENABLED = "QUARKUS_HTTP_ACCESS_LOG_ENABLED";
public static final String QUARKUS_HTTP_CORS_ORIGINS = "QUARKUS_HTTP_CORS_ORIGINS";

public static final String QUARKUS_HTTP_INSECURE_REQUESTS = "QUARKUS_HTTP_INSECURE_REQUESTS";
public static final String QUARKUS_TLS_KEY_STORE_P12_PATH = "QUARKUS_TLS_KEY_STORE_P12_PATH";
public static final String QUARKUS_TLS_KEY_STORE_P12_PASSWORD = "QUARKUS_TLS_KEY_STORE_P12_PASSWORD";
public static final String QUARKUS_TLS_TRUST_STORE_P12_PATH = "QUARKUS_TLS_TRUST_STORE_P12_PATH";
public static final String QUARKUS_TLS_TRUST_STORE_P12_PASSWORD = "QUARKUS_TLS_TRUST_STORE_P12_PASSWORD";
public static final String APICURIO_REST_DELETION_ARTIFACT_VERSION_ENABLED = "APICURIO_REST_DELETION_ARTIFACT-VERSION_ENABLED";
public static final String APICURIO_REST_DELETION_ARTIFACT_ENABLED = "APICURIO_REST_DELETION_ARTIFACT_ENABLED";
public static final String APICURIO_REST_DELETION_GROUP_ENABLED = "APICURIO_REST_DELETION_GROUP_ENABLED";

public static final String APICURIO_REST_MUTABILITY_ARTIFACT_VERSION_CONTENT_ENABLED = "APICURIO_REST_MUTABILITY_ARTIFACT-VERSION-CONTENT_ENABLED";

private static final String KAFKA_PREFIX = "APICURIO_KAFKA_COMMON_";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package io.apicurio.registry.operator.feat;

import io.apicurio.registry.operator.api.v1.ApicurioRegistry3;
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3Spec;
import io.apicurio.registry.operator.api.v1.TlsTrafficStatus;
import io.apicurio.registry.operator.api.v1.spec.AppSpec;
import io.apicurio.registry.operator.api.v1.spec.TLSSpec;
import io.apicurio.registry.operator.utils.SecretKeyRefTool;
import io.fabric8.kubernetes.api.model.EnvVar;
import io.fabric8.kubernetes.api.model.apps.Deployment;

import java.util.Map;
import java.util.Optional;

import static io.apicurio.registry.operator.EnvironmentVariables.*;
import static io.apicurio.registry.operator.resource.app.AppDeploymentResource.addEnvVar;
import static java.util.Optional.ofNullable;

public class TLS {

public static void configureTLS(ApicurioRegistry3 primary, Deployment deployment,
String containerName, Map<String, EnvVar> env) {

addEnvVar(env, QUARKUS_HTTP_INSECURE_REQUESTS, Optional.ofNullable(primary.getSpec())
.map(ApicurioRegistry3Spec::getApp)
.map(AppSpec::getTls)
.map(TLSSpec::getInsecureRequests)
.orElse(TlsTrafficStatus.ENABLED.getValue()));

var keystore = new SecretKeyRefTool(getTlsSpec(primary)
.map(TLSSpec::getKeystoreSecretRef)
.orElse(null), "user.p12");

var keystorePassword = new SecretKeyRefTool(getTlsSpec(primary)
.map(TLSSpec::getKeystorePasswordSecretRef)
.orElse(null), "user.password");

var truststore = new SecretKeyRefTool(getTlsSpec(primary)
.map(TLSSpec::getTruststoreSecretRef)
.orElse(null), "ca.p12");

var truststorePassword = new SecretKeyRefTool(getTlsSpec(primary)
.map(TLSSpec::getTruststorePasswordSecretRef)
.orElse(null), "ca.password");

if (truststore.isValid() && truststorePassword.isValid()) {
// ===== Truststore
truststore.applySecretVolume(deployment, containerName);
addEnvVar(env, QUARKUS_TLS_TRUST_STORE_P12_PATH, truststore.getSecretVolumeKeyPath());
truststorePassword.applySecretEnvVar(env, QUARKUS_TLS_TRUST_STORE_P12_PASSWORD);
}

if (keystore.isValid()
&& keystorePassword.isValid()) {
// ===== Keystore
keystore.applySecretVolume(deployment, containerName);
addEnvVar(env, QUARKUS_TLS_KEY_STORE_P12_PATH, keystore.getSecretVolumeKeyPath());
keystorePassword.applySecretEnvVar(env, QUARKUS_TLS_KEY_STORE_P12_PASSWORD);
}
}

private static Optional<TLSSpec> getTlsSpec(ApicurioRegistry3 primary) {
return ofNullable(primary)
.map(ApicurioRegistry3::getSpec)
.map(ApicurioRegistry3Spec::getApp)
.map(AppSpec::getTls);
}

public static boolean insecureRequestsEnabled(TLSSpec tlsSpec) {
return TlsTrafficStatus.ENABLED.getValue().equals(tlsSpec.getInsecureRequests());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3Spec;
import io.apicurio.registry.operator.api.v1.spec.AppSpec;
import io.apicurio.registry.operator.api.v1.spec.StudioUiSpec;
import io.apicurio.registry.operator.api.v1.spec.TLSSpec;
import io.apicurio.registry.operator.api.v1.spec.UiSpec;
import io.apicurio.registry.operator.status.ValidationErrorConditionManager;
import io.apicurio.registry.operator.status.StatusManager;
import io.apicurio.registry.operator.status.ValidationErrorConditionManager;
import io.apicurio.registry.operator.utils.SecretKeyRefTool;
import io.fabric8.kubernetes.api.model.*;
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentSpec;
Expand All @@ -23,8 +25,10 @@
import java.util.Map;
import java.util.Optional;

import static io.apicurio.registry.operator.Constants.DEFAULT_REPLICAS;
import static io.apicurio.registry.operator.api.v1.ContainerNames.*;
import static io.apicurio.registry.operator.Constants.*;
import static io.apicurio.registry.operator.api.v1.ContainerNames.REGISTRY_APP_CONTAINER_NAME;
import static io.apicurio.registry.operator.api.v1.ContainerNames.REGISTRY_UI_CONTAINER_NAME;
import static io.apicurio.registry.operator.api.v1.ContainerNames.STUDIO_UI_CONTAINER_NAME;
import static io.apicurio.registry.operator.resource.Labels.getSelectorLabels;
import static io.apicurio.registry.operator.resource.app.AppDeploymentResource.getContainerFromPodTemplateSpec;
import static io.apicurio.registry.operator.utils.Mapper.YAML_MAPPER;
Expand Down Expand Up @@ -56,19 +60,43 @@ public Deployment getDefaultAppDeployment(ApicurioRegistry3 primary) {
.map(AppSpec::getReplicas).orElse(DEFAULT_REPLICAS),
ofNullable(primary.getSpec()).map(ApicurioRegistry3Spec::getApp)
.map(AppSpec::getPodTemplateSpec).orElse(null)); // TODO:

var readinessProbe = DEFAULT_READINESS_PROBE;
var livenessProbe = DEFAULT_LIVENESS_PROBE;
var containerPort = List.of(new ContainerPortBuilder().withName("http").withProtocol("TCP").withContainerPort(8080).build());

Optional<TLSSpec> tlsSpec = ofNullable(primary.getSpec())
.map(ApicurioRegistry3Spec::getApp)
.map(AppSpec::getTls);

if (tlsSpec.isPresent()) {
var keystore = new SecretKeyRefTool(tlsSpec.map(TLSSpec::getKeystoreSecretRef)
.orElse(null), "keystore");

var keystorePassword = new SecretKeyRefTool(tlsSpec.map(TLSSpec::getKeystorePasswordSecretRef)
.orElse(null), "password");

if (keystore.isValid() && keystorePassword.isValid()) {
readinessProbe = TLS_DEFAULT_READINESS_PROBE;
livenessProbe = TLS_DEFAULT_LIVENESS_PROBE;
containerPort = List.of(new ContainerPortBuilder().withName("https").withProtocol("TCP").withContainerPort(8443).build());
}
}

// Replicas
mergeDeploymentPodTemplateSpec(
COMPONENT_APP_SPEC_FIELD_NAME,
primary,
r.getSpec().getTemplate(),
REGISTRY_APP_CONTAINER_NAME,
Configuration.getAppImage(),
List.of(new ContainerPortBuilder().withName("http").withProtocol("TCP").withContainerPort(8080).build()),
new ProbeBuilder().withHttpGet(new HTTPGetActionBuilder().withPath("/health/ready").withPort(new IntOrString(8080)).withScheme("HTTP").build()).build(),
new ProbeBuilder().withHttpGet(new HTTPGetActionBuilder().withPath("/health/live").withPort(new IntOrString(8080)).withScheme("HTTP").build()).build(),
containerPort,
readinessProbe,
livenessProbe,
Map.of("cpu", new Quantity("500m"), "memory", new Quantity("512Mi")),
Map.of("cpu", new Quantity("1"), "memory", new Quantity("1Gi"))
);

addDefaultLabels(r.getMetadata().getLabels(), primary, COMPONENT_APP);
addSelectorLabels(r.getSpec().getSelector().getMatchLabels(), primary, COMPONENT_APP);
addDefaultLabels(r.getSpec().getTemplate().getMetadata().getLabels(), primary, COMPONENT_APP);
Expand Down Expand Up @@ -106,7 +134,7 @@ public Deployment getDefaultStudioUIDeployment(ApicurioRegistry3 primary) {
.map(StudioUiSpec::getReplicas).orElse(DEFAULT_REPLICAS),
ofNullable(primary.getSpec()).map(ApicurioRegistry3Spec::getStudioUi)
.map(StudioUiSpec::getPodTemplateSpec).orElse(null)); // TODO:
// Replicas
// Replicas
mergeDeploymentPodTemplateSpec(
COMPONENT_STUDIO_UI_SPEC_FIELD_NAME,
primary,
Expand All @@ -126,7 +154,7 @@ public Deployment getDefaultStudioUIDeployment(ApicurioRegistry3 primary) {
}

private static Deployment initDefaultDeployment(ApicurioRegistry3 primary, String componentId,
int replicas, PodTemplateSpec pts) {
int replicas, PodTemplateSpec pts) {
var r = new Deployment();
r.setMetadata(new ObjectMeta());
r.getMetadata().setNamespace(primary.getMetadata().getNamespace());
Expand All @@ -137,7 +165,8 @@ private static Deployment initDefaultDeployment(ApicurioRegistry3 primary, Strin
r.getSpec().setSelector(new LabelSelector());
if (pts != null) {
r.getSpec().setTemplate(pts);
} else {
}
else {
r.getSpec().setTemplate(new PodTemplateSpec());
}
return r;
Expand Down Expand Up @@ -179,8 +208,8 @@ private static void mergeDeploymentPodTemplateSpec(
if (c.getEnv() != null && !c.getEnv().isEmpty()) {
StatusManager.get(primary).getConditionManager(ValidationErrorConditionManager.class)
.recordError("""
Field spec.%s.podTemplateSpec.spec.containers[name = %s].env must be empty. \
Use spec.%s.env to configure environment variables.""", componentFieldName, containerName, componentFieldName);
Field spec.%s.podTemplateSpec.spec.containers[name = %s].env must be empty. \
Use spec.%s.env to configure environment variables.""", componentFieldName, containerName, componentFieldName);
}
if (c.getPorts() == null) {
c.setPorts(new ArrayList<>());
Expand Down Expand Up @@ -269,7 +298,7 @@ public PodDisruptionBudget getDefaultStudioUIPodDisruptionBudget(ApicurioRegistr
}

private <T extends HasMetadata> T getDefaultResource(ApicurioRegistry3 primary, Class<T> klass,
String resourceType, String component) {
String resourceType, String component) {
var r = deserialize("/k8s/default/" + component + "." + resourceType + ".yaml", klass);
r.getMetadata().setNamespace(primary.getMetadata().getNamespace());
r.getMetadata().setName(primary.getMetadata().getName() + "-" + component + "-" + resourceType);
Expand Down Expand Up @@ -320,15 +349,17 @@ private void addSelectorLabels(Map<String, String> labels, ApicurioRegistry3 pri
public static <T> T deserialize(String path, Class<T> klass) {
try {
return YAML_MAPPER.readValue(load(path), klass);
} catch (JsonProcessingException ex) {
}
catch (JsonProcessingException ex) {
throw new OperatorException("Could not deserialize resource: " + path, ex);
}
}

public static <T> T deserialize(String path, Class<T> klass, ClassLoader classLoader) {
try {
return YAML_MAPPER.readValue(load(path, classLoader), klass);
} catch (JsonProcessingException ex) {
}
catch (JsonProcessingException ex) {
throw new OperatorException("Could not deserialize resource: " + path, ex);
}
}
Expand All @@ -340,7 +371,8 @@ public static String load(String path) {
public static String load(String path, ClassLoader classLoader) {
try (var stream = classLoader.getResourceAsStream(path)) {
return new String(stream.readAllBytes(), Charset.defaultCharset());
} catch (Exception ex) {
}
catch (Exception ex) {
throw new OperatorException("Could not read resource: " + path, ex);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import io.apicurio.registry.operator.feat.Cors;
import io.apicurio.registry.operator.feat.KafkaSql;
import io.apicurio.registry.operator.feat.PostgresSql;
import io.apicurio.registry.operator.feat.TLS;
import io.apicurio.registry.operator.feat.security.Auth;
import io.apicurio.registry.operator.status.ReadyConditionManager;
import io.apicurio.registry.operator.status.StatusManager;
Expand Down Expand Up @@ -87,6 +88,9 @@ protected Deployment desired(ApicurioRegistry3 primary, Context<ApicurioRegistry
// Configure the CORS_ALLOWED_ORIGINS env var based on the ingress host
Cors.configureAllowedOrigins(primary, envVars);

// Configure the TLS env vars
TLS.configureTLS(primary, deployment, REGISTRY_APP_CONTAINER_NAME, envVars);

// Enable the "mutability" feature in Registry, but only if Studio is deployed. It is based on Service
// in case a custom Ingress is used.
var sOpt = context.getSecondaryResource(STUDIO_UI_SERVICE_KEY.getKlass(),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package io.apicurio.registry.operator.resource.app;

import io.apicurio.registry.operator.api.v1.ApicurioRegistry3;
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3Spec;
import io.apicurio.registry.operator.api.v1.spec.AppSpec;
import io.apicurio.registry.operator.feat.TLS;
import io.apicurio.registry.operator.resource.LabelDiscriminators.AppNetworkPolicyDiscriminator;
import io.fabric8.kubernetes.api.model.IntOrStringBuilder;
import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy;
import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyIngressRuleBuilder;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.Optional;

import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_APP;
import static io.apicurio.registry.operator.resource.ResourceKey.APP_NETWORK_POLICY_KEY;
import static io.apicurio.registry.operator.utils.Mapper.toYAML;
Expand All @@ -31,6 +39,30 @@ public AppNetworkPolicyResource() {
@Override
protected NetworkPolicy desired(ApicurioRegistry3 primary, Context<ApicurioRegistry3> context) {
var networkPolicy = APP_NETWORK_POLICY_KEY.getFactory().apply(primary);

Optional.ofNullable(primary.getSpec())
.map(ApicurioRegistry3Spec::getApp)
.map(AppSpec::getTls)
.ifPresent(tls -> {

var httpsPolicy = new io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyPortBuilder()
.withPort(new IntOrStringBuilder().withValue(8443).build()).build();

var httpPolicy = new io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyPortBuilder()
.withPort(new IntOrStringBuilder().withValue(8080).build()).build();

if (!TLS.insecureRequestsEnabled(tls)) {
networkPolicy.getSpec().setIngress(List.of(new NetworkPolicyIngressRuleBuilder()
.withPorts(httpsPolicy)
.build()));
}
else {
networkPolicy.getSpec().setIngress(List.of(new NetworkPolicyIngressRuleBuilder()
.withPorts(httpsPolicy, httpPolicy)
.build()));
}
});

log.trace("Desired {} is {}", APP_NETWORK_POLICY_KEY.getId(), toYAML(networkPolicy));
return networkPolicy;
}
Expand Down
Loading
Loading