parseKeyValueMap(String kvString) {
- return parseKeyValueMap(kvString, identity());
- }
-
- /**
- * Parses a key-value pair string such as "param1=WdfaA,param2=AZrr,param3" into a map.
- *
- * Keys with no value are assigned an empty string for convenience. The `valueMapper` function is applied to
- * all values of the map
- *
- * @param kvString a string containing key value pairs. Multiple pairs are separated by comma ','
- * and the keys are separated from the values by the equal sign "="
- * @param valueMapper a function that will be applied to the parsed values. Use Functions.identity to keep the original value.
- * @return a String-String Map
- */
- private static Map parseKeyValueMap(String kvString, Function valueMapper) {
- return Stream.of(
- Optional.ofNullable(kvString)
- .map(StringUtils::trimAllWhitespace)
- .filter(StringUtils::hasText)
- .map(s -> s.split(PAIR_SEPARATOR))
- .orElse(new String[0]))
- .map(StringUtils::trimAllWhitespace)
- .map(pair -> pair.split(KEY_VALUE_SEPARATOR, 2))
- .collect(toMap(
- FIRST.andThen(StringUtils::trimAllWhitespace),
- SECOND.andThen(StringUtils::trimAllWhitespace).andThen(valueMapper)));
- }
-}
diff --git a/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/KmsEncryptionConfiguration.java b/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/KmsEncryptionConfiguration.java
deleted file mode 100644
index 81c4818..0000000
--- a/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/KmsEncryptionConfiguration.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms;
-
-import com.amazonaws.client.builder.AwsClientBuilder.EndpointConfiguration;
-import com.amazonaws.services.kms.AWSKMS;
-import com.amazonaws.services.kms.AWSKMSClient;
-import com.amazonaws.services.kms.AWSKMSClientBuilder;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.cloud.bootstrap.encrypt.EnvironmentDecryptApplicationInitializer;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-import java.util.Optional;
-
-/**
- * This config must be applied to the bootstrap context, which is done by META-INF/spring.factories.
- * The properties here can be configured in bootstrap.[yml|xml|properties], but not in application.[yml]xml|properties]
- */
-@Configuration
-@ConditionalOnProperty(prefix = "aws.kms", name = "enabled", havingValue = "true", matchIfMissing = true)
-@EnableConfigurationProperties(KmsProperties.class)
-class KmsEncryptionConfiguration {
-
- private final KmsTextEncryptor kmsTextEncryptor;
-
- @Autowired
- public KmsEncryptionConfiguration(KmsTextEncryptor kmsTextEncryptor) {
- this.kmsTextEncryptor = kmsTextEncryptor;
- }
-
- @Bean
- EnvironmentDecryptApplicationInitializer environmentDecryptApplicationInitializer() {
- return new EnvironmentDecryptApplicationInitializer(kmsTextEncryptor);
- }
-
- @Configuration
- static class KmsTextEncryptorConfiguration {
-
- private final KmsProperties properties;
-
- private final AWSKMS kms;
-
- @Autowired
- public KmsTextEncryptorConfiguration(KmsProperties properties, AWSKMS kms) {
- this.properties = properties;
- this.kms = kms;
- }
-
- @Bean
- KmsTextEncryptor kmsTextEncryptor() {
- return new KmsTextEncryptor(kms, properties.getKeyId(), properties.getEncryptionAlgorithm());
- }
- }
-
- @Configuration
- @ConditionalOnMissingBean(AWSKMS.class)
- static class KmsConfiguration {
-
- private final KmsProperties properties;
-
- @Autowired
- public KmsConfiguration(KmsProperties properties) {
- this.properties = properties;
- }
-
- @Bean
- public AWSKMS kms() {
- final AWSKMSClientBuilder builder = AWSKMSClient.builder();
-
- if (Optional.ofNullable(properties.getEndpoint()).isPresent()) {
- builder.withEndpointConfiguration(new EndpointConfiguration(properties.getEndpoint().getServiceEndpoint(), properties.getEndpoint().getSigningRegion()));
- } else {
- Optional.ofNullable(properties.getRegion()).ifPresent(builder::setRegion);
- }
-
- return builder.build();
- }
-
- }
-
-}
diff --git a/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/KmsProperties.java b/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/KmsProperties.java
deleted file mode 100644
index badcb8a..0000000
--- a/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/KmsProperties.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms;
-
-import com.amazonaws.regions.Regions;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-@ConfigurationProperties("aws.kms")
-public class KmsProperties {
-
- /**
- * Optional ID or full ARN of the KMS key, e.g.
- *
- * - arn:aws:kms:eu-west-1:089972051332:key/9d9fca31-54c5-4de5-ba4f-128dfb9a5031, or
- * - 9d9fca31-54c5-4de5-ba4f-128dfb9a5031
- *
- * Only needed for encryption of values.
- */
- private String keyId;
-
- /**
- * Optional id of the AWS region of the KMS key that was used for encryption/decryption.
- * Must match the `name` property of one enum entry of {@link Regions}. If not set, the
- *
- * Default Region Provider Chain of the AWS SDK is used.
- */
- private String region;
-
- /**
- * Optional service endpoint and signing region of AWS KMS that you would like to route to.
- * If provided, must supply either a custom created VPC Endpoint or one of the KMS Endpoints listed here.
- * In the event that both region and endpoint properties are both supplied, region will be ignored as region is derived from the service endpoint.
- */
- private Endpoint endpoint;
-
- /**
- * Optional encryption algorithm, that should be used for `encrypt` and `decrypt` operations.
- * For possible values see {@link com.amazonaws.services.kms.model.EncryptionAlgorithmSpec}
- */
- private String encryptionAlgorithm = "SYMMETRIC_DEFAULT";
-
- public static class Endpoint {
-
- /**
- * Required service endpoint, either with or without the protocol (e.g. https://kms.us-west-2.amazonaws.com or kms.us-west-2.amazonaws.com)
- */
- private String serviceEndpoint;
-
- /**
- * Optional signing region. The region to use for SigV4 signing of requests (e.g. us-west-1)
- * In most cases, this can be omitted. There are use cases where a signing region is also
- * needed and it may be different from the region where the service endpoint lives.
- */
- private String signingRegion;
-
- public String getServiceEndpoint() {
- return serviceEndpoint;
- }
-
- public void setServiceEndpoint(String serviceEndpoint) {
- this.serviceEndpoint = serviceEndpoint;
- }
-
- public String getSigningRegion() {
- return signingRegion;
- }
-
- public void setSigningRegion(String signingRegion) {
- this.signingRegion = signingRegion;
- }
-
- }
-
- public String getKeyId() {
- return keyId;
- }
-
- public void setKeyId(String keyId) {
- this.keyId = keyId;
- }
-
- public String getRegion() {
- return region;
- }
-
- public void setRegion(String region) {
- this.region = region;
- }
-
- public Endpoint getEndpoint() {
- return endpoint;
- }
-
- public void setEndpoint(Endpoint endpoint) {
- this.endpoint = endpoint;
- }
-
- public String getEncryptionAlgorithm() {
- return encryptionAlgorithm;
- }
-
- public void setEncryptionAlgorithm(String encryptionAlgorithm) {
- this.encryptionAlgorithm = encryptionAlgorithm;
- }
-}
diff --git a/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/KmsTextEncryptor.java b/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/KmsTextEncryptor.java
deleted file mode 100644
index 60f2336..0000000
--- a/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/KmsTextEncryptor.java
+++ /dev/null
@@ -1,137 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms;
-
-import com.amazonaws.services.kms.AWSKMS;
-import com.amazonaws.services.kms.model.DecryptRequest;
-import com.amazonaws.services.kms.model.EncryptRequest;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.crypto.encrypt.TextEncryptor;
-import org.springframework.util.Assert;
-
-import java.nio.ByteBuffer;
-import java.util.Base64;
-import java.util.Optional;
-
-import static de.zalando.spring.cloud.config.aws.kms.OutputMode.BASE64;
-
-/**
- * This {@link TextEncryptor} uses AWS KMS (Key Management Service) to encrypt / decrypt strings. Encoded cipher strings
- * are represented in Base64 format, to have a nicer string representation (only alpha-numeric chars), that can be
- * easily used as values in property files.
- */
-public class KmsTextEncryptor implements TextEncryptor {
-
- private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder();
- private static final String EMPTY_STRING = "";
-
- private static final boolean IS_ALGORITHM_AVAILABLE;
-
- static {
- boolean available;
- try {
- Class.forName("com.amazonaws.services.kms.model.EncryptionAlgorithmSpec");
- available = true;
- } catch (Exception e) {
- available = false;
- }
- IS_ALGORITHM_AVAILABLE = available;
- }
-
- private final Logger log = LoggerFactory.getLogger(getClass());
- private final AWSKMS kms;
- private final String kmsKeyId;
- private final String encryptionAlgorithm;
-
- /**
- * @param kms The AWS KMS client
- * @param kmsKeyId The ID or full ARN of the KMS key, e.g.
- * arn:aws:kms:eu-west-1:089972051332:key/9d9fca31-54c5-4de5-ba4f-128dfb9a5031. Must not be blank,
- * @param encryptionAlgorithm the encryption algorithm that should be used
- */
- public KmsTextEncryptor(final AWSKMS kms, final String kmsKeyId, final String encryptionAlgorithm) {
- Assert.notNull(kms, "KMS client must not be null");
- Assert.notNull(encryptionAlgorithm, "encryptionAlgorithm must not be null");
- this.kms = kms;
- this.kmsKeyId = kmsKeyId;
- this.encryptionAlgorithm = encryptionAlgorithm;
-
- checkAlgorithm(encryptionAlgorithm);
- }
-
- @Override
- public String encrypt(final String text) {
- Assert.hasText(kmsKeyId, "kmsKeyId must not be blank");
- if (text == null || text.isEmpty()) {
- return EMPTY_STRING;
- } else {
- final EncryptRequest encryptRequest = new EncryptRequest()
- .withKeyId(kmsKeyId)
- .withPlaintext(ByteBuffer.wrap(text.getBytes()));
-
- checkAlgorithm(encryptionAlgorithm);
-
- if (IS_ALGORITHM_AVAILABLE) {
- encryptRequest.setEncryptionAlgorithm(encryptionAlgorithm);
- }
-
- final ByteBuffer encryptedBytes = kms.encrypt(encryptRequest).getCiphertextBlob();
-
- return extractString(encryptedBytes, BASE64);
- }
- }
-
- @Override
- public String decrypt(final String encryptedText) {
- if (encryptedText == null || encryptedText.isEmpty()) {
- return EMPTY_STRING;
- } else {
-
- final EncryptedToken token = EncryptedToken.parse(encryptedText);
-
- final DecryptRequest decryptRequest = new DecryptRequest()
- .withCiphertextBlob(token.getCipherBytes())
- .withEncryptionContext(token.getEncryptionContext());
- final KmsTextEncryptorOptions options = token.getOptions();
- final String keyId = Optional.ofNullable(options.getKeyId()).orElse(kmsKeyId);
- final String algorithm = Optional.ofNullable(options.getEncryptionAlgorithm()).orElse(encryptionAlgorithm);
-
- checkAlgorithm(algorithm);
-
- if (IS_ALGORITHM_AVAILABLE) {
- decryptRequest.setEncryptionAlgorithm(algorithm);
- if (isAsymmetricEncryption(algorithm)) {
- Assert.hasText(keyId, "kmsKeyId must not be blank. Asymmetric decryption requires the key to be known");
- decryptRequest.setKeyId(keyId);
- }
- }
-
- return extractString(kms.decrypt(decryptRequest).getPlaintext(), options.getOutputMode());
- }
- }
-
- private static String extractString(final ByteBuffer bb, final OutputMode outputMode) {
- if (bb.hasRemaining()) {
- final byte[] bytes = new byte[bb.remaining()];
- bb.get(bytes, bb.arrayOffset(), bb.remaining());
- if (outputMode == BASE64) {
- return BASE64_ENCODER.encodeToString(bytes);
- } else {
- return new String(bytes);
- }
- } else {
- return EMPTY_STRING;
- }
- }
-
- private void checkAlgorithm(String algorithm) {
- if (isAsymmetricEncryption(algorithm) && !IS_ALGORITHM_AVAILABLE) {
- log.warn("Asymmetric encryption '{}' has been configured," +
- "but the version of aws-java-sdk you are using is outdated and does not support it. " +
- "Please upgrade to a more recent version.", algorithm);
- }
- }
-
- private static boolean isAsymmetricEncryption(String algorithm) {
- return !algorithm.equals("SYMMETRIC_DEFAULT");
- }
-}
diff --git a/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/KmsTextEncryptorOptions.java b/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/KmsTextEncryptorOptions.java
deleted file mode 100644
index a5657b6..0000000
--- a/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/KmsTextEncryptorOptions.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms;
-
-import java.util.Objects;
-
-class KmsTextEncryptorOptions {
-
- private static final OutputMode DEFAULT_OUTPUT_MODE = OutputMode.PLAIN;
-
- private final OutputMode outputMode;
-
- private final String keyId;
-
- private final String encryptionAlgorithm;
-
- KmsTextEncryptorOptions(OutputMode outputMode, String keyId, String encryptionAlgorithm) {
- this.outputMode = outputMode == null ? DEFAULT_OUTPUT_MODE : outputMode;
- this.keyId = keyId;
- this.encryptionAlgorithm = encryptionAlgorithm;
- }
-
- OutputMode getOutputMode() {
- return outputMode;
- }
-
- String getKeyId() {
- return keyId;
- }
-
- String getEncryptionAlgorithm() {
- return encryptionAlgorithm;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- KmsTextEncryptorOptions that = (KmsTextEncryptorOptions) o;
- return outputMode == that.outputMode &&
- Objects.equals(keyId, that.keyId) &&
- Objects.equals(encryptionAlgorithm, that.encryptionAlgorithm);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(outputMode, keyId, encryptionAlgorithm);
- }
-
- static Builder builder() {
- return new Builder();
- }
-
- static final class Builder {
- private OutputMode outputMode;
- private String keyId;
- private String encryptionAlgorithm;
-
- private Builder() {
- }
-
- public Builder withOutputMode(OutputMode outputMode) {
- this.outputMode = outputMode;
- return this;
- }
-
- public Builder withKeyId(String keyId) {
- this.keyId = keyId;
- return this;
- }
-
- public Builder withEncryptionAlgorithm(String encryptionAlgorithm) {
- this.encryptionAlgorithm = encryptionAlgorithm;
- return this;
- }
-
- public KmsTextEncryptorOptions build() {
- return new KmsTextEncryptorOptions(outputMode, keyId, encryptionAlgorithm);
- }
- }
-}
diff --git a/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/OutputMode.java b/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/OutputMode.java
deleted file mode 100644
index 61d9678..0000000
--- a/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/OutputMode.java
+++ /dev/null
@@ -1,6 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms;
-
-enum OutputMode {
- PLAIN,
- BASE64
-}
diff --git a/library/src/main/resources/META-INF/spring.factories b/library/src/main/resources/META-INF/spring.factories
deleted file mode 100644
index f1a7b95..0000000
--- a/library/src/main/resources/META-INF/spring.factories
+++ /dev/null
@@ -1 +0,0 @@
-org.springframework.cloud.bootstrap.BootstrapConfiguration=de.zalando.spring.cloud.config.aws.kms.KmsEncryptionConfiguration
diff --git a/library/src/test/java/de/zalando/spring/cloud/config/aws/kms/EncryptedTokenTest.java b/library/src/test/java/de/zalando/spring/cloud/config/aws/kms/EncryptedTokenTest.java
deleted file mode 100644
index 5f08b84..0000000
--- a/library/src/test/java/de/zalando/spring/cloud/config/aws/kms/EncryptedTokenTest.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms;
-
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-
-import java.nio.ByteBuffer;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.stream.Stream;
-
-import static de.zalando.spring.cloud.config.aws.kms.OutputMode.BASE64;
-import static de.zalando.spring.cloud.config.aws.kms.OutputMode.PLAIN;
-import static java.util.Collections.emptyMap;
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class EncryptedTokenTest {
-
- private static final KmsTextEncryptorOptions BASE64_OPTIONS = KmsTextEncryptorOptions.builder().withOutputMode(BASE64).build();
- private static final KmsTextEncryptorOptions PLAIN_OPTIONS = KmsTextEncryptorOptions.builder().withOutputMode(PLAIN).build();
- private static final Map EMPTY_MAP = emptyMap();
- private static final Map CONTEXT_MAP;
-
- static {
- CONTEXT_MAP = new HashMap<>();
- CONTEXT_MAP.put("param", "L’homme c’est rien");
- CONTEXT_MAP.put("test", "l’oeuvre c’est tout");
- CONTEXT_MAP.put("valueless", "");
- }
-
- public static Stream data() {
- return Stream.of(
- Arguments.of("(param=TOKAmWhvbW1lIGPigJllc3Qgcmllbg==,test=bOKAmW9ldXZyZSBj4oCZZXN0IHRvdXQ= ,valueless)[foo=bar,output=base64]SGVsbG8gV29ybGQ=", BASE64_OPTIONS, CONTEXT_MAP),
- Arguments.of("[foo=bar,output=base64](param=TOKAmWhvbW1lIGPigJllc3Qgcmllbg==,test=bOKAmW9ldXZyZSBj4oCZZXN0IHRvdXQ= ,valueless)SGVsbG8gV29ybGQ=", BASE64_OPTIONS, CONTEXT_MAP),
- Arguments.of("(param=TOKAmWhvbW1lIGPigJllc3Qgcmllbg==,test=bOKAmW9ldXZyZSBj4oCZZXN0IHRvdXQ= ,valueless)SGVsbG8gV29ybGQ=", PLAIN_OPTIONS, CONTEXT_MAP),
- Arguments.of("[foo=bar,output=base64]SGVsbG8gV29ybGQ=", BASE64_OPTIONS, EMPTY_MAP),
- Arguments.of("SGVsbG8gV29ybGQ=", PLAIN_OPTIONS, EMPTY_MAP),
- Arguments.of("()[]SGVsbG8gV29ybGQ=", PLAIN_OPTIONS, EMPTY_MAP),
- Arguments.of("[]()SGVsbG8gV29ybGQ=", PLAIN_OPTIONS, EMPTY_MAP),
- Arguments.of("()SGVsbG8gV29ybGQ=", PLAIN_OPTIONS, EMPTY_MAP),
- Arguments.of("[]SGVsbG8gV29ybGQ=", PLAIN_OPTIONS, EMPTY_MAP),
- Arguments.of("SGVsbG8gV29ybGQ=", PLAIN_OPTIONS, EMPTY_MAP)
- );
- }
-
- @ParameterizedTest(name = "{index}: fib[{0}]={1}")
- @MethodSource("data")
- public void testParseToken(String testString, KmsTextEncryptorOptions expectedOptions, Map expectedContext) {
- final EncryptedToken token = EncryptedToken.parse(testString);
-
- assertThat(token.getCipherBytes()).isEqualTo(ByteBuffer.wrap("Hello World".getBytes()));
- assertThat(token.getOptions()).isEqualTo(expectedOptions);
- assertThat(token.getEncryptionContext()).isEqualTo(expectedContext);
- }
-}
diff --git a/library/src/test/java/de/zalando/spring/cloud/config/aws/kms/KmsTextEncryptorTest.java b/library/src/test/java/de/zalando/spring/cloud/config/aws/kms/KmsTextEncryptorTest.java
deleted file mode 100644
index 5757c91..0000000
--- a/library/src/test/java/de/zalando/spring/cloud/config/aws/kms/KmsTextEncryptorTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms;
-
-import com.amazonaws.services.kms.AWSKMS;
-import com.amazonaws.services.kms.model.DecryptRequest;
-import com.amazonaws.services.kms.model.DecryptResult;
-import com.amazonaws.services.kms.model.EncryptRequest;
-import com.amazonaws.services.kms.model.EncryptResult;
-import org.junit.jupiter.api.AfterEach;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.Test;
-
-import java.nio.ByteBuffer;
-import java.util.Base64;
-
-import static com.amazonaws.services.kms.model.EncryptionAlgorithmSpec.SYMMETRIC_DEFAULT;
-import static java.nio.ByteBuffer.allocate;
-import static java.nio.ByteBuffer.wrap;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-public class KmsTextEncryptorTest {
-
- private static final String KMS_KEY_ID = "testKeyId";
- private static final String PLAINTEXT = "plaintext";
- private static final String CIPHER_TEXT = "C1PHERT3XT";
- private static final String BASE64_CIPHER_TEXT = new String(Base64.getEncoder().encode(CIPHER_TEXT.getBytes()));
-
- private AWSKMS mockKms;
- private KmsTextEncryptor textEncryptor;
- private EncryptRequest expectedEncryptRequest;
- private EncryptResult encryptResult;
- private DecryptRequest expectedDecryptRequest;
- private DecryptResult decryptResult;
-
- @BeforeEach
- public void setUp() {
- mockKms = mock(AWSKMS.class);
- textEncryptor = new KmsTextEncryptor(mockKms, KMS_KEY_ID, SYMMETRIC_DEFAULT.toString());
-
- expectedEncryptRequest = new EncryptRequest();
- expectedEncryptRequest.setKeyId(KMS_KEY_ID);
- expectedEncryptRequest.setPlaintext(wrap(PLAINTEXT.getBytes()));
- expectedEncryptRequest.setEncryptionAlgorithm(SYMMETRIC_DEFAULT.toString());
-
- encryptResult = new EncryptResult();
- encryptResult.setCiphertextBlob(wrap(CIPHER_TEXT.getBytes()));
- when(mockKms.encrypt(any(EncryptRequest.class))).thenReturn(encryptResult);
-
- expectedDecryptRequest = new DecryptRequest();
- expectedDecryptRequest.setCiphertextBlob(wrap(CIPHER_TEXT.getBytes()));
- expectedDecryptRequest.setEncryptionAlgorithm(SYMMETRIC_DEFAULT.toString());
-
- decryptResult = new DecryptResult();
- decryptResult.setPlaintext(wrap(PLAINTEXT.getBytes()));
- when(mockKms.decrypt(any(DecryptRequest.class))).thenReturn(decryptResult);
- }
-
- @AfterEach
- public void tearDown() {
- verifyNoMoreInteractions(mockKms);
- }
-
- @Test
- public void testEncrypt() {
- assertThat(textEncryptor.encrypt(PLAINTEXT)).isEqualTo(BASE64_CIPHER_TEXT);
- verify(mockKms).encrypt(eq(expectedEncryptRequest));
- }
-
- @Test
- public void testEncryptEmptyResponse() {
- encryptResult.setCiphertextBlob(allocate(0));
- assertThat(textEncryptor.encrypt(PLAINTEXT)).isEqualTo("");
- verify(mockKms).encrypt(eq(expectedEncryptRequest));
- }
-
- @Test
- public void testEncryptNull() {
- assertThat(textEncryptor.encrypt(null)).isEqualTo("");
- }
-
- @Test
- public void testEncryptEmptyString() {
- assertThat(textEncryptor.encrypt("")).isEqualTo("");
- }
-
- @Test
- public void testDecryptNull() {
- assertThat(textEncryptor.decrypt(null)).isEqualTo("");
- }
-
- @Test
- public void testDecryptEmptyString() {
- assertThat(textEncryptor.decrypt("")).isEqualTo("");
- }
-
- @Test
- public void testDecrypt() {
- assertThat(textEncryptor.decrypt(BASE64_CIPHER_TEXT)).isEqualTo(PLAINTEXT);
- verify(mockKms).decrypt(eq(expectedDecryptRequest));
- }
-
- @Test
- public void testDecryptEmptyResult() {
- decryptResult.setPlaintext(ByteBuffer.allocate(0));
- assertThat(textEncryptor.decrypt(BASE64_CIPHER_TEXT)).isEqualTo("");
- verify(mockKms).decrypt(eq(expectedDecryptRequest));
- }
-}
diff --git a/mvnw b/mvnw
new file mode 100755
index 0000000..66df285
--- /dev/null
+++ b/mvnw
@@ -0,0 +1,308 @@
+#!/bin/sh
+# ----------------------------------------------------------------------------
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+# ----------------------------------------------------------------------------
+
+# ----------------------------------------------------------------------------
+# Apache Maven Wrapper startup batch script, version 3.2.0
+#
+# Required ENV vars:
+# ------------------
+# JAVA_HOME - location of a JDK home dir
+#
+# Optional ENV vars
+# -----------------
+# MAVEN_OPTS - parameters passed to the Java VM when running Maven
+# e.g. to debug Maven itself, use
+# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+# ----------------------------------------------------------------------------
+
+if [ -z "$MAVEN_SKIP_RC" ] ; then
+
+ if [ -f /usr/local/etc/mavenrc ] ; then
+ . /usr/local/etc/mavenrc
+ fi
+
+ if [ -f /etc/mavenrc ] ; then
+ . /etc/mavenrc
+ fi
+
+ if [ -f "$HOME/.mavenrc" ] ; then
+ . "$HOME/.mavenrc"
+ fi
+
+fi
+
+# OS specific support. $var _must_ be set to either true or false.
+cygwin=false;
+darwin=false;
+mingw=false
+case "$(uname)" in
+ CYGWIN*) cygwin=true ;;
+ MINGW*) mingw=true;;
+ Darwin*) darwin=true
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
+ else
+ JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
+ fi
+ fi
+ ;;
+esac
+
+if [ -z "$JAVA_HOME" ] ; then
+ if [ -r /etc/gentoo-release ] ; then
+ JAVA_HOME=$(java-config --jre-home)
+ fi
+fi
+
+# For Cygwin, ensure paths are in UNIX format before anything is touched
+if $cygwin ; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
+fi
+
+# For Mingw, ensure paths are in UNIX format before anything is touched
+if $mingw ; then
+ [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
+ JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
+fi
+
+if [ -z "$JAVA_HOME" ]; then
+ javaExecutable="$(which javac)"
+ if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
+ # readlink(1) is not available as standard on Solaris 10.
+ readLink=$(which readlink)
+ if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
+ if $darwin ; then
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
+ else
+ javaExecutable="$(readlink -f "\"$javaExecutable\"")"
+ fi
+ javaHome="$(dirname "\"$javaExecutable\"")"
+ javaHome=$(expr "$javaHome" : '\(.*\)/bin')
+ JAVA_HOME="$javaHome"
+ export JAVA_HOME
+ fi
+ fi
+fi
+
+if [ -z "$JAVACMD" ] ; then
+ if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ else
+ JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
+ fi
+fi
+
+if [ ! -x "$JAVACMD" ] ; then
+ echo "Error: JAVA_HOME is not defined correctly." >&2
+ echo " We cannot execute $JAVACMD" >&2
+ exit 1
+fi
+
+if [ -z "$JAVA_HOME" ] ; then
+ echo "Warning: JAVA_HOME environment variable is not set."
+fi
+
+# traverses directory structure from process work directory to filesystem root
+# first directory with .mvn subdirectory is considered project base directory
+find_maven_basedir() {
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
+ while [ "$wdir" != '/' ] ; do
+ if [ -d "$wdir"/.mvn ] ; then
+ basedir=$wdir
+ break
+ fi
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=$(cd "$wdir/.." || exit 1; pwd)
+ fi
+ # end of workaround
+ done
+ printf '%s' "$(cd "$basedir" || exit 1; pwd)"
+}
+
+# concatenates all lines of a file
+concat_lines() {
+ if [ -f "$1" ]; then
+ # Remove \r in case we run on Windows within Git Bash
+ # and check out the repository with auto CRLF management
+ # enabled. Otherwise, we may read lines that are delimited with
+ # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
+ # splitting rules.
+ tr -s '\r\n' ' ' < "$1"
+ fi
+}
+
+log() {
+ if [ "$MVNW_VERBOSE" = true ]; then
+ printf '%s\n' "$1"
+ fi
+}
+
+BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
+log "$MAVEN_PROJECTBASEDIR"
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
+if [ -r "$wrapperJarPath" ]; then
+ log "Found $wrapperJarPath"
+else
+ log "Couldn't find $wrapperJarPath, downloading it ..."
+
+ if [ -n "$MVNW_REPOURL" ]; then
+ wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ else
+ wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ fi
+ while IFS="=" read -r key value; do
+ # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
+ safeValue=$(echo "$value" | tr -d '\r')
+ case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
+ esac
+ done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+ log "Downloading from: $wrapperUrl"
+
+ if $cygwin; then
+ wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
+ fi
+
+ if command -v wget > /dev/null; then
+ log "Found wget ... using wget"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ else
+ wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ log "Found curl ... using curl"
+ [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ else
+ curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
+ fi
+ else
+ log "Falling back to using Java to download"
+ javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaSource=$(cygpath --path --windows "$javaSource")
+ javaClass=$(cygpath --path --windows "$javaClass")
+ fi
+ if [ -e "$javaSource" ]; then
+ if [ ! -e "$javaClass" ]; then
+ log " - Compiling MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/javac" "$javaSource")
+ fi
+ if [ -e "$javaClass" ]; then
+ log " - Running MavenWrapperDownloader.java ..."
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+# If specified, validate the SHA-256 sum of the Maven wrapper jar file
+wrapperSha256Sum=""
+while IFS="=" read -r key value; do
+ case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
+ esac
+done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
+if [ -n "$wrapperSha256Sum" ]; then
+ wrapperSha256Result=false
+ if command -v sha256sum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ elif command -v shasum > /dev/null; then
+ if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
+ wrapperSha256Result=true
+ fi
+ else
+ echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
+ echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
+ exit 1
+ fi
+ if [ $wrapperSha256Result = false ]; then
+ echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
+ echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
+ echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
+ exit 1
+ fi
+fi
+
+MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin; then
+ [ -n "$JAVA_HOME" ] &&
+ JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
+ [ -n "$CLASSPATH" ] &&
+ CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
+ [ -n "$MAVEN_PROJECTBASEDIR" ] &&
+ MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
+fi
+
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
+export MAVEN_CMD_LINE_ARGS
+
+WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+# shellcheck disable=SC2086 # safe args
+exec "$JAVACMD" \
+ $MAVEN_OPTS \
+ $MAVEN_DEBUG_OPTS \
+ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
+ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
+ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
diff --git a/mvnw.cmd b/mvnw.cmd
new file mode 100644
index 0000000..95ba6f5
--- /dev/null
+++ b/mvnw.cmd
@@ -0,0 +1,205 @@
+@REM ----------------------------------------------------------------------------
+@REM Licensed to the Apache Software Foundation (ASF) under one
+@REM or more contributor license agreements. See the NOTICE file
+@REM distributed with this work for additional information
+@REM regarding copyright ownership. The ASF licenses this file
+@REM to you under the Apache License, Version 2.0 (the
+@REM "License"); you may not use this file except in compliance
+@REM with the License. You may obtain a copy of the License at
+@REM
+@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM
+@REM Unless required by applicable law or agreed to in writing,
+@REM software distributed under the License is distributed on an
+@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+@REM KIND, either express or implied. See the License for the
+@REM specific language governing permissions and limitations
+@REM under the License.
+@REM ----------------------------------------------------------------------------
+
+@REM ----------------------------------------------------------------------------
+@REM Apache Maven Wrapper startup batch script, version 3.2.0
+@REM
+@REM Required ENV vars:
+@REM JAVA_HOME - location of a JDK home dir
+@REM
+@REM Optional ENV vars
+@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
+@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
+@REM e.g. to debug Maven itself, use
+@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
+@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
+@REM ----------------------------------------------------------------------------
+
+@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
+@echo off
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
+@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
+
+@REM set %HOME% to equivalent of $HOME
+if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
+
+@REM Execute a user defined script before this one
+if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
+@REM check for pre script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
+if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
+:skipRcPre
+
+@setlocal
+
+set ERROR_CODE=0
+
+@REM To isolate internal variables from possible post scripts, we use another setlocal
+@setlocal
+
+@REM ==== START VALIDATION ====
+if not "%JAVA_HOME%" == "" goto OkJHome
+
+echo.
+echo Error: JAVA_HOME not found in your environment. >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+:OkJHome
+if exist "%JAVA_HOME%\bin\java.exe" goto init
+
+echo.
+echo Error: JAVA_HOME is set to an invalid directory. >&2
+echo JAVA_HOME = "%JAVA_HOME%" >&2
+echo Please set the JAVA_HOME variable in your environment to match the >&2
+echo location of your Java installation. >&2
+echo.
+goto error
+
+@REM ==== END VALIDATION ====
+
+:init
+
+@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
+@REM Fallback to current working directory if not found.
+
+set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
+IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
+
+set EXEC_DIR=%CD%
+set WDIR=%EXEC_DIR%
+:findBaseDir
+IF EXIST "%WDIR%"\.mvn goto baseDirFound
+cd ..
+IF "%WDIR%"=="%CD%" goto baseDirNotFound
+set WDIR=%CD%
+goto findBaseDir
+
+:baseDirFound
+set MAVEN_PROJECTBASEDIR=%WDIR%
+cd "%EXEC_DIR%"
+goto endDetectBaseDir
+
+:baseDirNotFound
+set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
+cd "%EXEC_DIR%"
+
+:endDetectBaseDir
+
+IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
+
+@setlocal EnableExtensions EnableDelayedExpansion
+for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
+@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
+
+:endReadAdditionalConfig
+
+SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
+set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+
+set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %WRAPPER_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
+SET WRAPPER_SHA_256_SUM=""
+FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
+)
+IF NOT %WRAPPER_SHA_256_SUM%=="" (
+ powershell -Command "&{"^
+ "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
+ "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
+ " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
+ " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
+ " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
+ " exit 1;"^
+ "}"^
+ "}"
+ if ERRORLEVEL 1 goto error
+)
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% ^
+ %JVM_CONFIG_MAVEN_PROPS% ^
+ %MAVEN_OPTS% ^
+ %MAVEN_DEBUG_OPTS% ^
+ -classpath %WRAPPER_JAR% ^
+ "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
+ %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+if ERRORLEVEL 1 goto error
+goto end
+
+:error
+set ERROR_CODE=1
+
+:end
+@endlocal & set ERROR_CODE=%ERROR_CODE%
+
+if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
+@REM check for post script, once with legacy .bat ending and once with .cmd ending
+if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
+if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
+:skipRcPost
+
+@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
+if "%MAVEN_BATCH_PAUSE%"=="on" pause
+
+if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
+
+cmd /C exit /B %ERROR_CODE%
diff --git a/pom.xml b/pom.xml
index e0d7d2d..8620e09 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,142 +1,109 @@
-
- 4.0.0
+
-
- org.springframework.cloud
- spring-cloud-starter-parent
- 2020.0.3
-
-
+ 4.0.0
+
+
+ org.springframework.cloud
+ spring-cloud-build
+ 4.1.0
+
+
+
+ com.zalando.awspring.cloud
+ zalando-cloud-aws
+ 3.1.1-SNAPSHOT
+ pom
+
+ Zalando Cloud AWS
+ Zalando Cloud AWS
+ https://github.com/zalando/zalando-cloud-aws
+
+
+ Zalando SE
+ http://tech.zalando.com
+
+
+
+ 3.1.0
+ 1.19.3
+
+
+
+ zalando-cloud-aws-kms
+ zalando-cloud-aws-autoconfigure
+ zalando-cloud-aws-starters/zalando-cloud-aws-starter-kms
+ zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample
+
- org.zalando
- spring-cloud-config-aws-kms-parent
- 5.2-SNAPSHOT
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-dependencies
+ ${spring-cloud-aws.version}
+ pom
+ import
+
+
+ org.testcontainers
+ testcontainers-bom
+ ${testcontainers.version}
+ pom
+ import
+
+
+
- pom
+
+ https://github.com/zalando/zalando-cloud-aws
+
+ ossrh
+ Nexus Release Repository
+ https://oss.sonatype.org/service/local/staging/deploy/maven2
+
+
+ ossrh
+ Sonatype Nexus Snapshots
+ https://oss.sonatype.org/content/repositories/snapshots
+
+
+ zalando-cloud-aws-docs
+ https://github.com/zalando/zalando-cloud-aws
+
+
-
- library
- tests
- coverage
- encryption-cli
-
-
- Spring Cloud Config AWS KMS Parent
- Spring Cloud Config add-on that provides encryption via AWS KMS
- http://github.com/zalando/spring-cloud-config-aws-kms
- 2015
-
-
- Zalando SE
- http://tech.zalando.com
-
-
-
-
- The Apache License, Version 2.0
- http://www.apache.org/licenses/LICENSE-2.0.txt
-
-
+
+ scm:git:git://github.com/zalando/zalando-cloud-aws.git
+ scm:git:ssh://git@github.com/zalando/zalando-cloud-aws.git
+ https://github.com/zalando/zalando-cloud-aws
+
-
- André Menneken
- andre@zalando.de
- Zalando SE
- http://tech.zalando.com
-
-
- Felix Roske
- felix@zalando.de
- Zalando SE
- http://tech.zalando.com
-
-
- Michele Randi
- michele@zalando.de
- Zalando SE
- http://tech.zalando.com
-
-
-
-
- scm:git:https://github.com/zalando/spring-cloud-config-aws-kms.git
- scm:git:git@github.com:zalando/spring-cloud-config-aws-kms.git
- https://github.com/zalando/spring-cloud-config-aws-kms
- HEAD
-
-
-
- 1.8
- true
+
+ Felix Roske
+ Zalando SE
+ elix@zalando.de
+
+
+ Oussema Toujani
+ Zalando SE
+ oussema.toujani@zalando.de
+
+
+ Daniel Rohe
+ Zalando SE
+ daniel.rohe@zalando.de
+
+
- 2.5.3
- 1.6.8
- 3.0.1
- 3.3.1
- 3.1.0
- 3.1.0
- 3.8.1
- 2.22.2
- 3.2.0
- 3.2.1
- 2.5.2
- 0.8.7
-
+
+
+ Apache License, Version 2.0
+ https://www.apache.org/licenses/LICENSE-2.0
+
+
-
-
-
-
- maven-surefire-plugin
-
-
- ${surefireArgLine}
-
-
-
- org.jacoco
- jacoco-maven-plugin
- ${jacoco-maven-plugin.version}
-
-
-
- prepare-agent
-
- prepare-agent
-
-
- surefireArgLine
-
-
-
- report
- test
-
- report
-
-
-
-
-
- maven-release-plugin
- ${maven-release-plugin.version}
-
- @{project.version}
- -Pwith-gpg
-
-
-
-
-
-
- maven-deploy-plugin
- ${maven-deploy-plugin.version}
-
- true
-
-
-
-
diff --git a/tests/integration-test-1/pom.xml b/tests/integration-test-1/pom.xml
deleted file mode 100644
index 8697f3e..0000000
--- a/tests/integration-test-1/pom.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
- 4.0.0
-
-
- org.zalando
- spring-cloud-config-aws-kms-tests
- 5.2-SNAPSHOT
-
-
- spring-cloud-config-aws-kms-integration-test-1
- Spring Cloud Config AWS KMS Integration Tests 1
-
- Run Integration Tests with old version of AWS SDK
-
-
-
- 1.11.415
-
- true
- true
-
-
-
-
-
- com.amazonaws
- aws-java-sdk-kms
- ${aws-java-sdk.version}
-
-
- com.amazonaws
- aws-java-sdk-core
- ${aws-java-sdk.version}
-
-
- com.amazonaws
- jmespath-java
- ${aws-java-sdk.version}
-
-
-
-
-
-
- org.zalando
- spring-cloud-config-aws-kms-test-support
- ${project.version}
- test
-
-
-
-
diff --git a/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/AsymmetricEncryptionNotAvailableTest.java b/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/AsymmetricEncryptionNotAvailableTest.java
deleted file mode 100644
index 27db2ea..0000000
--- a/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/AsymmetricEncryptionNotAvailableTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms.it;
-
-import com.amazonaws.services.kms.AWSKMS;
-import com.amazonaws.services.kms.model.DecryptRequest;
-import com.amazonaws.services.kms.model.EncryptRequest;
-import com.amazonaws.services.kms.model.InvalidCiphertextException;
-import com.amazonaws.services.kms.model.InvalidKeyUsageException;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.extension.ExtendWith;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.system.CapturedOutput;
-import org.springframework.boot.test.system.OutputCaptureExtension;
-import org.springframework.security.crypto.encrypt.TextEncryptor;
-import org.springframework.test.context.ActiveProfiles;
-
-import java.nio.ByteBuffer;
-import java.util.Base64;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.verify;
-
-@SpringBootTest
-@ActiveProfiles("asymmetric")
-@ExtendWith(OutputCaptureExtension.class)
-public class AsymmetricEncryptionNotAvailableTest {
-
- private static final String PLAINTEXT = "Hello";
- private static final String CIPHERTEXT = "b29wcw==";
- private static final String VERSION_HINT = "Asymmetric encryption 'RSAES_OAEP_SHA_1' has been configured," +
- "but the version of aws-java-sdk you are using is outdated and does not support it. " +
- "Please upgrade to a more recent version.";
-
- @Autowired
- private AWSKMS mockKms;
-
- @Autowired
- private TextEncryptor textEncryptor;
-
- @Test
- void testAsymmetricEncryptionIsNotAvailable(CapturedOutput output) {
- doThrow(InvalidKeyUsageException.class).when(mockKms).encrypt(any(EncryptRequest.class));
-
- try {
- // Asymmetric algorithm is not available, because an outdated AWS SDK is used. The textEncryptor will
- // print a warning and fall back to symmetric algorithm.
- // Trying to use an asymmetric key with the symmetric algorithm will lead to an exception.
- textEncryptor.encrypt(PLAINTEXT);
- failBecauseExceptionWasNotThrown(InvalidKeyUsageException.class);
- } catch (InvalidKeyUsageException ignored) {
- assertThat(output).contains(VERSION_HINT);
- final EncryptRequest expectedRequest = new EncryptRequest()
- .withKeyId("an-asymmetric-key")
- .withPlaintext(ByteBuffer.wrap(PLAINTEXT.getBytes()));
- verify(mockKms).encrypt(eq(expectedRequest));
- }
- }
-
- @Test
- void testAsymmetricDecryptionIsNotAvailable(CapturedOutput output) {
- doThrow(InvalidCiphertextException.class).when(mockKms).decrypt(any(DecryptRequest.class));
-
- try {
- // Asymmetric algorithm is not available, because an outdated AWS SDK is used. The textEncryptor will
- // print a warning and fall back to symmetric algorithm.
- // Trying to use an asymmetric key with the symmetric algorithm will lead to an exception.
- textEncryptor.decrypt(CIPHERTEXT);
- failBecauseExceptionWasNotThrown(InvalidCiphertextException.class);
- } catch (InvalidCiphertextException ignored) {
- assertThat(output).contains(VERSION_HINT);
- final DecryptRequest expectedRequest = new DecryptRequest()
- .withCiphertextBlob(ByteBuffer.wrap(Base64.getDecoder().decode(CIPHERTEXT.getBytes())));
- verify(mockKms).decrypt(eq(expectedRequest));
- }
- }
-}
diff --git a/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/DisabledKmsEncryptionTest.java b/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/DisabledKmsEncryptionTest.java
deleted file mode 100644
index 6fedf62..0000000
--- a/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/DisabledKmsEncryptionTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms.it;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.ActiveProfiles;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * This integration test shows the usage of "aws.kms.enabled=false" to completely disable KMS, although there are
- * encrypted properties. This may be useful for local development. Please note the "encrypt.failOnError=false" property.
- * If absent the application startup would fail fast, if any cipher-property cannot be decrypted.
- */
-@SpringBootTest({"aws.kms.enabled=false", "encrypt.failOnError=false"})
-@ActiveProfiles("encryption")
-public class DisabledKmsEncryptionTest {
-
- @Value("${secret}")
- private String decryptedSecret;
-
- @Test
- public void testPropertyHasNotBeenDecrypted() {
- assertThat(decryptedSecret).isEmpty();
- }
-}
diff --git a/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/KmsEncryptionTest.java b/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/KmsEncryptionTest.java
deleted file mode 100644
index d0b91bc..0000000
--- a/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/KmsEncryptionTest.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms.it;
-
-import com.amazonaws.services.kms.AWSKMS;
-import com.amazonaws.services.kms.model.DecryptRequest;
-import de.zalando.spring.cloud.config.aws.kms.MockAwsKmsConfig;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.ActiveProfiles;
-
-import java.nio.ByteBuffer;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.verify;
-
-/**
- * This integration test shows the usage of spring-cloud-config-aws-kms. You will find an encrypted property within
- * src/test/resources/ that will be decrypted during the bootstrap phase. In order to make this test runnable on every
- * machine, a mock is used instead of a real AWSKMSClient.
- */
-@SpringBootTest
-@ActiveProfiles("encryption")
-public class KmsEncryptionTest {
-
- private static final ByteBuffer CIPHER_TEXT_BLOB = ByteBuffer.wrap("secret".getBytes());
-
- @Autowired
- private AWSKMS mockKms;
-
- @Value("${secret}")
- private String decryptedSecret;
-
- @Test
- public void testPropertyHasBeenDecrypted() {
-
- assertThat(decryptedSecret).isEqualTo(MockAwsKmsConfig.PLAINTEXT);
-
- final DecryptRequest decryptRequest = new DecryptRequest();
- decryptRequest.setCiphertextBlob(CIPHER_TEXT_BLOB);
- verify(mockKms, atLeastOnce()).decrypt(decryptRequest);
- }
-}
diff --git a/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/KmsEndpointConfigurationTest.java b/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/KmsEndpointConfigurationTest.java
deleted file mode 100644
index 6097732..0000000
--- a/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/KmsEndpointConfigurationTest.java
+++ /dev/null
@@ -1,67 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms.it;
-
-import com.amazonaws.services.kms.AWSKMS;
-import com.amazonaws.services.kms.AWSKMSClient;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.ActiveProfiles;
-import org.springframework.util.ReflectionUtils;
-
-import java.lang.reflect.Field;
-import java.net.URI;
-import java.util.Objects;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * During this integration test, a real AWSKMSClient is created, but there are no encrypted properties, so te client is
- * never used.
- * See src/test/resources/*-noEncryption.yml files.
- */
-@SpringBootTest
-@ActiveProfiles("noEncryptionEndpoint")
-public class KmsEndpointConfigurationTest {
-
-
- @Value("${secret}")
- private String secret;
-
- @Autowired
- private AWSKMS kms;
-
- @Test
- public void testPropertyHasBeenDecrypted() {
- assertThat(secret).isEqualTo("secret");
- }
-
- @Test
- public void testContext() {
- assertThat(kms)
- .isNotNull()
- .isInstanceOf(AWSKMSClient.class);
-
- AWSKMSClient client = (AWSKMSClient) kms;
-
- // prove aws.kms.endpoint.service-endpoint was used to configure the kms client
- Field endpointField = ReflectionUtils.findField(AWSKMSClient.class, "endpoint");
- ReflectionUtils.makeAccessible(Objects.requireNonNull(endpointField));
- Object endpointObject = ReflectionUtils.getField(endpointField, client);
- assertThat(endpointObject)
- .isNotNull()
- .isInstanceOf(URI.class);
- URI endpoint = (URI) endpointObject;
- assertThat(endpoint.toString()).contains("us-east-1");
-
- // prove override was issued via the aws.kms.endpoint.signing-region property
- Field signerRegionField = ReflectionUtils.findField(AWSKMSClient.class, "signerRegionOverride");
- ReflectionUtils.makeAccessible(Objects.requireNonNull(signerRegionField));
- Object signerRegionObject = ReflectionUtils.getField(signerRegionField, client);
- assertThat(signerRegionObject)
- .isNotNull()
- .isInstanceOf(String.class);
- String signerRegion = (String) signerRegionObject;
- assertThat(signerRegion).isEqualTo("us-east-2");
- }
-}
diff --git a/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/KmsRegionConfigurationTest.java b/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/KmsRegionConfigurationTest.java
deleted file mode 100644
index 0d1c35a..0000000
--- a/tests/integration-test-1/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/KmsRegionConfigurationTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms.it;
-
-import com.amazonaws.services.kms.AWSKMS;
-import com.amazonaws.services.kms.AWSKMSClient;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.ActiveProfiles;
-import org.springframework.util.ReflectionUtils;
-
-import java.lang.reflect.Field;
-import java.net.URI;
-import java.util.Objects;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * During this integration test, a real AWSKMSClient is created, but there are no encrypted properties, so the client is
- * never used.
- * See src/test/resources/*-noEncryption.yml files.
- */
-@SpringBootTest
-@ActiveProfiles("noEncryption")
-public class KmsRegionConfigurationTest {
-
- @Value("${secret}")
- private String secret;
-
- @Autowired
- private AWSKMS kms;
-
- @Test
- public void testPropertyIsAvailable() {
- assertThat(secret).isEqualTo("secret");
- }
-
- @Test
- public void testContext() {
- assertThat(kms)
- .isNotNull()
- .isInstanceOf(AWSKMSClient.class);
-
- // endpoint configured based on aws.kms.region property
- AWSKMSClient client = (AWSKMSClient) kms;
- Field field = ReflectionUtils.findField(AWSKMSClient.class, "endpoint");
- ReflectionUtils.makeAccessible(Objects.requireNonNull(field));
- Object endpointObject = ReflectionUtils.getField(field, client);
- assertThat(endpointObject)
- .isNotNull()
- .isInstanceOf(URI.class);
- URI endpoint = (URI) endpointObject;
- assertThat(endpoint.toString()).contains("eu-central-1");
-
- // no override should occur in this configuration
- Field signerRegionField = ReflectionUtils.findField(AWSKMSClient.class, "signerRegionOverride");
- ReflectionUtils.makeAccessible(Objects.requireNonNull(signerRegionField));
- Object signerRegionObject = ReflectionUtils.getField(signerRegionField, client);
- assertThat(signerRegionObject).isNull();
- }
-}
diff --git a/tests/integration-test-1/src/test/resources/application-encryption.yml b/tests/integration-test-1/src/test/resources/application-encryption.yml
deleted file mode 100644
index ab799f9..0000000
--- a/tests/integration-test-1/src/test/resources/application-encryption.yml
+++ /dev/null
@@ -1 +0,0 @@
-secret: '{cipher}c2VjcmV0'
diff --git a/tests/integration-test-1/src/test/resources/application-noEncryption.yml b/tests/integration-test-1/src/test/resources/application-noEncryption.yml
deleted file mode 100644
index 87957a5..0000000
--- a/tests/integration-test-1/src/test/resources/application-noEncryption.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-# simply plaintext
-secret: 'secret'
diff --git a/tests/integration-test-1/src/test/resources/application-noEncryptionEndpoint.yml b/tests/integration-test-1/src/test/resources/application-noEncryptionEndpoint.yml
deleted file mode 100644
index 87957a5..0000000
--- a/tests/integration-test-1/src/test/resources/application-noEncryptionEndpoint.yml
+++ /dev/null
@@ -1,2 +0,0 @@
-# simply plaintext
-secret: 'secret'
diff --git a/tests/integration-test-1/src/test/resources/bootstrap-asymmetric.yml b/tests/integration-test-1/src/test/resources/bootstrap-asymmetric.yml
deleted file mode 100644
index 6bf1920..0000000
--- a/tests/integration-test-1/src/test/resources/bootstrap-asymmetric.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-aws:
- kms:
- useMock: true
- encryptionAlgorithm: "RSAES_OAEP_SHA_1"
- keyId: "an-asymmetric-key"
-spring:
- main:
- banner-mode: "off"
diff --git a/tests/integration-test-1/src/test/resources/bootstrap-encryption.yml b/tests/integration-test-1/src/test/resources/bootstrap-encryption.yml
deleted file mode 100644
index 0c6e5fc..0000000
--- a/tests/integration-test-1/src/test/resources/bootstrap-encryption.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-aws:
- kms:
- useMock: true
- keyId: 'sample-key'
-spring:
- main:
- banner-mode: "off"
diff --git a/tests/integration-test-1/src/test/resources/bootstrap-noEncryption.yml b/tests/integration-test-1/src/test/resources/bootstrap-noEncryption.yml
deleted file mode 100644
index c0bbaf7..0000000
--- a/tests/integration-test-1/src/test/resources/bootstrap-noEncryption.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-aws:
- kms:
- useMock: false
- keyId: 'sample-key'
- region: eu-central-1
-spring:
- main:
- banner-mode: "off"
diff --git a/tests/integration-test-1/src/test/resources/bootstrap-noEncryptionEndpoint.yml b/tests/integration-test-1/src/test/resources/bootstrap-noEncryptionEndpoint.yml
deleted file mode 100644
index 49ab022..0000000
--- a/tests/integration-test-1/src/test/resources/bootstrap-noEncryptionEndpoint.yml
+++ /dev/null
@@ -1,11 +0,0 @@
-aws:
- kms:
- useMock: false
- keyId: 'sample-key'
- region: eu-central-1 # ignored
- endpoint:
- service-endpoint: kms.us-east-1.amazonaws.com
- signing-region: us-east-2
-spring:
- main:
- banner-mode: "off"
diff --git a/tests/integration-test-2/pom.xml b/tests/integration-test-2/pom.xml
deleted file mode 100644
index c6ab880..0000000
--- a/tests/integration-test-2/pom.xml
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
- 4.0.0
-
-
- org.zalando
- spring-cloud-config-aws-kms-tests
- 5.2-SNAPSHOT
-
-
- spring-cloud-config-aws-kms-integration-test-2
- Spring Cloud Config AWS KMS Integration Tests 2
-
- Run Integration Tests with more recent version of AWS SDK
-
-
-
- [1.11.774,)
-
- true
- true
-
-
-
-
-
- com.amazonaws
- aws-java-sdk-kms
- ${aws-java-sdk.version}
-
-
- com.amazonaws
- aws-java-sdk-core
- ${aws-java-sdk.version}
-
-
- com.amazonaws
- jmespath-java
- ${aws-java-sdk.version}
-
-
-
-
-
-
- org.zalando
- spring-cloud-config-aws-kms-test-support
- ${project.version}
- test
-
-
-
-
diff --git a/tests/integration-test-2/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/AsymmetricEncryptionAlgorithmTest.java b/tests/integration-test-2/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/AsymmetricEncryptionAlgorithmTest.java
deleted file mode 100644
index f95bc68..0000000
--- a/tests/integration-test-2/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/AsymmetricEncryptionAlgorithmTest.java
+++ /dev/null
@@ -1,103 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms.it;
-
-import com.amazonaws.services.kms.AWSKMS;
-import com.amazonaws.services.kms.model.DecryptRequest;
-import com.amazonaws.services.kms.model.EncryptRequest;
-import com.amazonaws.services.kms.model.EncryptResult;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.security.crypto.encrypt.TextEncryptor;
-import org.springframework.test.context.ActiveProfiles;
-
-import java.nio.ByteBuffer;
-import java.util.Base64;
-
-import static com.amazonaws.services.kms.model.EncryptionAlgorithmSpec.RSAES_OAEP_SHA_1;
-import static com.amazonaws.services.kms.model.EncryptionAlgorithmSpec.RSAES_OAEP_SHA_256;
-import static com.amazonaws.services.kms.model.EncryptionAlgorithmSpec.SYMMETRIC_DEFAULT;
-import static de.zalando.spring.cloud.config.aws.kms.MockAwsKmsConfig.PLAINTEXT;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.verify;
-
-@SpringBootTest
-@ActiveProfiles("asymmetric")
-public class AsymmetricEncryptionAlgorithmTest {
-
- private static final ByteBuffer CIPHER_TEXT_BLOB1 =
- ByteBuffer.wrap("I'm some asymmetrically encrypted secret".getBytes());
- private static final ByteBuffer CIPHER_TEXT_BLOB2 =
- ByteBuffer.wrap("Symmetric and asymmetric secrets can be mixed".getBytes());
- private static final ByteBuffer CIPHER_TEXT_BLOB3 =
- ByteBuffer.wrap("I have a custom key and algorithm".getBytes());
-
- @Autowired
- private AWSKMS mockKms;
-
- @Autowired
- private TextEncryptor textEncryptor;
-
- @Value("${secret1}")
- private String decryptedSecret1;
-
- @Value("${secret2}")
- private String decryptedSecret2;
-
- @Value("${secret3}")
- private String decryptedSecret3;
-
- @Test
- void testDecryptAsymmetricProperty() {
- assertThat(decryptedSecret1).isEqualTo(PLAINTEXT);
-
- final DecryptRequest decryptRequest = new DecryptRequest();
- decryptRequest.withCiphertextBlob(CIPHER_TEXT_BLOB1);
- decryptRequest.withEncryptionAlgorithm(RSAES_OAEP_SHA_1);
- decryptRequest.withKeyId("asymmetric-sha1-sample-key");
- verify(mockKms, atLeastOnce()).decrypt(eq(decryptRequest));
- }
-
- @Test
- void testAlgorithmsCanBeMixed() {
- assertThat(decryptedSecret2).isEqualTo(PLAINTEXT);
-
- final DecryptRequest decryptRequest = new DecryptRequest();
- decryptRequest.withCiphertextBlob(CIPHER_TEXT_BLOB2);
- decryptRequest.withEncryptionAlgorithm(SYMMETRIC_DEFAULT);
- verify(mockKms, atLeastOnce()).decrypt(eq(decryptRequest));
- }
-
- @Test
- void testSecretWithCustomKeyId() {
- assertThat(decryptedSecret3).isEqualTo(PLAINTEXT);
-
- final DecryptRequest decryptRequest = new DecryptRequest();
- decryptRequest.withCiphertextBlob(CIPHER_TEXT_BLOB3);
- decryptRequest.withEncryptionAlgorithm(RSAES_OAEP_SHA_256);
- decryptRequest.withKeyId("different-key");
- verify(mockKms, atLeastOnce()).decrypt(eq(decryptRequest));
- }
-
- @Test
- void testEncrypt() {
- final byte[] cipherTextBytes = "bla".getBytes();
- final String expectedCipherString = Base64.getEncoder().encodeToString(cipherTextBytes);
- doReturn(new EncryptResult().withCiphertextBlob(ByteBuffer.wrap(cipherTextBytes)))
- .when(mockKms).encrypt(any(EncryptRequest.class));
-
- final String mySecret = "my-secret";
- final String encryptedString = textEncryptor.encrypt(mySecret);
- assertThat(encryptedString).isEqualTo(expectedCipherString);
-
- final EncryptRequest encryptRequest = new EncryptRequest()
- .withEncryptionAlgorithm("RSAES_OAEP_SHA_1")
- .withKeyId("asymmetric-sha1-sample-key")
- .withPlaintext(ByteBuffer.wrap(mySecret.getBytes()));
- verify(mockKms).encrypt(eq(encryptRequest));
- }
-}
diff --git a/tests/integration-test-2/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/AsymmetricEncryptionMissingKeyIdTest.java b/tests/integration-test-2/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/AsymmetricEncryptionMissingKeyIdTest.java
deleted file mode 100644
index dc0c686..0000000
--- a/tests/integration-test-2/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/AsymmetricEncryptionMissingKeyIdTest.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms.it;
-
-import com.amazonaws.services.kms.AWSKMS;
-import com.amazonaws.services.kms.model.DecryptRequest;
-import com.amazonaws.services.kms.model.EncryptRequest;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.security.crypto.encrypt.TextEncryptor;
-import org.springframework.test.context.ActiveProfiles;
-
-import java.util.Base64;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-@SpringBootTest
-@ActiveProfiles("missingKeyId")
-public class AsymmetricEncryptionMissingKeyIdTest {
-
- @Autowired
- private AWSKMS mockKms;
-
- @Autowired
- private TextEncryptor textEncryptor;
-
- @Test
- void testDecryptFails() {
- final String someCipher = Base64.getEncoder().encodeToString("SOME_CIPHER".getBytes());
- try {
- textEncryptor.decrypt(someCipher);
- failBecauseExceptionWasNotThrown(RuntimeException.class);
- } catch (Exception e) {
- assertThat(e).hasMessageContaining("kmsKeyId must not be blank. Asymmetric decryption requires the key to be known");
- }
- verify(mockKms, never()).decrypt(any(DecryptRequest.class));
- }
-
- @Test
- void testEncryptFails() {
- try {
- textEncryptor.encrypt("Hello");
- failBecauseExceptionWasNotThrown(RuntimeException.class);
- } catch (Exception e) {
- assertThat(e).hasMessageContaining("kmsKeyId must not be blank");
- }
-
- verify(mockKms, never()).encrypt(any(EncryptRequest.class));
- }
-}
diff --git a/tests/integration-test-2/src/test/resources/application-asymmetric.yaml b/tests/integration-test-2/src/test/resources/application-asymmetric.yaml
deleted file mode 100644
index e3e84da..0000000
--- a/tests/integration-test-2/src/test/resources/application-asymmetric.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-secret1: "{cipher}SSdtIHNvbWUgYXN5bW1ldHJpY2FsbHkgZW5jcnlwdGVkIHNlY3JldA=="
-secret2: "{cipher}[algorithm=SYMMETRIC_DEFAULT]U3ltbWV0cmljIGFuZCBhc3ltbWV0cmljIHNlY3JldHMgY2FuIGJlIG1peGVk"
-secret3: "{cipher}[algorithm=RSAES_OAEP_SHA_256,keyId=different-key]SSBoYXZlIGEgY3VzdG9tIGtleSBhbmQgYWxnb3JpdGht"
diff --git a/tests/integration-test-2/src/test/resources/bootstrap-asymmetric.yaml b/tests/integration-test-2/src/test/resources/bootstrap-asymmetric.yaml
deleted file mode 100644
index 22ec163..0000000
--- a/tests/integration-test-2/src/test/resources/bootstrap-asymmetric.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-aws:
- kms:
- encryptionAlgorithm: "RSAES_OAEP_SHA_1"
- keyId: "asymmetric-sha1-sample-key"
diff --git a/tests/integration-test-2/src/test/resources/bootstrap-missingKeyId.yaml b/tests/integration-test-2/src/test/resources/bootstrap-missingKeyId.yaml
deleted file mode 100644
index c2b1dcb..0000000
--- a/tests/integration-test-2/src/test/resources/bootstrap-missingKeyId.yaml
+++ /dev/null
@@ -1,3 +0,0 @@
-aws:
- kms:
- encryptionAlgorithm: "RSAES_OAEP_SHA_1"
diff --git a/tests/integration-test-2/src/test/resources/bootstrap.yaml b/tests/integration-test-2/src/test/resources/bootstrap.yaml
deleted file mode 100644
index a4a45a1..0000000
--- a/tests/integration-test-2/src/test/resources/bootstrap.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-aws:
- kms:
- useMock: true
-logging:
- level:
- "de.zalando.spring.cloud.config.aws.kms": DEBUG
diff --git a/tests/integration-test-2/src/test/resources/logback-test.xml b/tests/integration-test-2/src/test/resources/logback-test.xml
deleted file mode 100644
index ed03eae..0000000
--- a/tests/integration-test-2/src/test/resources/logback-test.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
diff --git a/tests/integration-test-3/pom.xml b/tests/integration-test-3/pom.xml
deleted file mode 100644
index bd991d2..0000000
--- a/tests/integration-test-3/pom.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
- 4.0.0
-
-
- org.zalando
- spring-cloud-config-aws-kms-tests
- 5.2-SNAPSHOT
-
-
- spring-cloud-config-aws-kms-integration-test-3
- Spring Cloud Config AWS KMS Integration Tests 3
-
- Run Integration Tests with Spring Cloud Config Server
-
-
- true
- true
-
-
-
-
- org.zalando
- spring-cloud-config-aws-kms-test-support
- ${project.version}
- test
-
-
- org.springframework.cloud
- spring-cloud-config-server
- test
-
-
-
diff --git a/tests/integration-test-3/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/ConfigServerTest.java b/tests/integration-test-3/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/ConfigServerTest.java
deleted file mode 100644
index a36a0a5..0000000
--- a/tests/integration-test-3/src/test/java/de/zalando/spring/cloud/config/aws/kms/it/ConfigServerTest.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms.it;
-
-import com.amazonaws.services.kms.AWSKMS;
-import com.amazonaws.services.kms.model.DecryptRequest;
-import com.amazonaws.services.kms.model.EncryptRequest;
-import com.amazonaws.services.kms.model.EncryptResult;
-import com.amazonaws.services.kms.model.EncryptionAlgorithmSpec;
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.json.BasicJsonTester;
-import org.springframework.boot.test.json.JsonContent;
-import org.springframework.boot.test.web.client.TestRestTemplate;
-import org.springframework.cloud.config.server.EnableConfigServer;
-import org.springframework.http.HttpStatus;
-import org.springframework.http.ResponseEntity;
-
-import java.net.URI;
-import java.nio.ByteBuffer;
-import java.util.Base64;
-
-import static com.amazonaws.services.kms.model.EncryptionAlgorithmSpec.SYMMETRIC_DEFAULT;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.atLeastOnce;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.verify;
-import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
-import static org.springframework.http.MediaType.APPLICATION_FORM_URLENCODED;
-import static org.springframework.http.RequestEntity.post;
-
-@SpringBootTest(webEnvironment = RANDOM_PORT, classes = ConfigServerTest.TestApp.class)
-public class ConfigServerTest {
-
- @Autowired
- private TestRestTemplate rest;
-
- @Autowired
- private AWSKMS mockKms;
-
- private final BasicJsonTester json = new BasicJsonTester(getClass());
-
- @Test
- void testGetConfigFromServer() {
- final ResponseEntity response = rest.getForEntity("/my-test-app/default", String.class);
- assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
- final JsonContent> jsonBody = json.from(response.getBody());
- System.out.println(jsonBody.getJson());
- assertThat(jsonBody).extractingJsonPathValue("$.name")
- .isEqualTo("my-test-app");
- assertThat(jsonBody).extractingJsonPathArrayValue("$.profiles")
- .containsExactly("default");
- assertThat(jsonBody).extractingJsonPathArrayValue("$.propertySources..source['info.foo']")
- .containsExactly("bar");
- assertThat(jsonBody).extractingJsonPathArrayValue("$.propertySources..source['top.secret']")
- .containsExactly("Hello World");
-
- final DecryptRequest expectedRequest = new DecryptRequest()
- .withCiphertextBlob(ByteBuffer.wrap(Base64.getDecoder().decode("c2VjcmV0".getBytes())))
- .withEncryptionAlgorithm(SYMMETRIC_DEFAULT);
- verify(mockKms, atLeastOnce()).decrypt(eq(expectedRequest));
- }
-
- @Test
- void testEncryptEndpoint() {
- final String plainText = "some-plaintext";
- final String cipherText = "cIpHeR";
-
- doAnswer(invocation -> new EncryptResult().withCiphertextBlob(ByteBuffer.wrap(cipherText.getBytes())))
- .when(mockKms).encrypt(any(EncryptRequest.class));
-
- final ResponseEntity response = rest.exchange(
- post(URI.create("/encrypt"))
- .contentType(APPLICATION_FORM_URLENCODED)
- .body(plainText),
- String.class);
- assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
- assertThat(response.getBody()).isEqualTo(Base64.getEncoder().encodeToString(cipherText.getBytes()));
- }
-
- @Test
- void testDecryptEndpoint() {
- final String cipherText = Base64.getEncoder().encodeToString("cIpHeR".getBytes());
-
- // Config Server does a "test" encrypt with the given key
- doAnswer(invocation -> new EncryptResult().withCiphertextBlob(ByteBuffer.wrap(cipherText.getBytes())))
- .when(mockKms).encrypt(any(EncryptRequest.class));
-
- final ResponseEntity response = rest.exchange(
- post(URI.create("/decrypt"))
- .contentType(APPLICATION_FORM_URLENCODED)
- .body(cipherText),
- String.class);
- assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
- assertThat(response.getBody()).isEqualTo("Hello World");
- }
-
- @SpringBootApplication
- @EnableConfigServer
- public static class TestApp {
-
- }
-}
diff --git a/tests/integration-test-3/src/test/resources/bootstrap.yml b/tests/integration-test-3/src/test/resources/bootstrap.yml
deleted file mode 100644
index 8b85c7d..0000000
--- a/tests/integration-test-3/src/test/resources/bootstrap.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-aws:
- kms:
- keyId: my-key-123
- useMock: true
-spring:
- application:
- name: config-server
- cloud:
- config:
- server:
- native:
- searchLocations:
- - classpath:/config-repo
- profiles:
- active: native
-logging:
- level:
- ROOT: info
- org.springframework.cloud: DEBUG
- de.zalando: DEBUG
diff --git a/tests/integration-test-3/src/test/resources/config-repo/my-test-app.yaml b/tests/integration-test-3/src/test/resources/config-repo/my-test-app.yaml
deleted file mode 100644
index 19618f7..0000000
--- a/tests/integration-test-3/src/test/resources/config-repo/my-test-app.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-info:
- foo: "bar"
-top:
- secret: '{cipher}c2VjcmV0'
diff --git a/tests/pom.xml b/tests/pom.xml
deleted file mode 100644
index 76dbfb8..0000000
--- a/tests/pom.xml
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
- 4.0.0
-
-
- org.zalando
- spring-cloud-config-aws-kms-parent
- 5.2-SNAPSHOT
-
-
- spring-cloud-config-aws-kms-tests
- Spring Cloud Config AWS KMS Tests
-
- pom
-
-
- test-support
- integration-test-1
- integration-test-2
- integration-test-3
-
-
-
- true
- true
-
-
-
-
- org.springframework.cloud
- spring-cloud-starter-bootstrap
-
-
-
-
-
-
- org.jacoco
- jacoco-maven-plugin
-
-
-
-
-
diff --git a/tests/test-support/pom.xml b/tests/test-support/pom.xml
deleted file mode 100644
index 5e15bb9..0000000
--- a/tests/test-support/pom.xml
+++ /dev/null
@@ -1,39 +0,0 @@
-
-
- 4.0.0
-
-
- org.zalando
- spring-cloud-config-aws-kms-tests
- 5.2-SNAPSHOT
-
-
- spring-cloud-config-aws-kms-test-support
- Spring Cloud Config AWS KMS Test Support
-
-
- true
- true
-
-
-
-
- org.zalando
- spring-cloud-config-aws-kms
- ${project.version}
- compile
-
-
- org.springframework.boot
- spring-boot-starter-test
- compile
-
-
- org.junit.vintage
- junit-vintage-engine
-
-
-
-
-
-
diff --git a/tests/test-support/src/main/java/de/zalando/spring/cloud/config/aws/kms/MockAwsKmsConfig.java b/tests/test-support/src/main/java/de/zalando/spring/cloud/config/aws/kms/MockAwsKmsConfig.java
deleted file mode 100644
index 70aa7fa..0000000
--- a/tests/test-support/src/main/java/de/zalando/spring/cloud/config/aws/kms/MockAwsKmsConfig.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms;
-
-import com.amazonaws.services.kms.AWSKMS;
-import com.amazonaws.services.kms.model.DecryptRequest;
-import com.amazonaws.services.kms.model.DecryptResult;
-import org.mockito.stubbing.Answer;
-import org.springframework.boot.autoconfigure.AutoConfigureBefore;
-import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-
-import java.lang.reflect.Method;
-import java.nio.ByteBuffer;
-
-import static org.mockito.Mockito.mock;
-
-@Configuration
-@ConditionalOnProperty(prefix = "aws.kms", name = "useMock", havingValue = "true")
-@AutoConfigureBefore(KmsEncryptionConfiguration.class)
-public class MockAwsKmsConfig {
-
- public static final String PLAINTEXT = "Hello World";
-
- private final Answer> defaultAnswer = invocation -> {
- final Method decryptMethod = AWSKMS.class.getMethod("decrypt", DecryptRequest.class);
- if (invocation.getMethod().equals(decryptMethod)) {
- return new DecryptResult().withPlaintext(ByteBuffer.wrap(PLAINTEXT.getBytes()));
- } else {
- throw new IllegalStateException("Unexpected mock invocation: " + invocation);
- }
- };
-
- @Bean
- AWSKMS kms() {
- return mock(AWSKMS.class, defaultAnswer);
- }
-}
diff --git a/tests/test-support/src/main/java/de/zalando/spring/cloud/config/aws/kms/it/TestApplication.java b/tests/test-support/src/main/java/de/zalando/spring/cloud/config/aws/kms/it/TestApplication.java
deleted file mode 100644
index 62a4ff7..0000000
--- a/tests/test-support/src/main/java/de/zalando/spring/cloud/config/aws/kms/it/TestApplication.java
+++ /dev/null
@@ -1,7 +0,0 @@
-package de.zalando.spring.cloud.config.aws.kms.it;
-
-import org.springframework.boot.autoconfigure.SpringBootApplication;
-
-@SpringBootApplication
-public class TestApplication {
-}
diff --git a/tests/test-support/src/main/resources/META-INF/spring.factories b/tests/test-support/src/main/resources/META-INF/spring.factories
deleted file mode 100644
index 1de5939..0000000
--- a/tests/test-support/src/main/resources/META-INF/spring.factories
+++ /dev/null
@@ -1 +0,0 @@
-org.springframework.cloud.bootstrap.BootstrapConfiguration=de.zalando.spring.cloud.config.aws.kms.MockAwsKmsConfig
diff --git a/zalando-cloud-aws-autoconfigure/pom.xml b/zalando-cloud-aws-autoconfigure/pom.xml
new file mode 100644
index 0000000..596c139
--- /dev/null
+++ b/zalando-cloud-aws-autoconfigure/pom.xml
@@ -0,0 +1,57 @@
+
+
+
+ 4.0.0
+
+
+ com.zalando.awspring.cloud
+ zalando-cloud-aws
+ 3.1.1-SNAPSHOT
+
+
+ com.zalando.awspring.cloud
+ zalando-cloud-aws-autoconfigure
+ 3.1.1-SNAPSHOT
+ jar
+ Zalando Cloud AWS Autoconfigure
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+ io.awspring.cloud
+ spring-cloud-aws-autoconfigure
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-actuator-autoconfigure
+ true
+
+
+ org.springframework.boot
+ spring-boot-configuration-processor
+ true
+
+
+
+ org.springframework.cloud
+ spring-cloud-context
+ true
+
+
+
+ com.zalando.awspring.cloud
+ zalando-cloud-aws-kms
+ ${project.version}
+ true
+
+
+
+
diff --git a/zalando-cloud-aws-autoconfigure/src/main/java/com/zalando/awsspring/cloud/autoconfigure/kms/KmsAutoConfiguration.java b/zalando-cloud-aws-autoconfigure/src/main/java/com/zalando/awsspring/cloud/autoconfigure/kms/KmsAutoConfiguration.java
new file mode 100644
index 0000000..59d9e3f
--- /dev/null
+++ b/zalando-cloud-aws-autoconfigure/src/main/java/com/zalando/awsspring/cloud/autoconfigure/kms/KmsAutoConfiguration.java
@@ -0,0 +1,40 @@
+package com.zalando.awsspring.cloud.autoconfigure.kms;
+
+import org.springframework.beans.factory.ObjectProvider;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import io.awspring.cloud.autoconfigure.core.AwsClientBuilderConfigurer;
+import io.awspring.cloud.autoconfigure.core.AwsClientCustomizer;
+import software.amazon.awssdk.services.kms.KmsAsyncClient;
+import software.amazon.awssdk.services.kms.KmsAsyncClientBuilder;
+import software.amazon.awssdk.services.kms.KmsClient;
+import software.amazon.awssdk.services.kms.KmsClientBuilder;
+
+@Configuration
+@ConditionalOnClass({ KmsClient.class, KmsAsyncClient.class })
+@EnableConfigurationProperties({ KmsProperties.class })
+@ConditionalOnProperty(name = "spring.cloud.aws.kms.enabled", havingValue = "true", matchIfMissing = true)
+public class KmsAutoConfiguration {
+
+ public KmsAutoConfiguration() {
+
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ public KmsClient kmsClient(AwsClientBuilderConfigurer awsClientBuilderConfigurer, ObjectProvider> configurer, KmsProperties properties) {
+ return awsClientBuilderConfigurer.configure(KmsClient.builder(), properties, configurer.getIfAvailable()).build();
+ }
+
+ @ConditionalOnMissingBean
+ @Bean
+ public KmsAsyncClient kmsAsyncClient(AwsClientBuilderConfigurer awsClientBuilderConfigurer, ObjectProvider> configurer,
+ KmsProperties properties) {
+ return awsClientBuilderConfigurer.configure(KmsAsyncClient.builder(), properties, configurer.getIfAvailable()).build();
+ }
+}
diff --git a/zalando-cloud-aws-autoconfigure/src/main/java/com/zalando/awsspring/cloud/autoconfigure/kms/KmsProperties.java b/zalando-cloud-aws-autoconfigure/src/main/java/com/zalando/awsspring/cloud/autoconfigure/kms/KmsProperties.java
new file mode 100644
index 0000000..abe3a63
--- /dev/null
+++ b/zalando-cloud-aws-autoconfigure/src/main/java/com/zalando/awsspring/cloud/autoconfigure/kms/KmsProperties.java
@@ -0,0 +1,12 @@
+package com.zalando.awsspring.cloud.autoconfigure.kms;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+import io.awspring.cloud.autoconfigure.AwsClientProperties;
+
+@ConfigurationProperties(prefix = KmsProperties.PREFIX)
+public class KmsProperties extends AwsClientProperties {
+
+ public static final String PREFIX = "spring.cloud.aws.kms";
+
+}
diff --git a/zalando-cloud-aws-autoconfigure/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/KmsEncryptConfiguration.java b/zalando-cloud-aws-autoconfigure/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/KmsEncryptConfiguration.java
new file mode 100644
index 0000000..c684601
--- /dev/null
+++ b/zalando-cloud-aws-autoconfigure/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/KmsEncryptConfiguration.java
@@ -0,0 +1,23 @@
+package com.zalando.awsspring.cloud.bootstrap.encrypt;
+
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.boot.context.properties.EnableConfigurationProperties;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.crypto.encrypt.TextEncryptor;
+
+import software.amazon.awssdk.services.kms.KmsClient;
+
+@Configuration
+@EnableConfigurationProperties({ KmsProperties.class })
+@ConditionalOnProperty(prefix = "encrypt.kms", name = "enabled", havingValue = "true", matchIfMissing = true)
+public class KmsEncryptConfiguration {
+
+ @ConditionalOnMissingBean
+ @Bean
+ public TextEncryptor textEncryptor(KmsClient kmsClient, KmsProperties properties) {
+ return new KmsTextEncryptor(kmsClient, properties.getKeyId(), properties.getEncryptionAlgorithm());
+ }
+
+}
diff --git a/zalando-cloud-aws-autoconfigure/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/KmsProperties.java b/zalando-cloud-aws-autoconfigure/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/KmsProperties.java
new file mode 100644
index 0000000..8194f9a
--- /dev/null
+++ b/zalando-cloud-aws-autoconfigure/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/KmsProperties.java
@@ -0,0 +1,29 @@
+package com.zalando.awsspring.cloud.bootstrap.encrypt;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(KmsProperties.PREFIX)
+public class KmsProperties {
+
+ public static final String PREFIX = "encrypt.kms";
+
+ private String keyId;
+
+ private String encryptionAlgorithm;
+
+ public String getKeyId() {
+ return keyId;
+ }
+
+ public void setKeyId(String value) {
+ this.keyId = value;
+ }
+
+ public String getEncryptionAlgorithm() {
+ return encryptionAlgorithm;
+ }
+
+ public void setEncryptionAlgorithm(String value) {
+ this.encryptionAlgorithm = value;
+ }
+}
diff --git a/zalando-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories b/zalando-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..e28d9a4
--- /dev/null
+++ b/zalando-cloud-aws-autoconfigure/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,7 @@
+# Spring Cloud Bootstrap components
+org.springframework.cloud.bootstrap.BootstrapConfiguration=\
+io.awspring.cloud.autoconfigure.core.AwsAutoConfiguration,\
+io.awspring.cloud.autoconfigure.core.CredentialsProviderAutoConfiguration,\
+io.awspring.cloud.autoconfigure.core.RegionProviderAutoConfiguration,\
+com.zalando.awsspring.cloud.autoconfigure.kms.KmsAutoConfiguration,\
+com.zalando.awsspring.cloud.bootstrap.encrypt.KmsEncryptConfiguration
diff --git a/zalando-cloud-aws-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/zalando-cloud-aws-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 0000000..8750c47
--- /dev/null
+++ b/zalando-cloud-aws-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+com.zalando.awsspring.cloud.autoconfigure.kms.KmsAutoConfiguration
diff --git a/zalando-cloud-aws-kms/README.md b/zalando-cloud-aws-kms/README.md
new file mode 100644
index 0000000..62e9e22
--- /dev/null
+++ b/zalando-cloud-aws-kms/README.md
@@ -0,0 +1,196 @@
+# Zalando Cloud AWS KMS
+
+This is a Spring Cloud AWS add-on that provides a KMS client and encryption via AWS (Amazon Web Services) KMS (Key management service).
+
+## Features
+
+* Compatible with Spring Cloud and Spring Cloud AWS
+* Spring Boot 3 - ready
+* Supports AWS KMS [encryption context](#use-an-encryption-context)
+* Supports different [output modes](#available-options) for decrypted values
+* Supports [asymmetric keys](#asymmetric-keys)
+* Minimal dependencies
+
+## Installation
+
+### Prerequisites
+
+Given you have a [Spring Boot](http://projects.spring.io/spring-boot/) application.
+
+### Step 1
+
+Add our starter as dependency to your Maven pom.xml or Gradle build file.
+
+```
+
+ org.zalando
+ zalando-cloud-awsspring-kms-starter
+ ${zalando-cloud-aws.version}
+
+```
+
+### Step 2 (optional)
+
+Apply configuration to the application's [Bootstrap Context](https://docs.spring.io/spring-cloud-commons/reference/spring-cloud-commons/application-context-services.html#the-bootstrap-application-context)
+
+E.g. `bootstrap.yml`:
+
+```
+spring.cloud:
+ decrypt-environment-post-processor.enabled: false # disable environment post processor for rsa keys
+
+spring.cloud.aws:
+ region:
+ static: eu-central-1 # optional
+ kms:
+ endpoint: http://localhost:4566 # only needed for endpoint override
+
+encrypt.kms:
+
+ # Optional: Turn off the KMS feature completely (e.g. for local development)
+ enabled: false
+
+ # Optional for decrypting values with SYMMETRIC_DEFAULT algorithm.
+ # Required for encrypting values.
+ # Required for decrypting values with some asymmetric algorithm.
+ keyId: 9d9fca31-54c5-4df5-ba4f-127dfb9a5031
+
+ # Optional: Switch to asymmetric algorithm.
+ # See com.amazonaws.services.kms.model.EncryptionAlgorithmSpec for available values.
+ encryptionAlgorithm: "RSAES_OAEP_SHA_256"
+```
+
+The `aws.kms.keyId` property must be set if
+ - values need to be decrypted with an asymmetric key
+ - values need to be encrypted (with any algorithm)
+
+Those are the properties used by this library:
+
+- `encrypt.kms.enabled`: (defaults to true)
+- `encrypt.kms.keyId`: either the keyId or the full ARN of the KMS key
+- `encrypt.kms.encryptionAlgorithm`: the encryption algorithm to use
+
+
+## Usage
+
+Now you can add encrypted values to you property files. An encrypted value must always start with `{cipher}`.
+Those properties are automatically decrypted on application startup.
+
+E.g. `application.yml`
+
+ secretPassword: '{cipher}CiA47hYvQqWFFGq3TLtzQO5FwZMam2AnaeQt4PGEZHhDLxFTAQEBAgB4OO4WL0KlhRRqt0y7c0DuRcGTGptgJ8nkLeDxhGR4Qy8AAABqMGgGCSqGSIb3DQEHBqBbMFkCAQAwVAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAx61LJpXQwgTcnGeSQCARCAJ4xhpGC5HT2xT+Vhy2iAuT+P/PLliZK5u6CiGhgudteZsCr7VJ/1aw=='
+
+### Use an encryption context
+
+An [encryption context](http://docs.aws.amazon.com/kms/latest/developerguide/encryption-context.html)
+is a set of key-value pairs used for encrypt and decrypt values, which might be useful as security
+enhancement.
+
+To use an encryption context with this library, you will have to use a custom syntax, that is not part
+of Spring Security (as the {cipher} prefix).
+
+E.g. `application.yml`
+
+ secretPassword: '{cipher}(Country=UG9ydHVnYWw=,Code=MzUx)CiA47hYvQqWFFGq3TLtzQO5FwZMam2AnaeQt4PGEZHhDLxFTAQEBAgB4OO4WL0KlhRRqt0y7c0DuRcGTGptgJ8nkLeDxhGR4Qy8AAABqMGgGCSqGSIb3DQEHBqBbMFkCAQAwVAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAx61LJpXQwgTcnGeSQCARCAJ4xhpGC5HT2xT+Vhy2iAuT+P/PLliZK5u6CiGhgudteZsCr7VJ/1aw=='
+
+The `(Country=UG9ydHVnYWw=,Code=MzUx)` part is the encryption context, where we used two keys for
+this example: Country and Code. And the values are Base64 encoded.
+
+Key-value pairs must be comma separated, and it is fine to use spaces to separate values. The order of the
+values in the context is not important. And one last note, is that the values used in the encryption
+context are logged in CloudTrail, so they must not be sensitive.
+
+### Asymmetric keys
+
+AWS KMS supports [symmetric and asymmetric keys](https://docs.aws.amazon.com/kms/latest/developerguide/symmetric-asymmetric.html) to encrypt/decrypt data. By default, this library assumes a symmetric key. There are configuration options available to enable asymmetric keys.
+
+#### Encryption
+
+Add `keyId` and `encryptionAlgorithm` to the `bootstrap.yaml`:
+
+```
+encrypt.kms:
+ keyId: "9d9fca31-54c5-4df5-ba4f-127dfb9a5031"
+ encryptionAlgorithm: "RSAES_OAEP_SHA_256" # or "RSAES_OAEP_SHA_1"
+```
+
+
+#### Decryption
+
+If all cipher values of your application have been encrypted with the
+same KMS key and algorithm, you can configure the `keyId` and `encryptionAlgorithm`
+globally in the `bootstrap.yaml` as shown above. In case you have to decrypt
+ciphers from different keys or different algorithms, you can specify those
+separately for each key using the ["extra options"](#use-extra-options) approach:
+
+e.g. `application.yml`
+
+ secret1: "{cipher}SSdtIHNvbWUgYXN5bW1ldHJpY2FsbHkgZW5jcnlwdGVkIHNlY3JldA=="
+ secret2: "{cipher}[algorithm=SYMMETRIC_DEFAULT]U3ltbWV0cmljIGFuZCBhc3ltbWV0cmljIHNlY3JldHMgY2FuIGJlIG1peGVk"
+ secret3: "{cipher}[algorithm=RSAES_OAEP_SHA_256,keyId=9d9fca31-54c5-4df5-ba4f-127dfb9a5031]SSBoYXZlIGEgY3VzdG9tIGtleSBhbmQgYWxnb3JpdGht"
+
+### Use extra options
+
+While decrypting config values, extra arguments can be supplied to control the output behavior.
+Extra args do also require a custom syntax, that is not part of Spring Security (as the {cipher} prefix).
+
+E.g. `application.yml`
+
+ secretKey: '{cipher}[output=base64]CiA47hYvQqWFFGq3TLtzQO5FwZMam2AnaeQt4PGEZHhDLxFTAQEBAgB4OO4WL0KlhRRqt0y7c0DuRcGTGptgJ8nkLeDxhGR4Qy8AAABqMGgGCSqGSIb3DQEHBqBbMFkCAQAwVAYJKoZIhvcNAQcBMB4GCWCGSAFlAwQBLjARBAx61LJpXQwgTcnGeSQCARCAJ4xhpGC5HT2xT+Vhy2iAuT+P/PLliZK5u6CiGhgudteZsCr7VJ/1aw=='
+
+The `[output=base64]` part defines the extra options.
+
+Encryption context and extra options can be combined in any order.
+`"{cipher}[output=base64](Code=MzUx)..."` is equivalent to `"{cipher}(Code=MzUx)[output=base64]..."`.
+
+#### Available Options
+
+| Option | Values | Default | Description |
+| ------ | ------ | ------- | ----------- |
+| output | `plain`, `base64` | `plain` | `plain` returns the decrypted secret as simple String. `base64` returns the decrypted secret in Base64 encoding. This is useful in cases where the plaintext secret contains non-printable characters (e.g. random AES keys) |
+| algorithm | as defined in `com.amazonaws.services.kms.model.EncryptionAlgorithmSpec` | `null` | Use the algorithm to decrypt the cipher text. |
+| keyId | ID or full ARN of a KMS key | `null` | Use the given key to decrypt the cipher text |
+
+
+## Development
+
+### Run Test Suite
+
+```
+mvn clean test
+```
+
+### Coverage Report
+
+```
+open coverage/target/site/jacoco/index.html
+```
+
+## Releases
+
+### Release to Maven Central
+
+```
+mvn clean release:prepare -Dresume=false
+mvn release:perform
+```
+
+## Contributing
+
+Contributions are highly welcome. For details please refer to the [guidelines](https://github.com/zalando/spring-cloud-config-aws-kms/tree/master/CONTRIBUTING.md).
+
+## License
+
+Copyright (C) 2024 Zalando SE (https://tech.zalando.com)
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
diff --git a/zalando-cloud-aws-kms/pom.xml b/zalando-cloud-aws-kms/pom.xml
new file mode 100644
index 0000000..15c7a67
--- /dev/null
+++ b/zalando-cloud-aws-kms/pom.xml
@@ -0,0 +1,60 @@
+
+
+
+ 4.0.0
+
+
+ com.zalando.awspring.cloud
+ zalando-cloud-aws
+ 3.1.1-SNAPSHOT
+
+
+ com.zalando.awspring.cloud
+ zalando-cloud-aws-kms
+ 3.1.1-SNAPSHOT
+ jar
+ Zalando Cloud AWS KMS Integration
+
+
+
+ software.amazon.awssdk
+ kms
+
+
+
+ org.springframework.cloud
+ spring-cloud-context
+ true
+
+
+ org.springframework.boot
+ spring-boot
+ true
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.testcontainers
+ testcontainers
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+ org.testcontainers
+ localstack
+ test
+
+
+
+
+
diff --git a/zalando-cloud-aws-kms/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/EncryptedToken.java b/zalando-cloud-aws-kms/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/EncryptedToken.java
new file mode 100644
index 0000000..2f33fa7
--- /dev/null
+++ b/zalando-cloud-aws-kms/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/EncryptedToken.java
@@ -0,0 +1,105 @@
+package com.zalando.awsspring.cloud.bootstrap.encrypt;
+
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.springframework.util.Assert;
+
+public class EncryptedToken {
+
+ private static final Pattern ENCRYPTED_TOKEN_PATTERN = Pattern.compile("^(?>\\((?.*)\\)|\\[(?.*)]){0,2}(?.*)$");
+
+ private byte[] cipher;
+
+ private Map context;
+
+ private EncryptedTokenOptions options;
+
+ private EncryptedToken(byte[] cipher, Map context, EncryptedTokenOptions options) {
+ this.cipher = cipher;
+ this.context = context;
+ this.options = options;
+ }
+
+ public byte[] getCipher() {
+ return cipher;
+ }
+
+ public Map getContext() {
+ return context;
+ }
+
+ public EncryptedTokenOptions getOptions() {
+ return options;
+ }
+
+ public static EncryptedToken parse(String text) {
+ Matcher matcher = ENCRYPTED_TOKEN_PATTERN.matcher(text);
+ boolean matches = matcher.matches();
+ Assert.isTrue(matches, "Malformed encrypted string '" + text + "'");
+
+ String contextString = matcher.group("context");
+ String optionsString = matcher.group("options");
+ String cipherString = matcher.group("cipher");
+
+ byte[] cipher = parseCipher(cipherString);
+ Map context = parseContext(contextString);
+ EncryptedTokenOptions options = parseOptions(optionsString);
+
+ return new EncryptedToken(cipher, context, options);
+ }
+
+ private static byte[] parseCipher(String cipherString) {
+ return Base64.getDecoder().decode(cipherString);
+ }
+
+ private static Map parseContext(String contextString) {
+ return parseMap(contextString, value -> new String(Base64.getDecoder().decode(value)));
+ }
+
+ private static EncryptedTokenOptions parseOptions(String optionsString) {
+ Map options = parseMap(optionsString, Function.identity());
+ if (options == null) {
+ return null;
+ }
+
+ String keyId = options.get("keyId");
+ String encryptionAlgorithm = options.get("encryptionAlgorithm");
+ String modeText = options.get("output");
+
+ if (modeText != null) {
+ return new EncryptedTokenOptions(keyId, encryptionAlgorithm, OutputMode.valueOf(modeText.toUpperCase()));
+ } else {
+ return new EncryptedTokenOptions(keyId, encryptionAlgorithm);
+ }
+ }
+
+ private static Map parseMap(String text, Function transformer) {
+ if (text == null) {
+ return null;
+ }
+
+ Map result = new HashMap<>();
+ String delimiter = ",";
+ int pos = 0;
+ int delPos;
+ while ((delPos = text.indexOf(delimiter, pos)) != -1 ) {
+ String kvString = text.substring(pos, delPos).strip();
+ String[] kv = kvString.split("=", 2);
+
+ result.put(kv[0], kv.length == 1 ? "" : transformer.apply(kv[1]));
+
+ pos = delPos + delimiter.length();
+ }
+ if (text.length() > 0 && pos <= text.length()) {
+ String kvString = text.substring(pos).strip();
+ String[] kv = kvString.split("=", 2);
+ result.put(kv[0].strip(), kv.length == 1 ? "" : transformer.apply(kv[1]));
+ }
+ return result;
+ }
+}
diff --git a/zalando-cloud-aws-kms/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/EncryptedTokenOptions.java b/zalando-cloud-aws-kms/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/EncryptedTokenOptions.java
new file mode 100644
index 0000000..132f5f2
--- /dev/null
+++ b/zalando-cloud-aws-kms/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/EncryptedTokenOptions.java
@@ -0,0 +1,55 @@
+package com.zalando.awsspring.cloud.bootstrap.encrypt;
+
+import java.util.Objects;
+
+public class EncryptedTokenOptions {
+
+ private String keyId;
+
+ private String encryptionAlgorithm;
+
+ private OutputMode output;
+
+ public EncryptedTokenOptions(String keyId, String encryptionAlgorithm) {
+ this(keyId, encryptionAlgorithm, OutputMode.PLAIN);
+ }
+
+ public EncryptedTokenOptions(String keyId, String encryptionAlgorithm, OutputMode output) {
+ this.keyId = keyId;
+ this.encryptionAlgorithm = encryptionAlgorithm;
+ this.output = output == null ? OutputMode.PLAIN : output;
+ }
+
+ public String getKeyId() {
+ return keyId;
+ }
+
+ public String getEncryptionAlgorithm() {
+ return encryptionAlgorithm;
+ }
+
+ public OutputMode getOutput() {
+ return output;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(keyId);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == this) {
+ return true;
+ }
+
+ if (obj == null || getClass() != obj.getClass()) {
+ return false;
+ }
+
+ EncryptedTokenOptions other = (EncryptedTokenOptions) obj;
+ return Objects.equals(keyId, other.keyId) && Objects.equals(encryptionAlgorithm, other.encryptionAlgorithm)
+ && Objects.equals(output, other.output);
+ }
+
+}
diff --git a/zalando-cloud-aws-kms/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/KmsTextEncryptor.java b/zalando-cloud-aws-kms/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/KmsTextEncryptor.java
new file mode 100644
index 0000000..a77b65b
--- /dev/null
+++ b/zalando-cloud-aws-kms/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/KmsTextEncryptor.java
@@ -0,0 +1,94 @@
+package com.zalando.awsspring.cloud.bootstrap.encrypt;
+
+import java.util.Base64;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.security.crypto.encrypt.TextEncryptor;
+
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.services.kms.KmsClient;
+import software.amazon.awssdk.services.kms.model.DecryptRequest;
+import software.amazon.awssdk.services.kms.model.DecryptResponse;
+import software.amazon.awssdk.services.kms.model.EncryptRequest;
+import software.amazon.awssdk.services.kms.model.EncryptResponse;
+
+/**
+ * Implementation of TextEncryptor that uses AWS KMS.
+ */
+public class KmsTextEncryptor implements TextEncryptor {
+
+ private static Logger LOG = LoggerFactory.getLogger(KmsTextEncryptor.class);
+
+ private final KmsClient kmsClient;
+
+ private final String kmsKeyId;
+
+ private final String kmsEncryptionAlgorithm;
+
+ public KmsTextEncryptor(KmsClient kmsClient, String kmsKeyId, String kmsEncryptionAlgorithm) {
+ this.kmsClient = kmsClient;
+ this.kmsKeyId = kmsKeyId;
+ this.kmsEncryptionAlgorithm = kmsEncryptionAlgorithm;
+ }
+
+ private String convertToString(byte[] cipherBytes, OutputMode output) {
+ if (OutputMode.BASE64 == output) {
+ return Base64.getEncoder().encodeToString(cipherBytes);
+ } else {
+ return new String(cipherBytes);
+ }
+ }
+
+
+ @Override
+ public String encrypt(String text) {
+ EncryptRequest request = buildEncryptRequest(text);
+ EncryptResponse response = kmsClient.encrypt(request);
+
+ byte[] cipherBytes = response.ciphertextBlob().asByteArray();
+
+ return convertToString(cipherBytes, OutputMode.BASE64);
+ }
+
+ private EncryptRequest buildEncryptRequest(String text) {
+ EncryptRequest.Builder requestBuilder = EncryptRequest.builder().keyId(kmsKeyId)
+ .plaintext(SdkBytes.fromUtf8String(text));
+
+ if (kmsEncryptionAlgorithm != null) {
+ requestBuilder = requestBuilder.encryptionAlgorithm(kmsEncryptionAlgorithm);
+ }
+
+ return requestBuilder.build();
+ }
+
+
+ @Override
+ public String decrypt(String encryptedText) {
+
+ EncryptedToken encryptedToken = EncryptedToken.parse(encryptedText);
+ LOG.info("decrypting {} as part of stack.\n{}", encryptedText, Thread.currentThread().getStackTrace());
+
+ DecryptRequest request = buildDecryptRequest(encryptedToken.getCipher());
+
+ DecryptResponse response = kmsClient.decrypt(request);
+ byte[] textBytes = response.plaintext().asByteArray();
+
+ return convertToString(textBytes, OutputMode.PLAIN);
+ }
+
+ private DecryptRequest buildDecryptRequest(byte[] encryptedText) {
+ DecryptRequest.Builder requestBuilder = DecryptRequest.builder();
+
+ requestBuilder = requestBuilder.ciphertextBlob(SdkBytes.fromByteArray(encryptedText));
+ if (kmsKeyId != null) {
+ requestBuilder = requestBuilder.keyId(kmsKeyId);
+ }
+ if (kmsEncryptionAlgorithm != null) {
+ requestBuilder = requestBuilder.encryptionAlgorithm(kmsEncryptionAlgorithm);
+ }
+
+ return requestBuilder.build();
+
+ }
+}
diff --git a/zalando-cloud-aws-kms/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/OutputMode.java b/zalando-cloud-aws-kms/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/OutputMode.java
new file mode 100644
index 0000000..664b113
--- /dev/null
+++ b/zalando-cloud-aws-kms/src/main/java/com/zalando/awsspring/cloud/bootstrap/encrypt/OutputMode.java
@@ -0,0 +1,6 @@
+package com.zalando.awsspring.cloud.bootstrap.encrypt;
+
+public enum OutputMode {
+
+ PLAIN, BASE64;
+}
diff --git a/zalando-cloud-aws-kms/src/test/java/com/zalando/awsspring/cloud/bootstrap/encrypt/EncryptedTokenTest.java b/zalando-cloud-aws-kms/src/test/java/com/zalando/awsspring/cloud/bootstrap/encrypt/EncryptedTokenTest.java
new file mode 100644
index 0000000..665f8bd
--- /dev/null
+++ b/zalando-cloud-aws-kms/src/test/java/com/zalando/awsspring/cloud/bootstrap/encrypt/EncryptedTokenTest.java
@@ -0,0 +1,50 @@
+package com.zalando.awsspring.cloud.bootstrap.encrypt;
+
+import java.util.Base64;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Stream;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+public class EncryptedTokenTest {
+
+ private static String CIPHER_BASE64 = Base64.getEncoder().encodeToString("Hello World".getBytes());
+
+ private static Map CONTEXT_MAP;
+
+ static {
+ CONTEXT_MAP = new HashMap<>();
+ CONTEXT_MAP.put("param", "L’homme c’est rien");
+ CONTEXT_MAP.put("test", "l’oeuvre c’est tout");
+ CONTEXT_MAP.put("valueless", "");
+ }
+
+ public static Stream data() {
+ return Stream.of(
+ Arguments.of(CIPHER_BASE64, null, null),
+ Arguments.of("[]" + CIPHER_BASE64, null, new EncryptedTokenOptions(null, null)),
+ Arguments.of("()" + CIPHER_BASE64, Collections.emptyMap(), null),
+ Arguments.of("[]()" + CIPHER_BASE64, Collections.emptyMap(), new EncryptedTokenOptions(null, null)),
+ Arguments.of("()[]" + CIPHER_BASE64, Collections.emptyMap(), new EncryptedTokenOptions(null, null)),
+ Arguments.of("[keyId=sample-key,encryptionAlgorithm=SYMMETRIC_DEFAULT,output=base64,foo=bar]" + CIPHER_BASE64, null, new EncryptedTokenOptions("sample-key", "SYMMETRIC_DEFAULT", OutputMode.BASE64)),
+ Arguments.of("(param=TOKAmWhvbW1lIGPigJllc3Qgcmllbg==,test=bOKAmW9ldXZyZSBj4oCZZXN0IHRvdXQ= ,valueless)" + CIPHER_BASE64, CONTEXT_MAP, null),
+ Arguments.of("(param=TOKAmWhvbW1lIGPigJllc3Qgcmllbg==,test=bOKAmW9ldXZyZSBj4oCZZXN0IHRvdXQ= ,valueless)[output=base64]" + CIPHER_BASE64, CONTEXT_MAP, new EncryptedTokenOptions(null, null, OutputMode.BASE64)),
+ Arguments.of("[output=base64](param=TOKAmWhvbW1lIGPigJllc3Qgcmllbg==,test=bOKAmW9ldXZyZSBj4oCZZXN0IHRvdXQ= ,valueless)" + CIPHER_BASE64, CONTEXT_MAP, new EncryptedTokenOptions(null, null, OutputMode.BASE64))
+ );
+ }
+
+ @ParameterizedTest
+ @MethodSource("data")
+ public void testParseToken(String tokenString, Map expectedContext, EncryptedTokenOptions expectedOptions) {
+ EncryptedToken token = EncryptedToken.parse(tokenString);
+
+ Assertions.assertThat(token.getCipher()).isEqualTo("Hello World".getBytes());
+ Assertions.assertThat(token.getContext()).isEqualTo(expectedContext);
+ Assertions.assertThat(token.getOptions()).isEqualTo(expectedOptions);
+ }
+}
diff --git a/zalando-cloud-aws-kms/src/test/java/com/zalando/awsspring/cloud/bootstrap/encrypt/KmsTextEncryptorTest.java b/zalando-cloud-aws-kms/src/test/java/com/zalando/awsspring/cloud/bootstrap/encrypt/KmsTextEncryptorTest.java
new file mode 100644
index 0000000..8a68f97
--- /dev/null
+++ b/zalando-cloud-aws-kms/src/test/java/com/zalando/awsspring/cloud/bootstrap/encrypt/KmsTextEncryptorTest.java
@@ -0,0 +1,99 @@
+package com.zalando.awsspring.cloud.bootstrap.encrypt;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+
+import org.assertj.core.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.testcontainers.containers.localstack.LocalStackContainer;
+import org.testcontainers.containers.localstack.LocalStackContainer.Service;
+import org.testcontainers.junit.jupiter.Container;
+import org.testcontainers.junit.jupiter.Testcontainers;
+import org.testcontainers.utility.DockerImageName;
+
+import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
+import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
+import software.amazon.awssdk.core.SdkBytes;
+import software.amazon.awssdk.regions.Region;
+import software.amazon.awssdk.services.kms.KmsClient;
+import software.amazon.awssdk.services.kms.model.CreateKeyRequest;
+import software.amazon.awssdk.services.kms.model.CreateKeyResponse;
+import software.amazon.awssdk.services.kms.model.EncryptRequest;
+import software.amazon.awssdk.services.kms.model.EncryptResponse;
+import software.amazon.awssdk.services.kms.model.KeySpec;
+import software.amazon.awssdk.services.kms.model.KeyUsageType;
+
+@Testcontainers
+public class KmsTextEncryptorTest {
+
+ @Container
+ private static final LocalStackContainer localstack = new LocalStackContainer(
+ DockerImageName.parse("localstack/localstack:latest")).withServices(Service.KMS);
+
+ private static KmsClient kmsClient;
+
+ private static String symmetricKeyId;
+
+ private static String rsaKeyId;
+
+ @BeforeAll
+ public static void beforeAll() {
+ kmsClient = KmsClient.builder().endpointOverride(localstack.getEndpoint())
+ .credentialsProvider(StaticCredentialsProvider
+ .create(AwsBasicCredentials.create(localstack.getAccessKey(), localstack.getSecretKey())))
+ .region(Region.of(localstack.getRegion())).build();
+
+ CreateKeyResponse response = kmsClient.createKey(CreateKeyRequest.builder().keySpec(KeySpec.SYMMETRIC_DEFAULT)
+ .keyUsage(KeyUsageType.ENCRYPT_DECRYPT).build());
+ symmetricKeyId = response.keyMetadata().keyId();
+
+ response = kmsClient.createKey(
+ CreateKeyRequest.builder().keySpec(KeySpec.RSA_4096).keyUsage(KeyUsageType.ENCRYPT_DECRYPT).build());
+ rsaKeyId = response.keyMetadata().keyId();
+ }
+
+ @Test
+ public void encryptSymmetric() throws Exception {
+ KmsTextEncryptor encryptor = new KmsTextEncryptor(kmsClient, symmetricKeyId, null);
+ String encrypted = encryptor.encrypt("secret");
+
+ Assertions.assertThat(encrypted).isNotBlank().isBase64();
+ }
+
+ @Test
+ public void encryptRsa() throws Exception {
+ KmsTextEncryptor encryptor = new KmsTextEncryptor(kmsClient, rsaKeyId, null);
+ String encrypted = encryptor.encrypt("secret");
+
+ Assertions.assertThat(encrypted).isNotBlank().isBase64();
+ }
+
+ @Test
+ public void decryptSymmetric() throws Exception {
+ String password = "secret";
+
+ EncryptResponse response = kmsClient.encrypt(EncryptRequest.builder().keyId(symmetricKeyId)
+ .plaintext(SdkBytes.fromString(password, StandardCharsets.ISO_8859_1)).build());
+ String encrypted = Base64.getEncoder().encodeToString(response.ciphertextBlob().asByteArray());
+
+ KmsTextEncryptor encryptor = new KmsTextEncryptor(kmsClient, symmetricKeyId, null);
+ String plaintext = encryptor.decrypt(encrypted);
+
+ Assertions.assertThat(plaintext).isEqualTo(password);
+ }
+
+ @Test
+ public void decryptRsa() throws Exception {
+ String password = "secret";
+
+ EncryptResponse response = kmsClient.encrypt(EncryptRequest.builder().keyId(rsaKeyId)
+ .plaintext(SdkBytes.fromString(password, StandardCharsets.ISO_8859_1)).build());
+ String encrypted = Base64.getEncoder().encodeToString(response.ciphertextBlob().asByteArray());
+
+ KmsTextEncryptor encryptor = new KmsTextEncryptor(kmsClient, rsaKeyId, null);
+ String plaintext = encryptor.decrypt(encrypted);
+
+ Assertions.assertThat(plaintext).isEqualTo(password);
+ }
+}
diff --git a/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/README.md b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/README.md
new file mode 100644
index 0000000..e791fd8
--- /dev/null
+++ b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/README.md
@@ -0,0 +1,57 @@
+# Sample
+
+Startup localstack with KMS
+
+```
+docker run -d --name localstack -p 4566:4566 -p 4510-4559:4510-4559 localstack/localstack
+```
+
+Create customer-managed key and note down key-id
+
+```
+aws --endpoint-url http://localhost:4566 kms create-key
+```
+
+Encode secret using base64
+
+```
+echo "secret" | base64
+```
+
+Encrypt secret using AWS CLI
+
+```
+aws --endpoint-url http://localhost:4566 kms encrypt --key-id --plaintext
+```
+
+2024-03-14T10:09:23.638+01:00[0;39m [32m INFO[0;39m [35m1174881[0;39m [2m---[0;39m [2m[ main][0;39m [2m[0;39m[36mc.z.a.c.b.encrypt.KmsTextEncryptor [0;39m [2m:[0;39m decrypting NGJlZDYyNzEtNjRhOS00OTRhLWJhMGItZjk2MmIyMmIyYWM1rHhX2gJ7okYuE5VvxT0mN0ZkatF+b3AdmNGYdj21/hfd5oFm3DBaTvHHPJppbXTX as part of stack.
+[java.base/java.lang.Thread.getStackTrace(Thread.java:1610),
+ com.zalando.awsspring.cloud.bootstrap.encrypt.KmsTextEncryptor.decrypt(KmsTextEncryptor.java:70),
+ org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.decrypt(AbstractEnvironmentDecrypt.java:143),
+ org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.lambda$decrypt$0(AbstractEnvironmentDecrypt.java:136),
+ java.base/java.util.LinkedHashMap.replaceAll(LinkedHashMap.java:731),
+ org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.decrypt(AbstractEnvironmentDecrypt.java:131),
+ org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.decrypt(AbstractEnvironmentDecrypt.java:70),
+ org.springframework.cloud.bootstrap.encrypt.EnvironmentDecryptApplicationInitializer.initialize(EnvironmentDecryptApplicationInitializer.java:95),
+ org.springframework.cloud.bootstrap.BootstrapApplicationListener$DelegatingEnvironmentDecryptApplicationInitializer.initialize(BootstrapApplicationListener.java:414),
+ org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:627),
+ org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:400),
+ org.springframework.boot.SpringApplication.run(SpringApplication.java:333),
+ org.springframework.boot.SpringApplication.run(SpringApplication.java:1354),
+ org.springframework.boot.SpringApplication.run(SpringApplication.java:1343),
+ com.zalando.awsspring.samples.bootstrap.BootstrapApplication.main(BootstrapApplication.java:10)]
+[2m2024-03-14T10:09:23.801+01:00[0;39m [32m INFO[0;39m [35m1174881[0;39m [2m---[0;39m [2m[ main][0;39m [2m[0;39m[36mc.z.a.c.b.encrypt.KmsTextEncryptor [0;39m [2m:[0;39m decrypting NGJlZDYyNzEtNjRhOS00OTRhLWJhMGItZjk2MmIyMmIyYWM1rHhX2gJ7okYuE5VvxT0mN0ZkatF+b3AdmNGYdj21/hfd5oFm3DBaTvHHPJppbXTX as part of stack.
+[java.base/java.lang.Thread.getStackTrace(Thread.java:1610),
+ com.zalando.awsspring.cloud.bootstrap.encrypt.KmsTextEncryptor.decrypt(KmsTextEncryptor.java:70),
+ org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.decrypt(AbstractEnvironmentDecrypt.java:143),
+ org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.lambda$decrypt$0(AbstractEnvironmentDecrypt.java:136),
+ java.base/java.util.LinkedHashMap.replaceAll(LinkedHashMap.java:731),
+ org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.decrypt(AbstractEnvironmentDecrypt.java:131),
+ org.springframework.cloud.bootstrap.encrypt.AbstractEnvironmentDecrypt.decrypt(AbstractEnvironmentDecrypt.java:70),
+ org.springframework.cloud.bootstrap.encrypt.EnvironmentDecryptApplicationInitializer.initialize(EnvironmentDecryptApplicationInitializer.java:95),
+ org.springframework.boot.SpringApplication.applyInitializers(SpringApplication.java:627),
+ org.springframework.boot.SpringApplication.prepareContext(SpringApplication.java:400),
+ org.springframework.boot.SpringApplication.run(SpringApplication.java:333),
+ org.springframework.boot.SpringApplication.run(SpringApplication.java:1354),
+ org.springframework.boot.SpringApplication.run(SpringApplication.java:1343),
+ com.zalando.awsspring.samples.bootstrap.BootstrapApplication.main(BootstrapApplication.java:10)]
diff --git a/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/pom.xml b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/pom.xml
new file mode 100644
index 0000000..6b4a107
--- /dev/null
+++ b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/pom.xml
@@ -0,0 +1,114 @@
+
+
+
+ 4.0.0
+
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 3.2.3
+
+
+
+ com.zalando.awspring.cloud
+ zalando-cloud-aws-kms-sample
+ 3.1.1-SNAPSHOT
+ jar
+ Zalando Cloud AWS KMS Sample
+
+
+ 17
+ 2023.0.0
+ 3.1.0
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.cloud
+ spring-cloud-starter
+
+
+ org.springframework.cloud
+ spring-cloud-starter-bootstrap
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-starter
+ ${awsspring.version}
+
+
+
+ com.zalando.awspring.cloud
+ zalando-cloud-aws-kms
+ ${project.version}
+
+
+ com.zalando.awspring.cloud
+ zalando-cloud-aws-autoconfigure
+ ${project.version}
+
+
+
+ io.micrometer
+ micrometer-registry-prometheus
+ runtime
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springframework.boot
+ spring-boot-testcontainers
+ test
+
+
+ org.testcontainers
+ junit-jupiter
+ test
+
+
+ org.testcontainers
+ localstack
+
+
+
+
+
+
+ org.springframework.cloud
+ spring-cloud-dependencies
+ ${spring-cloud.version}
+ pom
+ import
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/main/java/com/zalando/awsspring/samples/bootstrap/BootstrapApplication.java b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/main/java/com/zalando/awsspring/samples/bootstrap/BootstrapApplication.java
new file mode 100644
index 0000000..3cc52a0
--- /dev/null
+++ b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/main/java/com/zalando/awsspring/samples/bootstrap/BootstrapApplication.java
@@ -0,0 +1,12 @@
+package com.zalando.awsspring.samples.bootstrap;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class BootstrapApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(BootstrapApplication.class, args);
+ }
+}
diff --git a/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/main/java/com/zalando/awsspring/samples/bootstrap/api/SampleController.java b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/main/java/com/zalando/awsspring/samples/bootstrap/api/SampleController.java
new file mode 100644
index 0000000..ad59418
--- /dev/null
+++ b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/main/java/com/zalando/awsspring/samples/bootstrap/api/SampleController.java
@@ -0,0 +1,15 @@
+package com.zalando.awsspring.samples.bootstrap.api;
+
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+public class SampleController {
+
+ @GetMapping("/greeting")
+ public Greeting greeting() {
+ return new Greeting("Hello");
+ }
+
+ public static record Greeting(String text) {};
+}
diff --git a/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/main/resources/application.yaml b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/main/resources/application.yaml
new file mode 100644
index 0000000..76e4082
--- /dev/null
+++ b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/main/resources/application.yaml
@@ -0,0 +1,5 @@
+debug: true
+
+sample:
+ password: '{cipher}NGJlZDYyNzEtNjRhOS00OTRhLWJhMGItZjk2MmIyMmIyYWM1rHhX2gJ7okYuE5VvxT0mN0ZkatF+b3AdmNGYdj21/hfd5oFm3DBaTvHHPJppbXTX'
+
\ No newline at end of file
diff --git a/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/main/resources/bootstrap.yaml b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/main/resources/bootstrap.yaml
new file mode 100644
index 0000000..414e9b3
--- /dev/null
+++ b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/main/resources/bootstrap.yaml
@@ -0,0 +1,8 @@
+spring.cloud:
+ decrypt-environment-post-processor.enabled: false
+
+spring.cloud.aws:
+ region:
+ static: eu-central-1
+ kms:
+ endpoint: http://localhost:4566
diff --git a/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/test/java/com/zalando/awsspring/samples/bootstrap/BootstrapApplicationTest.java b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/test/java/com/zalando/awsspring/samples/bootstrap/BootstrapApplicationTest.java
new file mode 100644
index 0000000..f9d4645
--- /dev/null
+++ b/zalando-cloud-aws-samples/zalando-cloud-aws-kms-sample/src/test/java/com/zalando/awsspring/samples/bootstrap/BootstrapApplicationTest.java
@@ -0,0 +1,27 @@
+package com.zalando.awsspring.samples.bootstrap;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.DynamicPropertyRegistry;
+import org.testcontainers.containers.localstack.LocalStackContainer;
+import org.testcontainers.containers.localstack.LocalStackContainer.Service;
+import org.testcontainers.utility.DockerImageName;
+
+@SpringBootTest
+public class BootstrapApplicationTest {
+
+ static LocalStackContainer localStack = new LocalStackContainer(DockerImageName.parse("localstack/localstack:3.0"))
+ .withServices(Service.KMS);
+
+ public static void overridePropertes(DynamicPropertyRegistry registry) {
+ registry.add("spring.cloud.aws.region.static", () -> localStack.getRegion());
+ registry.add("spring.cloud.aws.credentials.access-key", () -> localStack.getAccessKey());
+ registry.add("sprng.cloud.aws.credentials.secret-key", () -> localStack.getSecretKey());
+ registry.add("spring.cloud.aws.kms.endpoint", () -> localStack.getEndpointOverride(Service.KMS));
+ }
+
+ @Test
+ public void contextLoads() {
+
+ }
+}
diff --git a/zalando-cloud-aws-starters/zalando-cloud-aws-starter-kms/pom.xml b/zalando-cloud-aws-starters/zalando-cloud-aws-starter-kms/pom.xml
new file mode 100644
index 0000000..cdbe0eb
--- /dev/null
+++ b/zalando-cloud-aws-starters/zalando-cloud-aws-starter-kms/pom.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+ zalando-cloud-aws
+ com.zalando.awspring.cloud
+ 3.1.1-SNAPSHOT
+ ../../pom.xml
+
+
+ 4.0.0
+
+ zalando-cloud-aws-starter-kms
+ Zalando Cloud AWS KMS Starter
+ Zalando Cloud AWS Key Management Service Starter
+
+
+
+ io.awspring.cloud
+ spring-cloud-aws-starter
+
+
+ com.zalando.awspring.cloud
+ zalando-cloud-aws-kms
+ ${project.version}
+
+
+
\ No newline at end of file