Skip to content

Commit 90bb95f

Browse files
Implement support for deploying KafkaBridge with TLS/SSL configuration (#12287)
Signed-off-by: Gantigmaa Selenge <tina.selenge@gmail.com> Signed-off-by: Paolo Patierno <ppatierno@live.com> Co-authored-by: Paolo Patierno <ppatierno@live.com>
1 parent d415473 commit 90bb95f

File tree

18 files changed

+764
-12
lines changed

18 files changed

+764
-12
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
* The `ServerSideApplyPhase1` feature gate moves to beta stage and is enabled by default.
3636
If needed, `ServerSideApplyPhase1` can be disabled in the feature gates configuration in the Cluster Operator.
3737
* Fixed auto-rebalancing on scale up not running anymore when Cruise Control is not ready yet on the first attempt.
38+
* Add support for TLS/SSL on the HTTP Bridge
39+
Set `spec.http.tls.certificateAndKey` configuration to enable it and provide the certificate and key via Secret.
3840

3941
### Major changes, deprecations, and removals
4042

api/src/main/java/io/strimzi/api/kafka/model/bridge/KafkaBridgeHttpConfig.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,14 @@
2828
builderPackage = Constants.FABRIC8_KUBERNETES_API
2929
)
3030
@JsonInclude(JsonInclude.Include.NON_NULL)
31-
@JsonPropertyOrder({"port", "cors"})
31+
@JsonPropertyOrder({"port", "tls", "cors"})
3232
@EqualsAndHashCode
3333
@ToString
3434
public class KafkaBridgeHttpConfig implements UnknownPropertyPreserving {
3535
public static final int HTTP_DEFAULT_PORT = 8080;
3636
public static final String HTTP_DEFAULT_HOST = "0.0.0.0";
3737
private int port = HTTP_DEFAULT_PORT;
38+
private KafkaBridgeHttpTls tls;
3839
private KafkaBridgeHttpCors cors;
3940
private Map<String, Object> additionalProperties;
4041

@@ -55,6 +56,16 @@ public void setPort(int port) {
5556
this.port = port;
5657
}
5758

59+
@Description("TLS configuration for clients connections to the HTTP Bridge.")
60+
@JsonInclude(JsonInclude.Include.NON_NULL)
61+
public KafkaBridgeHttpTls getTls() {
62+
return tls;
63+
}
64+
65+
public void setTls(KafkaBridgeHttpTls tls) {
66+
this.tls = tls;
67+
}
68+
5869
@Description("CORS configuration for the HTTP Bridge.")
5970
@JsonInclude(JsonInclude.Include.NON_NULL)
6071
public KafkaBridgeHttpCors getCors() {
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright Strimzi authors.
3+
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4+
*/
5+
package io.strimzi.api.kafka.model.bridge;
6+
7+
import com.fasterxml.jackson.annotation.JsonInclude;
8+
import com.fasterxml.jackson.annotation.JsonProperty;
9+
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
10+
import io.strimzi.api.kafka.model.common.CertAndKeySecretSource;
11+
import io.strimzi.api.kafka.model.common.Constants;
12+
import io.strimzi.api.kafka.model.common.UnknownPropertyPreserving;
13+
import io.strimzi.crdgenerator.annotations.Description;
14+
import io.sundr.builder.annotations.Buildable;
15+
import lombok.EqualsAndHashCode;
16+
import lombok.ToString;
17+
18+
import java.util.HashMap;
19+
import java.util.Map;
20+
21+
/**
22+
* A representation of the TLS configuration for the HTTP.
23+
*/
24+
@Buildable(
25+
editableEnabled = false,
26+
builderPackage = Constants.FABRIC8_KUBERNETES_API
27+
)
28+
@JsonInclude(JsonInclude.Include.NON_NULL)
29+
@JsonPropertyOrder({"certificateAndKey", "config"})
30+
@EqualsAndHashCode
31+
@ToString
32+
public class KafkaBridgeHttpTls implements UnknownPropertyPreserving {
33+
public static final String FORBIDDEN_PREFIXES = "ssl.";
34+
public static final String FORBIDDEN_PREFIX_EXCEPTIONS = "ssl.enabled.cipher.suites, ssl.enabled.protocols";
35+
36+
private CertAndKeySecretSource certificateAndKey = null;
37+
private Map<String, Object> config = new HashMap<>(0);
38+
private Map<String, Object> additionalProperties;
39+
40+
@Description("Reference to the `Secret` which holds the certificate and private key pair.")
41+
@JsonInclude(JsonInclude.Include.NON_NULL)
42+
@JsonProperty(required = true)
43+
public CertAndKeySecretSource getCertificateAndKey() {
44+
return certificateAndKey;
45+
}
46+
47+
public void setCertificateAndKey(CertAndKeySecretSource certificateAndKey) {
48+
this.certificateAndKey = certificateAndKey;
49+
}
50+
51+
@Description("Additional configuration for the HTTP server TLS. Properties with the following prefixes cannot be set: " + FORBIDDEN_PREFIXES + " (with the exception of: " + FORBIDDEN_PREFIX_EXCEPTIONS + ").")
52+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
53+
public Map<String, Object> getConfig() {
54+
return config;
55+
}
56+
57+
public void setConfig(Map<String, Object> config) {
58+
this.config = config;
59+
}
60+
61+
@Override
62+
public Map<String, Object> getAdditionalProperties() {
63+
return this.additionalProperties != null ? this.additionalProperties : Map.of();
64+
}
65+
66+
@Override
67+
public void setAdditionalProperty(String name, Object value) {
68+
if (this.additionalProperties == null) {
69+
this.additionalProperties = new HashMap<>(2);
70+
}
71+
this.additionalProperties.put(name, value);
72+
}
73+
}

api/src/test/resources/crds/v1/046-Crd-kafkabridge.yaml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,33 @@ spec:
160160
type: integer
161161
minimum: 1023
162162
description: Port the server listens on.
163+
tls:
164+
type: object
165+
properties:
166+
certificateAndKey:
167+
type: object
168+
properties:
169+
secretName:
170+
type: string
171+
description: The name of the Secret containing the certificate.
172+
certificate:
173+
type: string
174+
description: The name of the file certificate in the Secret.
175+
key:
176+
type: string
177+
description: "The name of the private key in the secret. The private key must be in unencrypted PKCS #8 format. For more information, see RFC 5208: https://datatracker.ietf.org/doc/html/rfc5208."
178+
required:
179+
- secretName
180+
- certificate
181+
- key
182+
description: Reference to the `Secret` which holds the certificate and private key pair.
183+
config:
184+
x-kubernetes-preserve-unknown-fields: true
185+
type: object
186+
description: "Additional configuration for the HTTP server TLS. Properties with the following prefixes cannot be set: ssl. (with the exception of: ssl.enabled.cipher.suites, ssl.enabled.protocols)."
187+
required:
188+
- certificateAndKey
189+
description: TLS configuration for clients connections to the HTTP Bridge.
163190
cors:
164191
type: object
165192
properties:

api/src/test/resources/crds/v1beta2/046-Crd-kafkabridge.yaml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,27 @@ spec:
139139
port:
140140
type: integer
141141
minimum: 1023
142+
tls:
143+
type: object
144+
properties:
145+
certificateAndKey:
146+
type: object
147+
properties:
148+
secretName:
149+
type: string
150+
certificate:
151+
type: string
152+
key:
153+
type: string
154+
required:
155+
- secretName
156+
- certificate
157+
- key
158+
config:
159+
x-kubernetes-preserve-unknown-fields: true
160+
type: object
161+
required:
162+
- certificateAndKey
142163
cors:
143164
type: object
144165
properties:
@@ -1577,6 +1598,27 @@ spec:
15771598
port:
15781599
type: integer
15791600
minimum: 1023
1601+
tls:
1602+
type: object
1603+
properties:
1604+
certificateAndKey:
1605+
type: object
1606+
properties:
1607+
secretName:
1608+
type: string
1609+
certificate:
1610+
type: string
1611+
key:
1612+
type: string
1613+
required:
1614+
- secretName
1615+
- certificate
1616+
- key
1617+
config:
1618+
x-kubernetes-preserve-unknown-fields: true
1619+
type: object
1620+
required:
1621+
- certificateAndKey
15801622
cors:
15811623
type: object
15821624
properties:

cluster-operator/src/main/java/io/strimzi/operator/cluster/model/KafkaBridgeCluster.java

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ public class KafkaBridgeCluster extends AbstractModel implements SupportsLogging
8686
* HTTP port configuration
8787
*/
8888
public static final int DEFAULT_REST_API_PORT = 8080;
89-
9089
/* test */ static final String COMPONENT_TYPE = "kafka-bridge";
9190
protected static final String REST_API_PORT_NAME = "rest-api";
9291
/* test */ static final String REST_API_MANAGEMENT_PORT_NAME = "rest-api-mgmt";
9392
/* test */ static final int REST_API_MANAGEMENT_PORT = 8081;
9493
protected static final String TLS_CERTS_BASE_VOLUME_MOUNT = "/opt/strimzi/bridge-certs/";
94+
protected static final String HTTP_SERVER_CERTS_BASE_VOLUME_MOUNT = "/opt/strimzi/bridge-http-certs/";
9595
protected static final String PASSWORD_VOLUME_MOUNT = "/opt/strimzi/bridge-password/";
9696
protected static final String ENV_VAR_KAFKA_INIT_INIT_FOLDER_KEY = "INIT_FOLDER";
9797
private static final String KAFKA_BRIDGE_CONFIG_VOLUME_NAME = "kafka-bridge-configurations";
@@ -246,8 +246,10 @@ private static void fromCrdTemplate(final KafkaBridgeCluster kafkaBridgeCluster,
246246
*/
247247
public Service generateService() {
248248
int port = DEFAULT_REST_API_PORT;
249+
boolean isTls = false;
249250
if (http != null) {
250251
port = http.getPort();
252+
isTls = http.getTls() != null;
251253
}
252254

253255
List<ServicePort> ports = new ArrayList<>();
@@ -263,7 +265,7 @@ public Service generateService() {
263265
ports,
264266
labels.strimziSelectorLabels(),
265267
ModelUtils.getCustomLabelsOrAnnotations(CO_ENV_VAR_CUSTOM_SERVICE_LABELS),
266-
Util.mergeLabelsOrAnnotations(getDiscoveryAnnotation(port), ModelUtils.getCustomLabelsOrAnnotations(CO_ENV_VAR_CUSTOM_SERVICE_ANNOTATIONS))
268+
Util.mergeLabelsOrAnnotations(getDiscoveryAnnotation(port, isTls), ModelUtils.getCustomLabelsOrAnnotations(CO_ENV_VAR_CUSTOM_SERVICE_ANNOTATIONS))
267269
);
268270
}
269271

@@ -272,15 +274,14 @@ public Service generateService() {
272274
*
273275
* @return JSON with discovery annotation
274276
*/
275-
/*test*/ Map<String, String> getDiscoveryAnnotation(int port) {
277+
/*test*/ Map<String, String> getDiscoveryAnnotation(int port, boolean isTls) {
276278
JsonArray anno = new JsonArray();
277279

278280
JsonObject discovery = new JsonObject();
279281
discovery.put("port", port);
280-
discovery.put("tls", false);
282+
discovery.put("tls", isTls);
281283
discovery.put("auth", "none");
282-
discovery.put("protocol", "http");
283-
284+
discovery.put("protocol", isTls ? "https" : "http");
284285
anno.add(discovery);
285286

286287
JsonObject managementDiscovery = new JsonObject();
@@ -316,6 +317,19 @@ protected List<Volume> getVolumes(boolean isOpenShift) {
316317
CertUtils.createTrustedCertificatesVolumes(volumeList, tls.getTrustedCertificates(), isOpenShift);
317318
}
318319

320+
if (http != null && http.getTls() != null) {
321+
if (http.getTls().getCertificateAndKey() != null) {
322+
String secretName = http.getTls().getCertificateAndKey().getSecretName();
323+
// skipping if a volume mount with same Secret name was already added
324+
if (volumeList.stream().noneMatch(v -> v.getName().equals(secretName))) {
325+
volumeList.add(VolumeUtils.createSecretVolume(secretName, secretName, isOpenShift));
326+
}
327+
} else {
328+
LOGGER.warnCr(reconciliation, "The spec.http.tls.certificateAndKey property is missing. This is required when TLS is enabled for HTTP Bridge.");
329+
throw new InvalidResourceException("The TLS configuration for the HTTP is not specified.");
330+
}
331+
}
332+
319333
if (rack != null) {
320334
volumeList.add(VolumeUtils.createEmptyDirVolume(INIT_VOLUME_NAME, "1Mi", "Memory"));
321335
}
@@ -337,6 +351,16 @@ protected List<VolumeMount> getVolumeMounts() {
337351
CertUtils.createTrustedCertificatesVolumeMounts(volumeMountList, tls.getTrustedCertificates(), TLS_CERTS_BASE_VOLUME_MOUNT);
338352
}
339353

354+
if (http != null && http.getTls() != null) {
355+
if (http.getTls().getCertificateAndKey() != null) {
356+
String secretName = http.getTls().getCertificateAndKey().getSecretName();
357+
volumeMountList.add(VolumeUtils.createVolumeMount(secretName, HTTP_SERVER_CERTS_BASE_VOLUME_MOUNT + secretName));
358+
} else {
359+
LOGGER.warnCr(reconciliation, "The spec.http.tls.certificateAndKey property is missing. This is required when TLS is enabled for HTTP Bridge.");
360+
throw new InvalidResourceException("The TLS configuration for the HTTP is not specified.");
361+
}
362+
}
363+
340364
if (rack != null) {
341365
volumeMountList.add(VolumeUtils.createVolumeMount(INIT_VOLUME_NAME, INIT_VOLUME_MOUNT));
342366
}

cluster-operator/src/main/java/io/strimzi/operator/cluster/model/KafkaBridgeConfigurationBuilder.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Map;
3030

3131
import static io.strimzi.api.kafka.model.common.metrics.StrimziMetricsReporter.TYPE_STRIMZI_METRICS_REPORTER;
32+
import static io.strimzi.operator.cluster.model.KafkaBridgeCluster.HTTP_SERVER_CERTS_BASE_VOLUME_MOUNT;
3233
import static io.strimzi.operator.cluster.model.KafkaBridgeCluster.KAFKA_BRIDGE_CONFIG_VOLUME_MOUNT;
3334

3435
/**
@@ -294,6 +295,21 @@ public KafkaBridgeConfigurationBuilder withHttp(KafkaBridgeHttpConfig http, Kafk
294295
printSectionHeader("HTTP configuration");
295296
writer.println("http.host=" + KafkaBridgeHttpConfig.HTTP_DEFAULT_HOST);
296297
writer.println("http.port=" + (http != null ? http.getPort() : KafkaBridgeHttpConfig.HTTP_DEFAULT_PORT));
298+
299+
if (http != null && http.getTls() != null) {
300+
writer.println("http.ssl.enable=true");
301+
302+
if (http.getTls().getCertificateAndKey() != null) {
303+
writer.println("http.ssl.certificate.location=" + HTTP_SERVER_CERTS_BASE_VOLUME_MOUNT + http.getTls().getCertificateAndKey().getSecretName() + "/" + http.getTls().getCertificateAndKey().getCertificate());
304+
writer.println("http.ssl.key.location=" + HTTP_SERVER_CERTS_BASE_VOLUME_MOUNT + http.getTls().getCertificateAndKey().getSecretName() + "/" + http.getTls().getCertificateAndKey().getKey());
305+
}
306+
307+
if (!http.getTls().getConfig().isEmpty()) {
308+
KafkaBridgeHttpTlsConfiguration tlsConfig = new KafkaBridgeHttpTlsConfiguration(reconciliation, http.getTls().getConfig().entrySet());
309+
tlsConfig.asOrderedProperties().asMap().forEach((key, value) -> writer.println("http." + key + "=" + value));
310+
}
311+
}
312+
297313
if (http != null && http.getCors() != null) {
298314
writer.println("http.cors.enabled=true");
299315

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright Strimzi authors.
3+
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4+
*/
5+
6+
package io.strimzi.operator.cluster.model;
7+
8+
import io.strimzi.api.kafka.model.bridge.KafkaBridgeHttpTls;
9+
import io.strimzi.operator.common.Reconciliation;
10+
11+
import java.util.List;
12+
import java.util.Map;
13+
14+
/**
15+
* Class for handling Kafka Bridge HTTP TLS configuration passed by the user
16+
*/
17+
public class KafkaBridgeHttpTlsConfiguration extends AbstractConfiguration {
18+
19+
private static final List<String> FORBIDDEN_PREFIXES;
20+
private static final List<String> FORBIDDEN_PREFIX_EXCEPTIONS;
21+
22+
static {
23+
FORBIDDEN_PREFIXES = AbstractConfiguration.splitPrefixesOrOptionsToList(KafkaBridgeHttpTls.FORBIDDEN_PREFIXES);
24+
FORBIDDEN_PREFIX_EXCEPTIONS = AbstractConfiguration.splitPrefixesOrOptionsToList(KafkaBridgeHttpTls.FORBIDDEN_PREFIX_EXCEPTIONS);
25+
}
26+
27+
/**
28+
* Constructor used to instantiate this class from JsonObject. Should be used to create configuration from
29+
* ConfigMap / CRD.
30+
*
31+
* @param reconciliation The reconciliation
32+
* @param jsonOptions Json object with configuration options as key ad value pairs.
33+
*/
34+
public KafkaBridgeHttpTlsConfiguration(Reconciliation reconciliation, Iterable<Map.Entry<String, Object>> jsonOptions) {
35+
super(reconciliation, jsonOptions, FORBIDDEN_PREFIXES, FORBIDDEN_PREFIX_EXCEPTIONS, List.of(), Map.of());
36+
}
37+
}

0 commit comments

Comments
 (0)