Skip to content

Commit

Permalink
DBZ-7087 Ability to protect JMX with password credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
jcechace committed Nov 4, 2023
1 parent 039c4e3 commit 587bb8e
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 10 deletions.
23 changes: 20 additions & 3 deletions k8/debeziumservers.debezium.io-v1.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,30 @@ spec:
jmx:
description: JMX configuration.
properties:
port:
description: JMX port.
type: integer
enabled:
description: Whether JMX should be enabled for this Debezium
Server instance.
type: boolean
port:
description: JMX port.
type: integer
authentication:
description: JMX authentication config.
properties:
passwordFile:
description: JMX password file name and secret key
type: string
accessFile:
description: JMX access file name and secret key
type: string
enabled:
description: Whether JMX authentication should be enabled
for this Debezium Server instance.
type: boolean
secret:
description: Secret providing credential files
type: string
type: object
type: object
volumes:
description: Additional volumes mounted to containers.
Expand Down
126 changes: 123 additions & 3 deletions src/main/java/io/debezium/operator/dependent/DeploymentDependent.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -37,6 +38,7 @@
import io.fabric8.kubernetes.api.model.PodTemplateSpec;
import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder;
import io.fabric8.kubernetes.api.model.ProbeBuilder;
import io.fabric8.kubernetes.api.model.SecretVolumeSourceBuilder;
import io.fabric8.kubernetes.api.model.ServiceAccount;
import io.fabric8.kubernetes.api.model.Volume;
import io.fabric8.kubernetes.api.model.VolumeBuilder;
Expand All @@ -52,12 +54,18 @@ public class DeploymentDependent extends CRUDKubernetesDependentResource<Deploym
public static final String DEFAULT_IMAGE = "quay.io/debezium/server";
public static final String CONFIG_VOLUME_NAME = "ds-config";
public static final String CONFIG_FILE_NAME = "application.properties";
public static final String CONFIG_FILE_PATH = "/debezium/conf/" + CONFIG_FILE_NAME;
public static final String CONFIG_DIR_PATH = "/debezium/conf";
public static final String CONFIG_FILE_PATH = CONFIG_DIR_PATH + "/" + CONFIG_FILE_NAME;
public static final String JMX_CONFIG_VOLUME_NAME = "ds-jmx-config";
public static final String JMX_CONFIG_VOLUME_INIT_NAME = "ds-jmx-config-init";
public static final String JMX_CONFIG_VOLUME_PATH = CONFIG_DIR_PATH + "/jmx";
public static final String JMX_CONFIG_VOLUME_INIT_PATH = "/jmx";
public static final String DATA_VOLUME_NAME = "ds-data";
public static final String DATA_VOLUME_PATH = "/debezium/data";
public static final String EXTERNAL_VOLUME_PATH = "/debezium/external-configuration/%s";
public static final int DEFAULT_HTTP_PORT = 8080;
private static final String CONFIG_MD5_ANNOTATION = "debezium.io/server-config-md5";
private static final String INIT_CONTAINER_IMAGE = "registry.access.redhat.com/ubi8-micro:latest";

