Skip to content

Commit

Permalink
credentials in connections are encrypted when written to the db
Browse files Browse the repository at this point in the history
Signed-off-by: Stanchev Aleksandar <aleksandar.stanchev@bosch.io>
  • Loading branch information
Aleksandar Stanchev committed Dec 6, 2022
1 parent 40983c2 commit aeead4d
Show file tree
Hide file tree
Showing 12 changed files with 892 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,13 @@ public interface ConnectionConfig extends WithSupervisorConfig, WithActivityChec
*/
Duration getShutdownTimeout();

/**
* Returns the configuration for connection fields encryption.
*
* @return the config.
*/
FieldsEncryptionConfig getFieldsEncryptionConfig();

/**
* An enumeration of the known config path expressions and their associated default values for
* {@code ConnectionConfig}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public final class DefaultConnectionConfig implements ConnectionConfig {
private final KafkaConfig kafkaConfig;
private final HttpPushConfig httpPushConfig;
private final ActivityCheckConfig activityCheckConfig;
private final FieldsEncryptionConfig fieldsEncryptionConfig;
private final Integer maxNumberOfTargets;
private final Integer maxNumberOfSources;
private final Duration ackLabelDeclareInterval;
Expand All @@ -80,6 +81,7 @@ private DefaultConnectionConfig(final ConfigWithFallback config) {
kafkaConfig = DefaultKafkaConfig.of(config);
httpPushConfig = DefaultHttpPushConfig.of(config);
activityCheckConfig = DefaultActivityCheckConfig.of(config);
fieldsEncryptionConfig = DefaultFieldsEncryptionConfig.of(config);
maxNumberOfTargets = config.getNonNegativeIntOrThrow(ConnectionConfigValue.MAX_TARGET_NUMBER);
maxNumberOfSources = config.getNonNegativeIntOrThrow(ConnectionConfigValue.MAX_SOURCE_NUMBER);
ackLabelDeclareInterval =
Expand Down Expand Up @@ -212,6 +214,11 @@ public ActivityCheckConfig getActivityCheckConfig() {
public CleanupConfig getCleanupConfig() {
return cleanupConfig;
}
@Override
public FieldsEncryptionConfig getFieldsEncryptionConfig(){
return fieldsEncryptionConfig;
}


@Override
public boolean equals(final Object o) {
Expand All @@ -238,6 +245,7 @@ public boolean equals(final Object o) {
Objects.equals(kafkaConfig, that.kafkaConfig) &&
Objects.equals(httpPushConfig, that.httpPushConfig) &&
Objects.equals(activityCheckConfig, that.activityCheckConfig) &&
Objects.equals(fieldsEncryptionConfig, that.fieldsEncryptionConfig) &&
Objects.equals(maxNumberOfTargets, that.maxNumberOfTargets) &&
Objects.equals(maxNumberOfSources, that.maxNumberOfSources) &&
Objects.equals(ackLabelDeclareInterval, that.ackLabelDeclareInterval) &&
Expand All @@ -250,8 +258,8 @@ public int hashCode() {
return Objects.hash(clientActorAskTimeout, clientActorRestartsBeforeEscalation, allowedHostnames,
blockedHostnames, blockedSubnets, blockedHostRegex, supervisorConfig, snapshotConfig,
acknowledgementConfig, cleanupConfig, maxNumberOfTargets, maxNumberOfSources, activityCheckConfig,
amqp10Config, amqp091Config, mqttConfig, kafkaConfig, httpPushConfig, ackLabelDeclareInterval,
priorityUpdateInterval, shutdownTimeout);
fieldsEncryptionConfig, amqp10Config, amqp091Config, mqttConfig, kafkaConfig, httpPushConfig,
ackLabelDeclareInterval, priorityUpdateInterval, shutdownTimeout);
}

@Override
Expand All @@ -273,6 +281,7 @@ public String toString() {
", kafkaConfig=" + kafkaConfig +
", httpPushConfig=" + httpPushConfig +
", activityCheckConfig=" + activityCheckConfig +
", fieldsEncryptionConfig=" + fieldsEncryptionConfig +
", maxNumberOfTargets=" + maxNumberOfTargets +
", maxNumberOfSources=" + maxNumberOfSources +
", ackLabelDeclareInterval=" + ackLabelDeclareInterval +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/

package org.eclipse.ditto.connectivity.service.config;

import java.util.*;

import javax.annotation.concurrent.Immutable;

import org.eclipse.ditto.internal.utils.config.ConfigWithFallback;

import com.typesafe.config.Config;

/**
* Default implementation of {@link FieldsEncryptionConfig}.
*/
@Immutable
public class DefaultFieldsEncryptionConfig implements FieldsEncryptionConfig {

private static final String CONFIG_PATH = "encryption";
private final boolean enabled;
private final String symmetricalKey;
private final List<String> jsonPointers;


private DefaultFieldsEncryptionConfig(final ConfigWithFallback config) {
this.enabled = config.getBoolean(ConfigValue.ENABLED.getConfigPath());
this.symmetricalKey = config.getString(ConfigValue.SYMMETRICAL_KEY.getConfigPath());
this.jsonPointers = Collections.unmodifiableList(
new ArrayList<>(config.getStringList(ConfigValue.JSON_POINTERS.getConfigPath())));
}

public static DefaultFieldsEncryptionConfig of(final Config config) {
final var fieldEncryptionConfig =
ConfigWithFallback.newInstance(config, CONFIG_PATH, FieldsEncryptionConfig.ConfigValue.values());

return new DefaultFieldsEncryptionConfig(fieldEncryptionConfig);
}

@Override
public boolean isEnabled() {
return this.enabled;
}

@Override
public String getSymmetricalKey() {
return this.symmetricalKey;
}

@Override
public Collection<String> getJsonPointers() {
return Collections.unmodifiableList(new ArrayList<>(this.jsonPointers));
}

@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
final DefaultFieldsEncryptionConfig that = (DefaultFieldsEncryptionConfig) o;
return enabled == that.enabled &&
Objects.equals(symmetricalKey, that.symmetricalKey) &&
Objects.equals(jsonPointers, that.jsonPointers);
}

