Skip to content

Commit

Permalink
Merge pull request #2006 from beyonnex-io/mongodb-iam-auth-with-crede…
Browse files Browse the repository at this point in the history
…ntial-provider

provide AWS credential for MongoDB IAM authentication via supplier
  • Loading branch information
thjaeckle authored Aug 27, 2024
2 parents b2e42bc + 4c87967 commit 88ee0a6
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 71 deletions.
2 changes: 1 addition & 1 deletion deployment/helm/ditto/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ description: |
A digital twin is a virtual, cloud based, representation of his real world counterpart
(real world “Things”, e.g. devices like sensors, smart heating, connected cars, smart grids, EV charging stations etc).
type: application
version: 3.5.14 # chart version is effectively set by release-job
version: 3.5.16 # chart version is effectively set by release-job
appVersion: 3.5.10
keywords:
- iot-chart
Expand Down
22 changes: 0 additions & 22 deletions deployment/helm/ditto/templates/_helpers.tpl
Original file line number Diff line number Diff line change
Expand Up @@ -70,25 +70,3 @@ We truncate at 63 chars because some Kubernetes name fields are limited to this
{{- end -}}
{{- end -}}
{{- end -}}

{{/*
Get IAM Role ARN from Values if USE_IAM_ROLE is true
*/}}
{{- define "ditto.iamRoleArn" -}}
{{- if .Values.serviceAccount.isUseAwsIamRole -}}
{{ .Values.serviceAccount.roleArn }}
{{- else -}}
""
{{- end -}}
{{- end -}}

