Spring Cloud Config - AWS KMS Add-on

Build Status Javadocs Maven Central License

This is a Spring Cloud Config add-on that provides encryption via AWS (Amazon Web Services) KMS (Key management service).

Features

Installation

Prerequisites

Given you have a Spring Boot application.

Choose the correct library version!

According to the Spring Cloud release trains:

Version Use with
2.x Spring Cloud Edgware + Spring Boot 1.5
3.x Spring Cloud Finchley + Spring Boot 2.0
4.x Spring Cloud Greenwich + Spring Boot 2.1
5.x Spring Cloud Hoxton + Spring Boot 2.2

Step 1

Add our dependency to your pom.xml (or Gradle build file).

...
<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>spring-cloud-config-aws-kms</artifactId>
    <version>${spring-cloud-aws-kms.version}</version>
</dependency>
...

Optional: Step 1-1

Use a more recent version of the AWS SDK. To enable all features of this library (especially asymmetric keys), one needs to upgrade the AWS Java SDK manually.

<properties>
    <aws-java-sdk.version>1.11.774</aws-java-sdk.version>
</properties>

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-kms</artifactId>
            <version>${aws-java-sdk.version}</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>aws-java-sdk-core</artifactId>
            <version>${aws-java-sdk.version}</version>
        </dependency>
        <dependency>
            <groupId>com.amazonaws</groupId>
            <artifactId>jmespath-java</artifactId>
            <version>${aws-java-sdk.version}</version>
        </dependency>
    </dependencies>
</dependencyManagement>

Optional: Step 2

Apply configuration to the application's Bootstrap Context

E.g. bootstrap.yml:

aws:
    kms:
        # 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: if not set, the AWS Default Region Provider Chain is used
        region: eu-central-1

        # Optional: Turn off the KMS feature completely (e.g. for local development) 
        enabled: false

        # Optional: Switch to asymmetric algorithm.
        # See com.amazonaws.services.kms.model.EncryptionAlgorithmSpec for available values.
        encryptionAlgorithm: "RSAES_OAEP_SHA_256"

        # Optional: Enable endpoint usage, if provided, aws.kms.region should be excluded as it will be ignored
        endpoint:
            # Required: service endpoint (vpc endpoint or standard regional endpoint); https://kms.eu-central-1.amazonaws.com is also valid
            service-endpoint: kms.eu-central-1.amazonaws.com

            # Optional: signing region for SigV4 signing of requests - if used, should be different from the already regional service-endpoint
            signing-region: us-east-1

The aws.kms.keyId property must be set if

Those are the properties used by this library:

AWS region and credentials are taken from the environment through the Default Credential Provider Chain and the Default Region Provider Chain. However, the region can be overwritten by property aws.kms.region if necessary.

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 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 to encrypt/decrypt data. By default, this library assumes a symmetric key. There are configuration options available to enable asymmetric keys. Important: Please make sure to upgrade the AWS SDK in your project

Encryption

Add keyId and encryptionAlgorithm to the bootstrap.yaml:

aws:
  kms:
    encryptionAlgorithm: "RSAES_OAEP_SHA_256"  # or "RSAES_OAEP_SHA_1"
    keyId: "9d9fca31-54c5-4df5-ba4f-127dfb9a5031"

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" 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

FAQ

Error with Spring Cloud Config Server

When using this library together with spring-cloud-config-server some users saw this error:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.cloud.config.server.config.EncryptionAutoConfiguration$EncryptorConfiguration': Unsatisfied dependency expressed through field 'encryptor'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.security.crypto.encrypt.TextEncryptor' available: expected single matching bean but found 2: defaultTextEncryptor,kmsTextEncryptor
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:643) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:130) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessProperties(AutowiredAnnotationBeanPostProcessor.java:399) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1422) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    ...
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.security.crypto.encrypt.TextEncryptor' available: expected single matching bean but found 2: defaultTextEncryptor,kmsTextEncryptor
    at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:220) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1265) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1207) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:640) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    ...

The solution is to make sure, that spring-cloud-config-aws-kms appears above spring-cloud-config-server in the list of dependencies of your maven pom.xml file. Please take a look at the ConfigServerTest in the module integration-test-3 for usage with Spring Cloud Config Server.

How to get the cipher text?

The Spring Cloud Config Server library provides an endpoint to encrypt plain text strings. Make sure to secure this endpoint properly! See reference for details.

You can also use AWS CLI or our small CLI tool to encrypt and decrypt values.

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.

License

Copyright (C) 2015 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.