diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..5f0536e --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,2 @@ +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/coverage/pom.xml b/coverage/pom.xml deleted file mode 100644 index 4776892..0000000 --- a/coverage/pom.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - 4.0.0 - - - org.zalando - spring-cloud-config-aws-kms-parent - 5.2-SNAPSHOT - - - spring-cloud-config-aws-kms-coverage - Spring Cloud Config AWS KMS Test Coverage - - pom - - - true - true - - - - - org.zalando - spring-cloud-config-aws-kms - ${project.version} - - - org.zalando - spring-cloud-config-aws-kms-integration-test-1 - ${project.version} - - - org.zalando - spring-cloud-config-aws-kms-integration-test-2 - ${project.version} - - - - - - - org.jacoco - jacoco-maven-plugin - - - - aggregate-reports - test - - report-aggregate - - - Spring Cloud Config AWS KMS Overall Coverage - ${project.reporting.outputDirectory}/jacoco - - - - - - - diff --git a/encryption-cli/README.md b/encryption-cli/README.md deleted file mode 100644 index e9c555e..0000000 --- a/encryption-cli/README.md +++ /dev/null @@ -1,22 +0,0 @@ -Installation ------------- - - # in the project root - mvn clean package -DskipTests -pl encryption-cli -am - -Preparation ------------ - -Configure AWS [credentials](http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default) -and [region](http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/java-dg-region-selection.html#automatically-determine-the-aws-region-from-the-environment). - -Usage ------ - -### Encrypt - - ./run.sh --encrypt.plaintext='Hello World!' --aws.kms.keyId='9d9fca31-54c5-4df5-ba4f-127dfb9a5031' - -### Decrypt - - ./run.sh --decrypt.ciphertext='CiA47hYvQqWFFGq3TLtzQO5ArcwDkjq69Q==' diff --git a/encryption-cli/pom.xml b/encryption-cli/pom.xml deleted file mode 100644 index ddb390c..0000000 --- a/encryption-cli/pom.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - 4.0.0 - - - org.zalando - spring-cloud-config-aws-kms-parent - 5.2-SNAPSHOT - - - spring-cloud-config-aws-kms-encryption-cli - Spring Cloud Config AWS KMS Encryption CLI - - - true - true - [1.11.774,) - - - - - - 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.springframework.boot - spring-boot-starter - - - org.zalando - spring-cloud-config-aws-kms - ${project.version} - - - com.google.guava - guava - 31.0.1-android - - - - - ${project.artifactId} - - - org.springframework.boot - spring-boot-maven-plugin - - - - - diff --git a/encryption-cli/run.sh b/encryption-cli/run.sh deleted file mode 100755 index c5bf580..0000000 --- a/encryption-cli/run.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -java -jar target/spring-cloud-config-aws-kms-encryption-cli.jar "$@" diff --git a/encryption-cli/src/main/java/de/zalando/spring/cloud/config/aws/kms/test/DecryptProperties.java b/encryption-cli/src/main/java/de/zalando/spring/cloud/config/aws/kms/test/DecryptProperties.java deleted file mode 100644 index 2406ca0..0000000 --- a/encryption-cli/src/main/java/de/zalando/spring/cloud/config/aws/kms/test/DecryptProperties.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.zalando.spring.cloud.config.aws.kms.test; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties("decrypt") -public class DecryptProperties { - - private String ciphertext; - - public String getCiphertext() { - return ciphertext; - } - - public void setCiphertext(String ciphertext) { - this.ciphertext = ciphertext; - } -} diff --git a/encryption-cli/src/main/java/de/zalando/spring/cloud/config/aws/kms/test/EncryptProperties.java b/encryption-cli/src/main/java/de/zalando/spring/cloud/config/aws/kms/test/EncryptProperties.java deleted file mode 100644 index cc7d351..0000000 --- a/encryption-cli/src/main/java/de/zalando/spring/cloud/config/aws/kms/test/EncryptProperties.java +++ /dev/null @@ -1,17 +0,0 @@ -package de.zalando.spring.cloud.config.aws.kms.test; - -import org.springframework.boot.context.properties.ConfigurationProperties; - -@ConfigurationProperties("encrypt") -public class EncryptProperties { - - private String plaintext; - - public String getPlaintext() { - return plaintext; - } - - public void setPlaintext(String plaintext) { - this.plaintext = plaintext; - } -} diff --git a/encryption-cli/src/main/java/de/zalando/spring/cloud/config/aws/kms/test/EncryptionCLI.java b/encryption-cli/src/main/java/de/zalando/spring/cloud/config/aws/kms/test/EncryptionCLI.java deleted file mode 100644 index 721f38f..0000000 --- a/encryption-cli/src/main/java/de/zalando/spring/cloud/config/aws/kms/test/EncryptionCLI.java +++ /dev/null @@ -1,51 +0,0 @@ -package de.zalando.spring.cloud.config.aws.kms.test; - -import de.zalando.spring.cloud.config.aws.kms.KmsTextEncryptor; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.stereotype.Component; - -import static org.springframework.util.StringUtils.hasText; - -@Component -public class EncryptionCLI implements CommandLineRunner { - - private final KmsTextEncryptor kmsTextEncryptor; - private final EncryptProperties encrypt; - private final DecryptProperties decrypt; - - @Autowired - public EncryptionCLI(KmsTextEncryptor kmsTextEncryptor, EncryptProperties encrypt, DecryptProperties decrypt) { - this.kmsTextEncryptor = kmsTextEncryptor; - this.encrypt = encrypt; - this.decrypt = decrypt; - } - - private void printUsage() { - System.out.println("Usage:\n" // - + "Make sure that AWS credentials and region are set, either in ~/.aws/config, ~/.aws/credentials\n" // - + "or via environment variables, e.g. `export AWS_REGION=eu-central-1`\n" // - + "\n" // - + "then do\n" // - + "./run.sh --encrypt.plaintext='Hello World!' --aws.kms.keyId='9d9fca31-54c5-4df5-ba4f-127dfb9a5031'\n" // - + "./run.sh --decrypt.ciphertext='CiA47hYvQqWFFGq3TLtzQO5ArcwDkjq69Q=='"); - } - - @Override - public void run(final String... args) { - final String plaintext = encrypt.getPlaintext(); - final String ciphertext = decrypt.getCiphertext(); - try { - if (hasText(plaintext)) { - System.out.println(kmsTextEncryptor.encrypt(plaintext)); - } else if (hasText(ciphertext)) { - System.out.println(kmsTextEncryptor.decrypt(ciphertext)); - } else { - printUsage(); - } - } catch (Exception e) { - System.out.println(e.getMessage()); - printUsage(); - } - } -} diff --git a/encryption-cli/src/main/java/de/zalando/spring/cloud/config/aws/kms/test/TestApplication.java b/encryption-cli/src/main/java/de/zalando/spring/cloud/config/aws/kms/test/TestApplication.java deleted file mode 100644 index 0e446e0..0000000 --- a/encryption-cli/src/main/java/de/zalando/spring/cloud/config/aws/kms/test/TestApplication.java +++ /dev/null @@ -1,18 +0,0 @@ -package de.zalando.spring.cloud.config.aws.kms.test; - -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.builder.SpringApplicationBuilder; -import org.springframework.boot.context.properties.EnableConfigurationProperties; - -import static org.springframework.boot.Banner.Mode.OFF; - -@SpringBootApplication -@EnableConfigurationProperties({EncryptProperties.class, DecryptProperties.class}) -public class TestApplication { - - public static void main(final String[] args) { - final SpringApplicationBuilder app = new SpringApplicationBuilder(TestApplication.class); - app.bannerMode(OFF); - app.run(args); - } -} diff --git a/encryption-cli/src/main/resources/logback.xml b/encryption-cli/src/main/resources/logback.xml deleted file mode 100644 index 020f1e9..0000000 --- a/encryption-cli/src/main/resources/logback.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - ${CONSOLE_LOG_PATTERN} - utf8 - - - - - - diff --git a/library/pom.xml b/library/pom.xml deleted file mode 100644 index 0b38e6b..0000000 --- a/library/pom.xml +++ /dev/null @@ -1,181 +0,0 @@ - - - 4.0.0 - - - org.zalando - spring-cloud-config-aws-kms-parent - 5.2-SNAPSHOT - - - spring-cloud-config-aws-kms - - jar - - Spring Cloud Config AWS KMS - - - 1.8 - true - - [1.11.774,) - - - - - - 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.springframework - spring-context - - - org.springframework.cloud - spring-cloud-context - - - org.springframework.boot - spring-boot-autoconfigure - - - - com.amazonaws - aws-java-sdk-kms - ${aws-java-sdk.version} - - - - org.slf4j - slf4j-api - - - - org.springframework.boot - spring-boot-configuration-processor - true - - - - org.springframework.boot - spring-boot-starter-test - test - - - org.springframework.boot - spring-boot-starter-logging - test - - - org.yaml - snakeyaml - test - - - - - - - ossrh - Sonatype Nexus Snapshots - https://oss.sonatype.org/content/repositories/snapshots - - - ossrh - Nexus Release Repository - https://oss.sonatype.org/service/local/staging/deploy/maven2 - - - - - - - maven-source-plugin - ${maven-source-plugin.version} - - - attach-sources - - jar-no-fork - - - - - - maven-javadoc-plugin - ${maven-javadoc-plugin.version} - - - attach-javadocs - - jar - - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - ${nexus-staging-maven-plugin.version} - - ossrh - https://oss.sonatype.org/ - true - true - - - - default-deploy - - deploy - - deploy - - - - - org.jacoco - jacoco-maven-plugin - - - - - - - with-gpg - - - - maven-gpg-plugin - ${maven-gpg-plugin.version} - - - sign-artifacts - verify - - sign - - - - - - - - - diff --git a/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/EncryptedToken.java b/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/EncryptedToken.java deleted file mode 100644 index ba5a4c1..0000000 --- a/library/src/main/java/de/zalando/spring/cloud/config/aws/kms/EncryptedToken.java +++ /dev/null @@ -1,120 +0,0 @@ -package de.zalando.spring.cloud.config.aws.kms; - -import org.springframework.util.Assert; -import org.springframework.util.StringUtils; - -import java.nio.ByteBuffer; -import java.util.Base64; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Stream; - -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.toMap; - -class EncryptedToken { - - private static final Pattern ENCRYPTED_STRING = Pattern.compile("^(?>\\((?.*)\\)|\\[(?.*)]){0,2}(?.*)$"); - private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder(); - private static final Function BASE64_DECODE_VALUE = v -> new String(BASE64_DECODER.decode(v)); - private static final Function FIRST = arr -> arr[0]; - private static final Function SECOND = arr -> arr.length > 1 ? arr[1] : ""; - private static final String PAIR_SEPARATOR = ","; - private static final String KEY_VALUE_SEPARATOR = "="; - - private final ByteBuffer cipherBytes; - private final Map encryptionContext; - private final KmsTextEncryptorOptions options; - - private EncryptedToken(ByteBuffer cipherBytes, Map encryptionContext, KmsTextEncryptorOptions options) { - this.cipherBytes = cipherBytes; - this.encryptionContext = encryptionContext; - this.options = options; - } - - ByteBuffer getCipherBytes() { - return cipherBytes; - } - - Map getEncryptionContext() { - return encryptionContext; - } - - KmsTextEncryptorOptions getOptions() { - return options; - } - - static EncryptedToken parse(String s) { - Assert.hasText(s, "Encrypted string must not be blank"); - - final Matcher matcher = ENCRYPTED_STRING.matcher(s); - Assert.isTrue(matcher.matches(), "Malformed encrypted string '" + s + "'"); - - final String contextString = matcher.group("context"); - final String optionsString = matcher.group("options"); - final String cipherString = matcher.group("cipher"); - - return new EncryptedToken(parseCipher(cipherString), parseContext(contextString), parseOptions(optionsString)); - } - - private static ByteBuffer parseCipher(String valueString) { - return ByteBuffer.wrap(BASE64_DECODER.decode(valueString.getBytes())); - } - - private static KmsTextEncryptorOptions parseOptions(String optionsString) { - final Map optionsMap = parseKeyValueMap(optionsString); - - final OutputMode output = OutputMode.valueOf( - Optional.ofNullable(optionsMap.get("output")) - .map(String::toUpperCase) - .orElse(OutputMode.PLAIN.name())); - final String kmsKeyId = optionsMap.get("keyId"); - final String encryptionAlgorithm = optionsMap.get("algorithm"); - - return KmsTextEncryptorOptions.builder() - .withOutputMode(output) - .withEncryptionAlgorithm(encryptionAlgorithm) - .withKeyId(kmsKeyId) - .build(); - } - - private static Map parseContext(String contextString) { - return parseKeyValueMap(contextString, BASE64_DECODE_VALUE); - } - - /** - * Convenience overload of {@link #parseKeyValueMap(String, Function)} that keeps the original map values without - * modifying them - */ - private static Map 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  INFO 1174881 --- [ main] c.z.a.c.b.encrypt.KmsTextEncryptor  : 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)] +2024-03-14T10:09:23.801+01:00  INFO 1174881 --- [ main] c.z.a.c.b.encrypt.KmsTextEncryptor  : 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