{{/*
Get AWS session name from Values if USE_IAM_ROLE is true
*/}}
{{- define "ditto.awsSessionName" -}}
{{- if .Values.serviceAccount.isUseAwsIamRole -}}
{{ .Values.serviceAccount.awsSessionName }}
{{- else -}}
"defaultSessionName"
{{- end -}}
{{- end -}}
8 changes: 5 additions & 3 deletions deployment/helm/ditto/templates/connectivity-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -284,16 +284,18 @@ spec:
{{- if .Values.connectivity.extraEnv }}
{{- toYaml .Values.connectivity.extraEnv | nindent 12 }}
{{- end }}
{{- if .Values.serviceAccount.isUseAwsIamRole }}
{{- if .Values.serviceAccount.useAwsIamRole }}
- name: MONGO_DB_AWS_REGION
value: "{{ .Values.serviceAccount.awsRegion }}"
- name: MONGO_DB_AWS_ROLE_ARN
value: "{{ .Values.serviceAccount.roleArn }}"
value: "{{ .Values.serviceAccount.awsRoleArn }}"
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"
- name: MONGO_DB_AWS_SESSION_NAME
value: "{{ .Values.serviceAccount.awsSessionName }}"
{{- end }}
- name: MONGO_DB_USE_AWS_IAM_ROLE
value: "{{ printf "%t" .Values.serviceAccount.isUseAwsIamRole }}"
value: "{{ printf "%t" .Values.serviceAccount.useAwsIamRole }}"
ports:
- name: remoting
containerPort: {{ .Values.pekko.remoting.port }}
Expand Down
8 changes: 5 additions & 3 deletions deployment/helm/ditto/templates/policies-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -268,16 +268,18 @@ spec:
{{- if .Values.policies.extraEnv }}
{{- toYaml .Values.policies.extraEnv | nindent 12 }}
{{- end }}
{{- if .Values.serviceAccount.isUseAwsIamRole }}
{{- if .Values.serviceAccount.useAwsIamRole }}
- name: MONGO_DB_AWS_REGION
value: "{{ .Values.serviceAccount.awsRegion }}"
- name: MONGO_DB_AWS_ROLE_ARN
value: "{{ .Values.serviceAccount.roleArn }}"
value: "{{ .Values.serviceAccount.awsRoleArn }}"
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"
- name: MONGO_DB_AWS_SESSION_NAME
value: "{{ .Values.serviceAccount.awsSessionName }}"
{{- end }}
- name: MONGO_DB_USE_AWS_IAM_ROLE
value: "{{ printf "%t" .Values.serviceAccount.isUseAwsIamRole }}"
value: "{{ printf "%t" .Values.serviceAccount.useAwsIamRole }}"
ports:
- name: http
containerPort: 8080
Expand Down
4 changes: 2 additions & 2 deletions deployment/helm/ditto/templates/serviceaccount.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ metadata:
labels:
app.kubernetes.io/name: {{ include "ditto.name" . }}
annotations:
{{- if .Values.serviceAccount.isUseAwsIamRole }}
eks.amazonaws.com/role-arn: {{ .Values.serviceAccount.roleArn }}
{{- if .Values.serviceAccount.useAwsIamRole }}
eks.amazonaws.com/role-arn: {{ .Values.serviceAccount.awsRoleArn }}
{{- end }}
{{ include "ditto.labels" . | indent 4 }}
{{- end -}}
8 changes: 5 additions & 3 deletions deployment/helm/ditto/templates/things-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -351,16 +351,18 @@ spec:
{{- if .Values.things.extraEnv }}
{{- toYaml .Values.things.extraEnv | nindent 12 }}
{{- end }}
{{- if .Values.serviceAccount.isUseAwsIamRole }}
{{- if .Values.serviceAccount.useAwsIamRole }}
- name: MONGO_DB_AWS_REGION
value: "{{ .Values.serviceAccount.awsRegion }}"
- name: MONGO_DB_AWS_ROLE_ARN
value: "{{ .Values.serviceAccount.roleArn }}"
value: "{{ .Values.serviceAccount.awsRoleArn }}"
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"
- name: MONGO_DB_AWS_SESSION_NAME
value: "{{ .Values.serviceAccount.awsSessionName }}"
{{- end }}
- name: MONGO_DB_USE_AWS_IAM_ROLE
value: "{{ printf "%t" .Values.serviceAccount.isUseAwsIamRole }}"
value: "{{ printf "%t" .Values.serviceAccount.useAwsIamRole }}"
ports:
- name: remoting
containerPort: {{ .Values.pekko.remoting.port }}
Expand Down
8 changes: 5 additions & 3 deletions deployment/helm/ditto/templates/thingssearch-deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -263,16 +263,18 @@ spec:
{{- if .Values.thingsSearch.extraEnv }}
{{- toYaml .Values.thingsSearch.extraEnv | nindent 12 }}
{{- end }}
{{- if .Values.serviceAccount.isUseAwsIamRole }}
{{- if .Values.serviceAccount.useAwsIamRole }}
- name: MONGO_DB_AWS_REGION
value: "{{ .Values.serviceAccount.awsRegion }}"
- name: MONGO_DB_AWS_ROLE_ARN
value: "{{ .Values.serviceAccount.roleArn }}"
value: "{{ .Values.serviceAccount.awsRoleArn }}"
- name: AWS_WEB_IDENTITY_TOKEN_FILE
value: "/var/run/secrets/eks.amazonaws.com/serviceaccount/token"
- name: MONGO_DB_AWS_SESSION_NAME
value: "{{ .Values.serviceAccount.awsSessionName }}"
{{- end }}
- name: MONGO_DB_USE_AWS_IAM_ROLE
value: "{{ printf "%t" .Values.serviceAccount.isUseAwsIamRole }}"
value: "{{ printf "%t" .Values.serviceAccount.useAwsIamRole }}"
ports:
- name: remoting
containerPort: {{ .Values.pekko.remoting.port }}
Expand Down
14 changes: 8 additions & 6 deletions deployment/helm/ditto/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,16 @@ serviceAccount:
# name is the name of the service account to use
# If not set and create is true, a name is generated using the fullname template
name:
# isUseAwsIamRole specifies whether to use an AWS IAM Role for Service Accounts (IRSA).
# useAwsIamRole specifies whether to use an AWS IAM Role for Service Accounts (IRSA).
# Set to true to use IRSA, which allows the pod to assume an IAM role.
# When set to true, ensure that roleArn is specified.
isUseAwsIamRole: false
# roleArn is the Amazon Resource Name (ARN) of the IAM role to be assumed by the service account.
# This is required if isUseAwsIamRole is set to true.
# When set to true, ensure that awsRoleArn is specified.
useAwsIamRole: false
# awsRegion
awsRegion: ""
# awsRoleArn is the Amazon Resource Name (ARN) of the IAM role to be assumed by the service account.
# This is required if useAwsIamRole is set to true.
# Example: arn:aws:iam::<account-id>:role/<role-name>
roleArn: ""
awsRoleArn: ""
# awsSessionName is the name of the session when assuming the AWS role.
# This can be used to identify the session in logs or IAM policies.
# Default is "dittoSession".
Expand Down
5 changes: 5 additions & 0 deletions internal/utils/config/src/main/resources/ditto-mongo.conf
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ ditto.mongodb {
useAwsIamRole = false
useAwsIamRole = ${?MONGO_DB_USE_AWS_IAM_ROLE}

# Specifies the AWS region - if this is an empty string, the AWS SDK will attempt to identify the
# region automatically based on the environment and eventually EC2 instances
awsRegion = ""
awsRegion = ${?MONGO_DB_AWS_REGION}

# Specifies the ARN of the AWS IAM Role to be assumed for MongoDB authentication.
awsRoleArn = ""
awsRoleArn = ${?MONGO_DB_AWS_ROLE_ARN}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,24 +20,28 @@
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;
import javax.net.ssl.SSLContext;

import org.bson.Document;
import org.bson.conversions.Bson;
import org.eclipse.ditto.internal.utils.persistence.mongo.config.MongoDbConfig;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.mongodb.AwsCredential;
import com.mongodb.ClientSessionOptions;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.MongoCredential;
import com.mongodb.ReadConcern;
import com.mongodb.ReadPreference;
import com.mongodb.ServerAddress;
import com.mongodb.WriteConcern;
import com.mongodb.MongoCredential;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.eclipse.ditto.internal.utils.persistence.mongo.config.MongoDbConfig;
import org.reactivestreams.Publisher;

import com.mongodb.connection.ClusterDescription;
import com.mongodb.connection.ServerDescription;
import com.mongodb.connection.TransportSettings;
Expand All @@ -54,18 +58,22 @@

import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.AwsSessionCredentials;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sts.StsClient;
import software.amazon.awssdk.services.sts.auth.StsAssumeRoleCredentialsProvider;
import software.amazon.awssdk.services.sts.StsClientBuilder;
import software.amazon.awssdk.services.sts.model.AssumeRoleRequest;
import software.amazon.awssdk.services.sts.model.AssumeRoleResponse;
import software.amazon.awssdk.services.sts.model.Credentials;

/**
* Default implementation of DittoMongoClient.
*/
@NotThreadSafe
public final class MongoClientWrapper implements DittoMongoClient {

private static final Logger LOGGER = LoggerFactory.getLogger(MongoClientWrapper.class);

private final MongoClient mongoClient;
private final MongoClientSettings clientSettings;
private final MongoDatabase defaultDatabase;
Expand Down Expand Up @@ -266,6 +274,7 @@ static final class MongoClientWrapperBuilder implements DittoMongoClientBuilder,
private String defaultDatabaseName;
private boolean sslEnabled;
private boolean isUseAwsIamRole;
private String awsRegion;
private String awsRoleArn;
private String awsSessionName;
@Nullable private EventLoopGroup eventLoopGroup;
Expand Down Expand Up @@ -317,6 +326,7 @@ static GeneralPropertiesStep newInstance(final MongoDbConfig mongoDbConfig) {
final MongoDbConfig.OptionsConfig optionsConfig = mongoDbConfig.getOptionsConfig();
builder.enableSsl(optionsConfig.isSslEnabled());
builder.useAwsIamRole(optionsConfig.isUseAwsIamRole());
builder.awsRegion(optionsConfig.awsRegion());
builder.awsRoleArn(optionsConfig.awsRoleArn());
builder.awsSessionName(optionsConfig.awsSessionName());
builder.setReadPreference(optionsConfig.readPreference().getMongoReadPreference());
Expand All @@ -332,6 +342,11 @@ public MongoClientWrapperBuilder useAwsIamRole(final boolean useAwsIamRole) {
return this;
}

public MongoClientWrapperBuilder awsRegion(final String awsRegion) {
this.awsRegion = awsRegion;
return this;
}

public MongoClientWrapperBuilder awsRoleArn(final String awsRoleArn) {
this.awsRoleArn = awsRoleArn;
return this;
Expand Down Expand Up @@ -508,26 +523,36 @@ private static SSLContext createAndInitSslContext() throws NoSuchAlgorithmExcept
}

private void applyAwsIamRoleSettings() {
final StsClient stsClient = StsClient.builder().build();

final AwsSessionCredentials tempCredentials = (AwsSessionCredentials) StsAssumeRoleCredentialsProvider.builder()
.stsClient(stsClient)
.refreshRequest(req -> req.roleArn(awsRoleArn).roleSessionName(awsSessionName))
.build()
.resolveCredentials();
final StsClientBuilder stsClientBuilder = StsClient.builder()
.credentialsProvider(DefaultCredentialsProvider.create());
if (!awsRegion.isEmpty()) {
stsClientBuilder.region(Region.of(awsRegion));
}

final String accessKeyId = tempCredentials.accessKeyId();
final char[] secretAccessKey = tempCredentials.secretAccessKey().toCharArray();
String sessionToken = tempCredentials.sessionToken();
final Supplier<AwsCredential> awsFreshCredentialSupplier;
try (final StsClient stsClient = stsClientBuilder.build()) {
awsFreshCredentialSupplier = () -> {
LOGGER.info("Supplying AWS IAM credentials, assuming role <{}> in session name <{}>",
awsRoleArn, awsSessionName);

final MongoCredential credential = MongoCredential.createAwsCredential(accessKeyId, secretAccessKey)
.withMechanismProperty("AWS_SESSION_TOKEN", sessionToken);
// assume role using the AWS SDK
final AssumeRoleRequest roleRequest = AssumeRoleRequest.builder()
.roleArn(awsRoleArn)
.roleSessionName(awsSessionName)
.build();
final AssumeRoleResponse roleResponse = stsClient.assumeRole(roleRequest);
final Credentials awsCredentials = roleResponse.credentials();

mongoClientSettingsBuilder.credential(credential);
}
return new AwsCredential(awsCredentials.accessKeyId(), awsCredentials.secretAccessKey(),
awsCredentials.sessionToken());
};

private static AwsCredentialsProvider getAwsCredentialsProvider() {
return DefaultCredentialsProvider.create();
final MongoCredential credential = MongoCredential.createAwsCredential(null, null)
.withMechanismProperty(MongoCredential.AWS_CREDENTIAL_PROVIDER_KEY, awsFreshCredentialSupplier);

mongoClientSettingsBuilder.credential(credential);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public final class DefaultOptionsConfig implements MongoDbConfig.OptionsConfig {
static final String CONFIG_PATH = "options";

private final boolean useAwsIamRole;
private final String awsRegion;
private final String awsArnRole;
private final String awsSessionName;
private final boolean sslEnabled;
Expand All @@ -51,9 +52,10 @@ public final class DefaultOptionsConfig implements MongoDbConfig.OptionsConfig {

private DefaultOptionsConfig(final ScopedConfig config) {
useAwsIamRole = config.getBoolean(OptionsConfigValue.USE_AWS_IAM_ROLE.getConfigPath());
awsRegion = config.getString(OptionsConfigValue.AWS_REGION.getConfigPath());
awsArnRole = config.getString(OptionsConfigValue.AWS_ROLE_ARN.getConfigPath());
sslEnabled = config.getBoolean(OptionsConfigValue.SSL_ENABLED.getConfigPath());
this.awsSessionName = config.getString(OptionsConfigValue.AWS_SESSION_NAME.getConfigPath());;
this.awsSessionName = config.getString(OptionsConfigValue.AWS_SESSION_NAME.getConfigPath());
final var readPreferenceString = config.getString(OptionsConfigValue.READ_PREFERENCE.getConfigPath());
readPreference = ReadPreference.ofReadPreference(readPreferenceString)
.orElseThrow(() -> {
Expand Down Expand Up @@ -129,13 +131,18 @@ public boolean isUseAwsIamRole() {
return useAwsIamRole;
}

@Override
public String awsRegion() {
return awsRegion;
}

@Override
public String awsRoleArn() {
return awsArnRole;
}

@Override
public String awsSessionName() { return awsSessionName; }
public String awsSessionName() {return awsSessionName;}

@Override
public Map<String, Object> extraUriOptions() {
Expand All @@ -152,6 +159,7 @@ public boolean equals(final Object o) {
}
final DefaultOptionsConfig that = (DefaultOptionsConfig) o;
return useAwsIamRole == that.useAwsIamRole
&& Objects.equals(awsRegion, that.awsRegion)
&& Objects.equals(awsArnRole, that.awsArnRole)
&& Objects.equals(awsSessionName, that.awsSessionName)
&& sslEnabled == that.sslEnabled
Expand All @@ -164,13 +172,15 @@ public boolean equals(final Object o) {

@Override
public int hashCode() {
return Objects.hash(useAwsIamRole, awsArnRole, awsSessionName, sslEnabled, readPreference, readConcern, writeConcern, retryWrites, extraUriOptions);
return Objects.hash(useAwsIamRole, awsRegion, awsArnRole, awsArnRole, awsSessionName, sslEnabled,
readPreference, readConcern, writeConcern, retryWrites, extraUriOptions);
}

@Override
public String toString() {
return getClass().getSimpleName() + " [" +
"useAwsIamRole=" + useAwsIamRole +
", awsRegion=" + awsRegion +
", awsArnRole=" + awsArnRole +
", awsSessionName=" + awsSessionName +
", sslEnabled=" + sslEnabled +
Expand Down
Loading

0 comments on commit 88ee0a6

Please sign in to comment.