@ConfigProperty(name = "debezium.image", defaultValue = DEFAULT_IMAGE)
String defaultImage;
Expand Down Expand Up @@ -104,6 +112,7 @@ protected Deployment desired(DebeziumServer primary, Context<DebeziumServer> con

addTemplateConfigurationToPod(templates.getPod(), pod);
addExternalVolumesToPod(runtime, pod);
addJmxConfigurationToPod(primary, pod);

return new DeploymentBuilder()
.withMetadata(new ObjectMetaBuilder()
Expand Down Expand Up @@ -149,6 +158,45 @@ private void addExternalVolumesToPod(Runtime runtime, PodTemplateSpec pod) {
volumes.addAll(runtime.getVolumes());
}

/**
* Adds JMX configuration to pod if required
*
* @param primary primary resource
* @param pod target pod
*/
private void addJmxConfigurationToPod(DebeziumServer primary, PodTemplateSpec pod) {
var jmx = primary.getSpec().getRuntime().getJmx();
var auth = jmx.getAuthentication();

if (!auth.isEnabled()) {
return;
}

var volumes = pod.getSpec().getVolumes();
var initContainers = pod.getSpec().getInitContainers();

// Add JMX volumes to pod
var jmxInitVolume = new VolumeBuilder()
.withName(JMX_CONFIG_VOLUME_INIT_NAME)
.withSecret(new SecretVolumeSourceBuilder()
.withSecretName(auth.getSecret())
.build())
.build();

var jmxConfigVolume = new VolumeBuilder()
.withName(JMX_CONFIG_VOLUME_NAME)
.withEmptyDir(new EmptyDirVolumeSourceBuilder().build())
.build();

volumes.add(jmxInitVolume);
volumes.add(jmxConfigVolume);

// Add JMX init container
var image = getTaggedImage(primary);
var container = desiredJmxInitContainer(jmx, image);
container.ifPresent(initContainers::add);
}

private void addTemplateConfigurationToContainer(ContainerTemplate template, Container container) {
var containerEnv = template.getEnv()
.stream()
Expand Down Expand Up @@ -232,6 +280,36 @@ private Container desiredServerContainer(DebeziumServer primary) {
return container;
}

/**
* Creates desired JMX init container
*
* @param jmx jmx configuration
* @return init container or empty optional
*/
private Optional<Container> desiredJmxInitContainer(JmxConfig jmx, String image) {
var auth = jmx.getAuthentication();

if (!auth.isEnabled()) {
return Optional.empty();
}

var initContainer = new ContainerBuilder()
.withName("server-init")
.withImage(image)
.withCommand("sh", "-c", JmxCmd.of(auth.getAccessFile()) + " && " + JmxCmd.of(auth.getPasswordFile()))
.addToVolumeMounts(new VolumeMountBuilder()
.withName(JMX_CONFIG_VOLUME_INIT_NAME)
.withMountPath(JMX_CONFIG_VOLUME_INIT_PATH)
.build())
.addToVolumeMounts(new VolumeMountBuilder()
.withName(JMX_CONFIG_VOLUME_NAME)
.withMountPath(JMX_CONFIG_VOLUME_PATH)
.build())
.build();

return Optional.of(initContainer);
}

/**
* Adds external volume mounts to container if required
*
Expand Down Expand Up @@ -278,14 +356,33 @@ private void addJmxConfigurationToContainer(JmxConfig jmx, Container container)
.withContainerPort(jmx.getPort())
.build());

var opts = Map.of(
var opts = new HashMap<>(Map.of(
"-Dcom.sun.management.jmxremote.ssl", false,
"-Dcom.sun.management.jmxremote.port", jmx.getPort(),
"-Dcom.sun.management.jmxremote.rmi.port", jmx.getPort(),
"-Dcom.sun.management.jmxremote.local.only", false,
"-Djava.rmi.server.hostname", "0.0.0.0",
"-Dcom.sun.management.jmxremote.verbose", true,
"-Dcom.sun.management.jmxremote.authenticate", false);
"-Dcom.sun.management.jmxremote.authenticate", false));

var auth = jmx.getAuthentication();

// If JMX authentication is enabled
// Add JVM options and mount config files
if (auth.isEnabled()) {
opts.putAll(Map.of(
"-Dcom.sun.management.jmxremote.authenticate", true,
"-Dcom.sun.management.jmxremote.access.file", JMX_CONFIG_VOLUME_PATH + "/" + auth.getAccessFile(),
"-Dcom.sun.management.jmxremote.password.file", JMX_CONFIG_VOLUME_PATH + "/" + auth.getPasswordFile()));

var mount = new VolumeMountBuilder()
.withName(JMX_CONFIG_VOLUME_NAME)
.withMountPath(JMX_CONFIG_VOLUME_PATH)
.withReadOnly(true)
.build();

container.getVolumeMounts().add(mount);
}

// If JAVA_OPTS is already set (e.g. from container template) we don't want to override it
mergeJavaOptsEnvVar(opts, container);
Expand Down Expand Up @@ -342,4 +439,27 @@ private String getTaggedImage(DebeziumServer primary) {

return image;
}

/**
* JMX auth file copy and permission command representation
*/
private static final class JmxCmd {

private final String source;
private final String target;

private JmxCmd(String file) {
source = JMX_CONFIG_VOLUME_INIT_PATH + "/" + file;
target = JMX_CONFIG_VOLUME_PATH + "/" + file;
}

@Override
public String toString() {
return "cp '%s' '%s' && chmod 600 '%s'".formatted(source, target, target);
}

public static JmxCmd of(String file) {
return new JmxCmd(file);
}
}
}
57 changes: 57 additions & 0 deletions src/main/java/io/debezium/operator/model/JmxAuthentication.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright Debezium Authors.
*
* Licensed under the Apache Software License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package io.debezium.operator.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder({ "enabled", "secret", "accessFile", "secretFile" })
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class JmxAuthentication {
@JsonPropertyDescription("Whether JMX authentication should be enabled for this Debezium Server instance.")
private boolean enabled = false;
@JsonPropertyDescription("Secret providing credential files")
@JsonProperty(required = true)
private String secret;
@JsonPropertyDescription("JMX access file name and secret key")
private String accessFile = "jmxremote.access";
@JsonPropertyDescription("JMX password file name and secret key")
private String passwordFile = "jmxremote.password";

public boolean isEnabled() {
return enabled;
}

public void setEnabled(boolean enabled) {
this.enabled = enabled;
}

public String getSecret() {
return secret;
}

public void setSecret(String secret) {
this.secret = secret;
}

public String getAccessFile() {
return accessFile;
}

public void setAccessFile(String accessFile) {
this.accessFile = accessFile;
}

public String getPasswordFile() {
return passwordFile;
}

public void setPasswordFile(String passwordFile) {
this.passwordFile = passwordFile;
}
}
21 changes: 17 additions & 4 deletions src/main/java/io/debezium/operator/model/JmxConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,20 @@
import com.fasterxml.jackson.annotation.JsonPropertyDescription;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;

@JsonPropertyOrder({ "enabled", "port" })
@JsonPropertyOrder({ "enabled", "port", "auth" })
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
public class JmxConfig {

@JsonPropertyDescription("Whether JMX should be enabled for this Debezium Server instance.")
boolean enabled = false;

private boolean enabled = false;
@JsonPropertyDescription("JMX port.")
int port = 1099;
private int port = 1099;
@JsonPropertyDescription("JMX authentication config.")
private JmxAuthentication authentication;

public JmxConfig() {
this.authentication = new JmxAuthentication();
}

public boolean isEnabled() {
return enabled;
Expand All @@ -34,4 +39,12 @@ public int getPort() {
public void setPort(int port) {
this.port = port;
}

public JmxAuthentication getAuthentication() {
return authentication;
}

public void setAuthentication(JmxAuthentication authentication) {
this.authentication = authentication;
}
}

0 comments on commit 587bb8e

Please sign in to comment.