diff --git a/deployment/helm/ditto/Chart.yaml b/deployment/helm/ditto/Chart.yaml index 01fdc4f0d5..4a47303ba3 100644 --- a/deployment/helm/ditto/Chart.yaml +++ b/deployment/helm/ditto/Chart.yaml @@ -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 diff --git a/deployment/helm/ditto/templates/_helpers.tpl b/deployment/helm/ditto/templates/_helpers.tpl index e2b199891d..8ba1d57081 100644 --- a/deployment/helm/ditto/templates/_helpers.tpl +++ b/deployment/helm/ditto/templates/_helpers.tpl @@ -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 -}} \ No newline at end of file diff --git a/deployment/helm/ditto/templates/connectivity-deployment.yaml b/deployment/helm/ditto/templates/connectivity-deployment.yaml index 21ce466168..e3c50f16f0 100644 --- a/deployment/helm/ditto/templates/connectivity-deployment.yaml +++ b/deployment/helm/ditto/templates/connectivity-deployment.yaml @@ -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 }} diff --git a/deployment/helm/ditto/templates/policies-deployment.yaml b/deployment/helm/ditto/templates/policies-deployment.yaml index e397bb19f9..74e9ef1347 100644 --- a/deployment/helm/ditto/templates/policies-deployment.yaml +++ b/deployment/helm/ditto/templates/policies-deployment.yaml @@ -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 diff --git a/deployment/helm/ditto/templates/serviceaccount.yaml b/deployment/helm/ditto/templates/serviceaccount.yaml index 8ee0a155e2..ff865f6889 100644 --- a/deployment/helm/ditto/templates/serviceaccount.yaml +++ b/deployment/helm/ditto/templates/serviceaccount.yaml @@ -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 -}} diff --git a/deployment/helm/ditto/templates/things-deployment.yaml b/deployment/helm/ditto/templates/things-deployment.yaml index 8e531d2876..bf29213f67 100644 --- a/deployment/helm/ditto/templates/things-deployment.yaml +++ b/deployment/helm/ditto/templates/things-deployment.yaml @@ -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 }} diff --git a/deployment/helm/ditto/templates/thingssearch-deployment.yaml b/deployment/helm/ditto/templates/thingssearch-deployment.yaml index c4190ad82b..97f392c636 100644 --- a/deployment/helm/ditto/templates/thingssearch-deployment.yaml +++ b/deployment/helm/ditto/templates/thingssearch-deployment.yaml @@ -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 }} diff --git a/deployment/helm/ditto/values.yaml b/deployment/helm/ditto/values.yaml index 59a93f1545..d003d8e30f 100644 --- a/deployment/helm/ditto/values.yaml +++ b/deployment/helm/ditto/values.yaml @@ -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:::role/ - 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". diff --git a/internal/utils/config/src/main/resources/ditto-mongo.conf b/internal/utils/config/src/main/resources/ditto-mongo.conf index 15d3c2eb27..58ec81ac23 100644 --- a/internal/utils/config/src/main/resources/ditto-mongo.conf +++ b/internal/utils/config/src/main/resources/ditto-mongo.conf @@ -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} diff --git a/internal/utils/persistence/src/main/java/org/eclipse/ditto/internal/utils/persistence/mongo/MongoClientWrapper.java b/internal/utils/persistence/src/main/java/org/eclipse/ditto/internal/utils/persistence/mongo/MongoClientWrapper.java index dc98473cf1..174d988dd0 100644 --- a/internal/utils/persistence/src/main/java/org/eclipse/ditto/internal/utils/persistence/mongo/MongoClientWrapper.java +++ b/internal/utils/persistence/src/main/java/org/eclipse/ditto/internal/utils/persistence/mongo/MongoClientWrapper.java @@ -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; @@ -54,11 +58,13 @@ 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. @@ -66,6 +72,8 @@ @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; @@ -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; @@ -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()); @@ -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; @@ -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 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); + } } } } diff --git a/internal/utils/persistence/src/main/java/org/eclipse/ditto/internal/utils/persistence/mongo/config/DefaultOptionsConfig.java b/internal/utils/persistence/src/main/java/org/eclipse/ditto/internal/utils/persistence/mongo/config/DefaultOptionsConfig.java index 31a905fa23..c3776a26d3 100644 --- a/internal/utils/persistence/src/main/java/org/eclipse/ditto/internal/utils/persistence/mongo/config/DefaultOptionsConfig.java +++ b/internal/utils/persistence/src/main/java/org/eclipse/ditto/internal/utils/persistence/mongo/config/DefaultOptionsConfig.java @@ -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; @@ -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(() -> { @@ -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 extraUriOptions() { @@ -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 @@ -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 + diff --git a/internal/utils/persistence/src/main/java/org/eclipse/ditto/internal/utils/persistence/mongo/config/MongoDbConfig.java b/internal/utils/persistence/src/main/java/org/eclipse/ditto/internal/utils/persistence/mongo/config/MongoDbConfig.java index 52394b4468..ff5c82c9c1 100644 --- a/internal/utils/persistence/src/main/java/org/eclipse/ditto/internal/utils/persistence/mongo/config/MongoDbConfig.java +++ b/internal/utils/persistence/src/main/java/org/eclipse/ditto/internal/utils/persistence/mongo/config/MongoDbConfig.java @@ -172,6 +172,15 @@ interface OptionsConfig { */ boolean isUseAwsIamRole(); + /** + * Retrieves the AWS region used for IAM authentication. + * If this is an empty string, the AWS SDK will attempt to identify the region automatically based on the + * environment and eventually EC2 instances. + * + * @return the AWS region as a String. + */ + String awsRegion(); + /** * Retrieves the AWS IAM Role ARN to be assumed for authentication. * @@ -179,7 +188,6 @@ interface OptionsConfig { */ String awsRoleArn(); - /** * Retrieves the AWS session name to be used when assuming the IAM role. * @@ -230,6 +238,11 @@ enum OptionsConfigValue implements KnownConfigValue { */ USE_AWS_IAM_ROLE("useAwsIamRole", false), + /** + * Specifies the region to use in AWS IAM based MongoDB authentication. + */ + AWS_REGION("awsRegion", ""), + /** * Specifies the ARN of the AWS IAM Role to be assumed for MongoDB authentication. */