@Override
public int hashCode() {
return Objects.hash(enabled, symmetricalKey, jsonPointers);
}

@Override
public String toString() {
return getClass().getSimpleName() + "[" +
"enabled=" + enabled +
", symmetricalKey='" + symmetricalKey + '\'' +
", jsonPointers=" + jsonPointers +
']';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2022 Contributors to the Eclipse Foundation
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/

package org.eclipse.ditto.connectivity.service.config;

import org.eclipse.ditto.internal.utils.config.KnownConfigValue;

import java.util.Collection;
import java.util.List;

/**
* Provides configuration settings for encrypting json field values in Connections.
*/
public interface FieldsEncryptionConfig {


/**
* Indicates whether encryption of connection fields should be enabled.
*
* @return {@code true} if connection fields encryption should be enabled.
*/
boolean isEnabled();


/**
* Returns the symmetricalKey used for encryption.
* @return the symmetricalKey
*/
String getSymmetricalKey();


/**
* Returns string json pointers to the values of json fields to be encrypted.
* "uri" has a special handling in which only the password of the uri is encrypted.
*
* @return pointers list
*/
Collection<String> getJsonPointers();



/**
* An enumeration of the known config path expressions and their associated default values for {@code FieldsEncryptionConfig}.
*/
enum ConfigValue implements KnownConfigValue {

/**
* Determines whether json value encryption is enabled.
*/
ENABLED("enabled", false),
/**
* The symmetrical key used for encryption.
*/
SYMMETRICAL_KEY("symmetrical-key", ""),

/**
* The pointer to the json values to be encrypted.
*/
JSON_POINTERS("json-pointers", List.of(
"/uri",
"/credentials/key",
"/sshTunnel/credentials/password",
"/sshTunnel/credentials/privateKey",
"/credentials/parameters/accessKey",
"/credentials/parameters/secretKey",
"/credentials/parameters/sharedKey",
"/credentials/clientSecret"));

private final String configPath;
private final Object defaultValue;

ConfigValue(final String theConfigPath, final Object theDefaultValue) {
configPath = theConfigPath;
defaultValue = theDefaultValue;
}

@Override
public Object getDefaultValue() {
return defaultValue;
}

@Override
public String getConfigPath() {
return configPath;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,31 +12,54 @@
*/
package org.eclipse.ditto.connectivity.service.messaging.persistence;

import java.util.HashMap;
import java.util.Map;

import javax.annotation.Nullable;

import org.eclipse.ditto.connectivity.model.Connection;
import org.eclipse.ditto.internal.utils.persistence.mongo.AbstractMongoEventAdapter;
import akka.actor.ActorSystem;
import akka.actor.ExtendedActorSystem;
import org.eclipse.ditto.base.model.signals.JsonParsable;
import org.eclipse.ditto.base.model.signals.events.EventJsonDeserializer;
import org.eclipse.ditto.base.model.signals.events.EventRegistry;
import org.eclipse.ditto.base.model.signals.events.GlobalEventRegistry;
import org.eclipse.ditto.connectivity.model.Connection;
import org.eclipse.ditto.connectivity.model.signals.events.ConnectionCreated;
import org.eclipse.ditto.connectivity.model.signals.events.ConnectionModified;
import org.eclipse.ditto.connectivity.model.signals.events.ConnectivityEvent;
import org.eclipse.ditto.connectivity.service.config.DittoConnectivityConfig;
import org.eclipse.ditto.connectivity.service.config.FieldsEncryptionConfig;
import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig;
import org.eclipse.ditto.internal.utils.persistence.mongo.AbstractMongoEventAdapter;
import org.eclipse.ditto.json.JsonObject;

import akka.actor.ExtendedActorSystem;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.Map;

/**
* EventAdapter for {@link ConnectivityEvent}s persisted into
* akka-persistence event-journal. Converts Events to MongoDB BSON objects and vice versa.
*/
public final class ConnectivityMongoEventAdapter extends AbstractMongoEventAdapter<ConnectivityEvent<?>> {

private final FieldsEncryptionConfig encryptionConfig;

public ConnectivityMongoEventAdapter(@Nullable final ExtendedActorSystem system) {
super(system, createEventRegistry());
final DittoConnectivityConfig connectivityConfig = DittoConnectivityConfig.of(
DefaultScopedConfig.dittoScoped(system.settings().config()));
encryptionConfig = connectivityConfig.getConnectionConfig().getFieldsEncryptionConfig();
}

@Override
protected JsonObject performToJournalMigration(final JsonObject jsonObject) {
if (encryptionConfig.isEnabled()) {
return JsonFieldsEncryptor.encrypt(jsonObject, encryptionConfig.getJsonPointers(),
encryptionConfig.getSymmetricalKey());
}
return jsonObject;
}

@Override
protected JsonObject performFromJournalMigration(final JsonObject jsonObject) {
return JsonFieldsEncryptor.decrypt(jsonObject, encryptionConfig.getJsonPointers(),
encryptionConfig.getSymmetricalKey());
}

private static EventRegistry<ConnectivityEvent<?>> createEventRegistry() {
Expand Down
Loading

0 comments on commit aeead4d

Please sign in to comment.