Skip to content

Commit d03af4a

Browse files
authored
Implement tls support for the app (#6007)
* Implement tls support for the app * Fix auth tests * Reach the server using port forward in tls tests
1 parent 97649d4 commit d03af4a

File tree

15 files changed

+635
-20
lines changed

15 files changed

+635
-20
lines changed

operator/controller/src/main/java/io/apicurio/registry/operator/Constants.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.apicurio.registry.operator;
22

33
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3;
4+
import io.fabric8.kubernetes.api.model.HTTPGetActionBuilder;
5+
import io.fabric8.kubernetes.api.model.IntOrString;
46
import io.fabric8.kubernetes.api.model.Probe;
57
import io.fabric8.kubernetes.api.model.ProbeBuilder;
68
import io.fabric8.kubernetes.api.model.Quantity;
@@ -26,13 +28,16 @@ public class Constants {
2628
public static final Map<String, Quantity> DEFAULT_LIMITS = Map.of("cpu",
2729
new QuantityBuilder().withAmount("1").build(), "memory",
2830
new QuantityBuilder().withAmount("1300").withFormat("Mi").build());
29-
public static final Probe DEFAULT_READINESS_PROBE = new ProbeBuilder().withNewHttpGet()
30-
.withPath("/health/ready").withNewPort().withValue(8080).endPort().endHttpGet()
31+
public static final Probe DEFAULT_READINESS_PROBE = new ProbeBuilder().withHttpGet(new HTTPGetActionBuilder().withPath("/health/ready").withPort(new IntOrString(8080)).withScheme("HTTP").build()).build();
32+
public static final Probe DEFAULT_LIVENESS_PROBE = new ProbeBuilder().withHttpGet(new HTTPGetActionBuilder().withPath("/health/live").withPort(new IntOrString(8080)).withScheme("HTTP").build()).build();
33+
34+
public static final Probe TLS_DEFAULT_READINESS_PROBE = new ProbeBuilder().withNewHttpGet()
35+
.withScheme("HTTPS").withPath("/health/ready").withNewPort().withValue(8443).endPort().endHttpGet()
3136
.withInitialDelaySeconds(15).withTimeoutSeconds(5).withPeriodSeconds(10).withSuccessThreshold(1)
3237
.withFailureThreshold(3).build();
3338

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

operator/controller/src/main/java/io/apicurio/registry/operator/EnvironmentVariables.java

+5-1
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@ public class EnvironmentVariables {
66
public static final String QUARKUS_HTTP_ACCESS_LOG_ENABLED = "QUARKUS_HTTP_ACCESS_LOG_ENABLED";
77
public static final String QUARKUS_HTTP_CORS_ORIGINS = "QUARKUS_HTTP_CORS_ORIGINS";
88

9+
public static final String QUARKUS_HTTP_INSECURE_REQUESTS = "QUARKUS_HTTP_INSECURE_REQUESTS";
10+
public static final String QUARKUS_TLS_KEY_STORE_P12_PATH = "QUARKUS_TLS_KEY_STORE_P12_PATH";
11+
public static final String QUARKUS_TLS_KEY_STORE_P12_PASSWORD = "QUARKUS_TLS_KEY_STORE_P12_PASSWORD";
12+
public static final String QUARKUS_TLS_TRUST_STORE_P12_PATH = "QUARKUS_TLS_TRUST_STORE_P12_PATH";
13+
public static final String QUARKUS_TLS_TRUST_STORE_P12_PASSWORD = "QUARKUS_TLS_TRUST_STORE_P12_PASSWORD";
914
public static final String APICURIO_REST_DELETION_ARTIFACT_VERSION_ENABLED = "APICURIO_REST_DELETION_ARTIFACT-VERSION_ENABLED";
1015
public static final String APICURIO_REST_DELETION_ARTIFACT_ENABLED = "APICURIO_REST_DELETION_ARTIFACT_ENABLED";
1116
public static final String APICURIO_REST_DELETION_GROUP_ENABLED = "APICURIO_REST_DELETION_GROUP_ENABLED";
12-
1317
public static final String APICURIO_REST_MUTABILITY_ARTIFACT_VERSION_CONTENT_ENABLED = "APICURIO_REST_MUTABILITY_ARTIFACT-VERSION-CONTENT_ENABLED";
1418

1519
private static final String KAFKA_PREFIX = "APICURIO_KAFKA_COMMON_";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package io.apicurio.registry.operator.feat;
2+
3+
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3;
4+
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3Spec;
5+
import io.apicurio.registry.operator.api.v1.TlsTrafficStatus;
6+
import io.apicurio.registry.operator.api.v1.spec.AppSpec;
7+
import io.apicurio.registry.operator.api.v1.spec.TLSSpec;
8+
import io.apicurio.registry.operator.utils.SecretKeyRefTool;
9+
import io.fabric8.kubernetes.api.model.EnvVar;
10+
import io.fabric8.kubernetes.api.model.apps.Deployment;
11+
12+
import java.util.Map;
13+
import java.util.Optional;
14+
15+
import static io.apicurio.registry.operator.EnvironmentVariables.*;
16+
import static io.apicurio.registry.operator.resource.app.AppDeploymentResource.addEnvVar;
17+
import static java.util.Optional.ofNullable;
18+
19+
public class TLS {
20+
21+
public static void configureTLS(ApicurioRegistry3 primary, Deployment deployment,
22+
String containerName, Map<String, EnvVar> env) {
23+
24+
addEnvVar(env, QUARKUS_HTTP_INSECURE_REQUESTS, Optional.ofNullable(primary.getSpec())
25+
.map(ApicurioRegistry3Spec::getApp)
26+
.map(AppSpec::getTls)
27+
.map(TLSSpec::getInsecureRequests)
28+
.orElse(TlsTrafficStatus.ENABLED.getValue()));
29+
30+
var keystore = new SecretKeyRefTool(getTlsSpec(primary)
31+
.map(TLSSpec::getKeystoreSecretRef)
32+
.orElse(null), "user.p12");
33+
34+
var keystorePassword = new SecretKeyRefTool(getTlsSpec(primary)
35+
.map(TLSSpec::getKeystorePasswordSecretRef)
36+
.orElse(null), "user.password");
37+
38+
var truststore = new SecretKeyRefTool(getTlsSpec(primary)
39+
.map(TLSSpec::getTruststoreSecretRef)
40+
.orElse(null), "ca.p12");
41+
42+
var truststorePassword = new SecretKeyRefTool(getTlsSpec(primary)
43+
.map(TLSSpec::getTruststorePasswordSecretRef)
44+
.orElse(null), "ca.password");
45+
46+
if (truststore.isValid() && truststorePassword.isValid()) {
47+
// ===== Truststore
48+
truststore.applySecretVolume(deployment, containerName);
49+
addEnvVar(env, QUARKUS_TLS_TRUST_STORE_P12_PATH, truststore.getSecretVolumeKeyPath());
50+
truststorePassword.applySecretEnvVar(env, QUARKUS_TLS_TRUST_STORE_P12_PASSWORD);
51+
}
52+
53+
if (keystore.isValid()
54+
&& keystorePassword.isValid()) {
55+
// ===== Keystore
56+
keystore.applySecretVolume(deployment, containerName);
57+
addEnvVar(env, QUARKUS_TLS_KEY_STORE_P12_PATH, keystore.getSecretVolumeKeyPath());
58+
keystorePassword.applySecretEnvVar(env, QUARKUS_TLS_KEY_STORE_P12_PASSWORD);
59+
}
60+
}
61+
62+
private static Optional<TLSSpec> getTlsSpec(ApicurioRegistry3 primary) {
63+
return ofNullable(primary)
64+
.map(ApicurioRegistry3::getSpec)
65+
.map(ApicurioRegistry3Spec::getApp)
66+
.map(AppSpec::getTls);
67+
}
68+
69+
public static boolean insecureRequestsEnabled(TLSSpec tlsSpec) {
70+
return TlsTrafficStatus.ENABLED.getValue().equals(tlsSpec.getInsecureRequests());
71+
}
72+
}

operator/controller/src/main/java/io/apicurio/registry/operator/resource/ResourceFactory.java

+47-15
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@
77
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3Spec;
88
import io.apicurio.registry.operator.api.v1.spec.AppSpec;
99
import io.apicurio.registry.operator.api.v1.spec.StudioUiSpec;
10+
import io.apicurio.registry.operator.api.v1.spec.TLSSpec;
1011
import io.apicurio.registry.operator.api.v1.spec.UiSpec;
11-
import io.apicurio.registry.operator.status.ValidationErrorConditionManager;
1212
import io.apicurio.registry.operator.status.StatusManager;
13+
import io.apicurio.registry.operator.status.ValidationErrorConditionManager;
14+
import io.apicurio.registry.operator.utils.SecretKeyRefTool;
1315
import io.fabric8.kubernetes.api.model.*;
1416
import io.fabric8.kubernetes.api.model.apps.Deployment;
1517
import io.fabric8.kubernetes.api.model.apps.DeploymentSpec;
@@ -23,8 +25,10 @@
2325
import java.util.Map;
2426
import java.util.Optional;
2527

26-
import static io.apicurio.registry.operator.Constants.DEFAULT_REPLICAS;
27-
import static io.apicurio.registry.operator.api.v1.ContainerNames.*;
28+
import static io.apicurio.registry.operator.Constants.*;
29+
import static io.apicurio.registry.operator.api.v1.ContainerNames.REGISTRY_APP_CONTAINER_NAME;
30+
import static io.apicurio.registry.operator.api.v1.ContainerNames.REGISTRY_UI_CONTAINER_NAME;
31+
import static io.apicurio.registry.operator.api.v1.ContainerNames.STUDIO_UI_CONTAINER_NAME;
2832
import static io.apicurio.registry.operator.resource.Labels.getSelectorLabels;
2933
import static io.apicurio.registry.operator.resource.app.AppDeploymentResource.getContainerFromPodTemplateSpec;
3034
import static io.apicurio.registry.operator.utils.Mapper.YAML_MAPPER;
@@ -56,19 +60,43 @@ public Deployment getDefaultAppDeployment(ApicurioRegistry3 primary) {
5660
.map(AppSpec::getReplicas).orElse(DEFAULT_REPLICAS),
5761
ofNullable(primary.getSpec()).map(ApicurioRegistry3Spec::getApp)
5862
.map(AppSpec::getPodTemplateSpec).orElse(null)); // TODO:
63+
64+
var readinessProbe = DEFAULT_READINESS_PROBE;
65+
var livenessProbe = DEFAULT_LIVENESS_PROBE;
66+
var containerPort = List.of(new ContainerPortBuilder().withName("http").withProtocol("TCP").withContainerPort(8080).build());
67+
68+
Optional<TLSSpec> tlsSpec = ofNullable(primary.getSpec())
69+
.map(ApicurioRegistry3Spec::getApp)
70+
.map(AppSpec::getTls);
71+
72+
if (tlsSpec.isPresent()) {
73+
var keystore = new SecretKeyRefTool(tlsSpec.map(TLSSpec::getKeystoreSecretRef)
74+
.orElse(null), "keystore");
75+
76+
var keystorePassword = new SecretKeyRefTool(tlsSpec.map(TLSSpec::getKeystorePasswordSecretRef)
77+
.orElse(null), "password");
78+
79+
if (keystore.isValid() && keystorePassword.isValid()) {
80+
readinessProbe = TLS_DEFAULT_READINESS_PROBE;
81+
livenessProbe = TLS_DEFAULT_LIVENESS_PROBE;
82+
containerPort = List.of(new ContainerPortBuilder().withName("https").withProtocol("TCP").withContainerPort(8443).build());
83+
}
84+
}
85+
5986
// Replicas
6087
mergeDeploymentPodTemplateSpec(
6188
COMPONENT_APP_SPEC_FIELD_NAME,
6289
primary,
6390
r.getSpec().getTemplate(),
6491
REGISTRY_APP_CONTAINER_NAME,
6592
Configuration.getAppImage(),
66-
List.of(new ContainerPortBuilder().withName("http").withProtocol("TCP").withContainerPort(8080).build()),
67-
new ProbeBuilder().withHttpGet(new HTTPGetActionBuilder().withPath("/health/ready").withPort(new IntOrString(8080)).withScheme("HTTP").build()).build(),
68-
new ProbeBuilder().withHttpGet(new HTTPGetActionBuilder().withPath("/health/live").withPort(new IntOrString(8080)).withScheme("HTTP").build()).build(),
93+
containerPort,
94+
readinessProbe,
95+
livenessProbe,
6996
Map.of("cpu", new Quantity("500m"), "memory", new Quantity("512Mi")),
7097
Map.of("cpu", new Quantity("1"), "memory", new Quantity("1Gi"))
7198
);
99+
72100
addDefaultLabels(r.getMetadata().getLabels(), primary, COMPONENT_APP);
73101
addSelectorLabels(r.getSpec().getSelector().getMatchLabels(), primary, COMPONENT_APP);
74102
addDefaultLabels(r.getSpec().getTemplate().getMetadata().getLabels(), primary, COMPONENT_APP);
@@ -106,7 +134,7 @@ public Deployment getDefaultStudioUIDeployment(ApicurioRegistry3 primary) {
106134
.map(StudioUiSpec::getReplicas).orElse(DEFAULT_REPLICAS),
107135
ofNullable(primary.getSpec()).map(ApicurioRegistry3Spec::getStudioUi)
108136
.map(StudioUiSpec::getPodTemplateSpec).orElse(null)); // TODO:
109-
// Replicas
137+
// Replicas
110138
mergeDeploymentPodTemplateSpec(
111139
COMPONENT_STUDIO_UI_SPEC_FIELD_NAME,
112140
primary,
@@ -126,7 +154,7 @@ public Deployment getDefaultStudioUIDeployment(ApicurioRegistry3 primary) {
126154
}
127155

128156
private static Deployment initDefaultDeployment(ApicurioRegistry3 primary, String componentId,
129-
int replicas, PodTemplateSpec pts) {
157+
int replicas, PodTemplateSpec pts) {
130158
var r = new Deployment();
131159
r.setMetadata(new ObjectMeta());
132160
r.getMetadata().setNamespace(primary.getMetadata().getNamespace());
@@ -137,7 +165,8 @@ private static Deployment initDefaultDeployment(ApicurioRegistry3 primary, Strin
137165
r.getSpec().setSelector(new LabelSelector());
138166
if (pts != null) {
139167
r.getSpec().setTemplate(pts);
140-
} else {
168+
}
169+
else {
141170
r.getSpec().setTemplate(new PodTemplateSpec());
142171
}
143172
return r;
@@ -179,8 +208,8 @@ private static void mergeDeploymentPodTemplateSpec(
179208
if (c.getEnv() != null && !c.getEnv().isEmpty()) {
180209
StatusManager.get(primary).getConditionManager(ValidationErrorConditionManager.class)
181210
.recordError("""
182-
Field spec.%s.podTemplateSpec.spec.containers[name = %s].env must be empty. \
183-
Use spec.%s.env to configure environment variables.""", componentFieldName, containerName, componentFieldName);
211+
Field spec.%s.podTemplateSpec.spec.containers[name = %s].env must be empty. \
212+
Use spec.%s.env to configure environment variables.""", componentFieldName, containerName, componentFieldName);
184213
}
185214
if (c.getPorts() == null) {
186215
c.setPorts(new ArrayList<>());
@@ -269,7 +298,7 @@ public PodDisruptionBudget getDefaultStudioUIPodDisruptionBudget(ApicurioRegistr
269298
}
270299

271300
private <T extends HasMetadata> T getDefaultResource(ApicurioRegistry3 primary, Class<T> klass,
272-
String resourceType, String component) {
301+
String resourceType, String component) {
273302
var r = deserialize("/k8s/default/" + component + "." + resourceType + ".yaml", klass);
274303
r.getMetadata().setNamespace(primary.getMetadata().getNamespace());
275304
r.getMetadata().setName(primary.getMetadata().getName() + "-" + component + "-" + resourceType);
@@ -320,15 +349,17 @@ private void addSelectorLabels(Map<String, String> labels, ApicurioRegistry3 pri
320349
public static <T> T deserialize(String path, Class<T> klass) {
321350
try {
322351
return YAML_MAPPER.readValue(load(path), klass);
323-
} catch (JsonProcessingException ex) {
352+
}
353+
catch (JsonProcessingException ex) {
324354
throw new OperatorException("Could not deserialize resource: " + path, ex);
325355
}
326356
}
327357

328358
public static <T> T deserialize(String path, Class<T> klass, ClassLoader classLoader) {
329359
try {
330360
return YAML_MAPPER.readValue(load(path, classLoader), klass);
331-
} catch (JsonProcessingException ex) {
361+
}
362+
catch (JsonProcessingException ex) {
332363
throw new OperatorException("Could not deserialize resource: " + path, ex);
333364
}
334365
}
@@ -340,7 +371,8 @@ public static String load(String path) {
340371
public static String load(String path, ClassLoader classLoader) {
341372
try (var stream = classLoader.getResourceAsStream(path)) {
342373
return new String(stream.readAllBytes(), Charset.defaultCharset());
343-
} catch (Exception ex) {
374+
}
375+
catch (Exception ex) {
344376
throw new OperatorException("Could not read resource: " + path, ex);
345377
}
346378
}

operator/controller/src/main/java/io/apicurio/registry/operator/resource/app/AppDeploymentResource.java

+4
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import io.apicurio.registry.operator.feat.Cors;
1212
import io.apicurio.registry.operator.feat.KafkaSql;
1313
import io.apicurio.registry.operator.feat.PostgresSql;
14+
import io.apicurio.registry.operator.feat.TLS;
1415
import io.apicurio.registry.operator.feat.security.Auth;
1516
import io.apicurio.registry.operator.status.ReadyConditionManager;
1617
import io.apicurio.registry.operator.status.StatusManager;
@@ -87,6 +88,9 @@ protected Deployment desired(ApicurioRegistry3 primary, Context<ApicurioRegistry
8788
// Configure the CORS_ALLOWED_ORIGINS env var based on the ingress host
8889
Cors.configureAllowedOrigins(primary, envVars);
8990

91+
// Configure the TLS env vars
92+
TLS.configureTLS(primary, deployment, REGISTRY_APP_CONTAINER_NAME, envVars);
93+
9094
// Enable the "mutability" feature in Registry, but only if Studio is deployed. It is based on Service
9195
// in case a custom Ingress is used.
9296
var sOpt = context.getSecondaryResource(STUDIO_UI_SERVICE_KEY.getKlass(),

operator/controller/src/main/java/io/apicurio/registry/operator/resource/app/AppNetworkPolicyResource.java

+32
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
package io.apicurio.registry.operator.resource.app;
22

33
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3;
4+
import io.apicurio.registry.operator.api.v1.ApicurioRegistry3Spec;
5+
import io.apicurio.registry.operator.api.v1.spec.AppSpec;
6+
import io.apicurio.registry.operator.feat.TLS;
47
import io.apicurio.registry.operator.resource.LabelDiscriminators.AppNetworkPolicyDiscriminator;
8+
import io.fabric8.kubernetes.api.model.IntOrStringBuilder;
59
import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicy;
10+
import io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyIngressRuleBuilder;
611
import io.javaoperatorsdk.operator.api.reconciler.Context;
712
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
813
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
914
import org.slf4j.Logger;
1015
import org.slf4j.LoggerFactory;
1116

17+
import java.util.List;
18+
import java.util.Optional;
19+
1220
import static io.apicurio.registry.operator.resource.ResourceFactory.COMPONENT_APP;
1321
import static io.apicurio.registry.operator.resource.ResourceKey.APP_NETWORK_POLICY_KEY;
1422
import static io.apicurio.registry.operator.utils.Mapper.toYAML;
@@ -31,6 +39,30 @@ public AppNetworkPolicyResource() {
3139
@Override
3240
protected NetworkPolicy desired(ApicurioRegistry3 primary, Context<ApicurioRegistry3> context) {
3341
var networkPolicy = APP_NETWORK_POLICY_KEY.getFactory().apply(primary);
42+
43+
Optional.ofNullable(primary.getSpec())
44+
.map(ApicurioRegistry3Spec::getApp)
45+
.map(AppSpec::getTls)
46+
.ifPresent(tls -> {
47+
48+
var httpsPolicy = new io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyPortBuilder()
49+
.withPort(new IntOrStringBuilder().withValue(8443).build()).build();
50+
51+
var httpPolicy = new io.fabric8.kubernetes.api.model.networking.v1.NetworkPolicyPortBuilder()
52+
.withPort(new IntOrStringBuilder().withValue(8080).build()).build();
53+
54+
if (!TLS.insecureRequestsEnabled(tls)) {
55+
networkPolicy.getSpec().setIngress(List.of(new NetworkPolicyIngressRuleBuilder()
56+
.withPorts(httpsPolicy)
57+
.build()));
58+
}
59+
else {
60+
networkPolicy.getSpec().setIngress(List.of(new NetworkPolicyIngressRuleBuilder()
61+
.withPorts(httpsPolicy, httpPolicy)
62+
.build()));
63+
}
64+
});
65+
3466
log.trace("Desired {} is {}", APP_NETWORK_POLICY_KEY.getId(), toYAML(networkPolicy));
3567
return networkPolicy;
3668
}

0 commit comments

Comments
 (0)