Skip to content
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

Implement tls support for the app #6007

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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,71 @@
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.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.*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a nit, but * is not much good practice form my pov (different projects, different standards) :)

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("enabled"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use boolean for insecureRequests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, the possible values are enabled, disabled and redirect, so I don't think boolean makes sense here.


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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a constructor that takes Optional would be useful here. We've got this pattern elsewhere, e.g. in io.apicurio.registry.operator.feat.KafkaSqlTLS. Optional suggestion, could be done in a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion, I was just following the pattern from KafkaSqlTLS. I'll let you decide if you want to do it in a separate PR.


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);
}
Comment on lines +45 to +50
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if I will use just PEM cert as truststore? Like

kind: ApicurioRegistry3
apiVersion: registry.apicur.io/v1
metadata:
  name: schema-registry
spec:
  app:
    storage:
      type: "kafkasql"
      kafkasql:
        bootstrapServers: anubis-kafka-bootstrap.strimzi-kafka.svc:9093
        tls:
          truststoreSecretRef:
            name: anubis-cluster-ca-cert
            key: ca.crt

wouldn't it cause failure? I would more expect that keystore password will be required

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the operator level we're supporting P12 (since it's usually a better practice than just specifying cert by cert). You will still be able to use PEM files using Quarkus own env vars.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks.


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 "enabled".equals(tlsSpec.getInsecureRequests());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
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;
Expand All @@ -23,7 +24,7 @@
import java.util.Map;
import java.util.Optional;

import static io.apicurio.registry.operator.Constants.DEFAULT_REPLICAS;
import static io.apicurio.registry.operator.Constants.*;
import static io.apicurio.registry.operator.api.v1.ContainerNames.*;
import static io.apicurio.registry.operator.resource.Labels.getSelectorLabels;
import static io.apicurio.registry.operator.resource.app.AppDeploymentResource.getContainerFromPodTemplateSpec;
Expand Down Expand Up @@ -56,19 +57,35 @@ 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()) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assuming TLS is enabled only by the presence of the tls object in the CR seems like it could cause user errors. I suggest we also check that the truststore/keystore is valid via io.apicurio.registry.operator.utils.SecretKeyRefTool#isValid before we assume user wants to enable TLS. Could be done by a static method in io.apicurio.registry.operator.feat.TLS.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but, if it's misconfigured, the application won't even start, which, if you tried to configure TLS, might be even better than a "healthy" application and then having to look at the logs, wdyt?

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
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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think TLS only makes sense for Registry container at the moment, so REGISTRY_APP_CONTAINER_NAME could be inlined. Optional suggestion, might be done in a separate PR, since it's also done elsewhere.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The same value is also used in line 114 in this class, so I think it makes more sense to import the constant (easier refactor later if needed).


// 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
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
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.fabric8.kubernetes.api.model.IntOrStringBuilder;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.ServicePortBuilder;
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.LabelDiscriminators.AppServiceDiscriminator;
import static io.apicurio.registry.operator.resource.ResourceKey.APP_SERVICE_KEY;
import static io.apicurio.registry.operator.utils.Mapper.toYAML;
Expand All @@ -24,6 +32,31 @@ public AppServiceResource() {
@Override
protected Service desired(ApicurioRegistry3 primary, Context<ApicurioRegistry3> context) {
var s = APP_SERVICE_KEY.getFactory().apply(primary);

Optional.ofNullable(primary.getSpec())
.map(ApicurioRegistry3Spec::getApp)
.map(AppSpec::getTls)
.ifPresent(tls -> {
var httpPort = new ServicePortBuilder()
.withName("http")
.withPort(8080)
.withTargetPort(new IntOrStringBuilder().withValue(8080).build())
.build();

var httpsPort = new ServicePortBuilder()
.withName("https")
.withPort(443)
.withTargetPort(new IntOrStringBuilder().withValue(8443).build())
.build();

if (!TLS.insecureRequestsEnabled(tls)) {
s.getSpec().setPorts(List.of(httpsPort, httpPort));
}
else {
s.getSpec().setPorts(List.of(httpsPort));
}
});

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