= Secrets Manager Integration

https://aws.amazon.com/secrets-manager/[Secrets Manager] helps to protect secrets needed to access your applications, services, and IT resources. The service enables you to easily rotate, manage, and retrieve database credentials, API keys, and other secrets throughout their lifecycle.

Spring Cloud AWS adds support for loading configuration properties from Secrets Manager through Spring Boot https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-external-config-files-importing[config import feature].

Maven coordinates, using <<index.adoc#bill-of-materials, Spring Cloud AWS BOM>>:

[source,xml]
----
<dependency>
	<groupId>io.awspring.cloud</groupId>
	<artifactId>spring-cloud-aws-starter-secrets-manager</artifactId>
</dependency>
----

== Loading External Configuration

To fetch secrets from Secrets Manager and add them to Spring's environment properties, add `spring.config.import` property to `application.properties`:

For example, assuming that the secret name in Secrets Manager is `/secrets/database-secrets`:

[source,properties]
----
spring.config.import=aws-secretsmanager:/secrets/database-secrets
----

If a secret with given name does not exist in Secrets Manager, application will fail to start. If secret value is not required for the application, and it should continue to startup even when secret is missing, add `optional` before prefix:

[source,properties]
----
spring.config.import=optional:aws-secretsmanager:/secrets/database-secrets
----

To load multiple secrets, separate their names with `;`:

[source,properties]
----
spring.config.import=aws-secretsmanager:/secrets/database-secrets;/secrets/webclient-secrets
----

If some secrets are required, and other ones are optional, list them as separate entries in `spring.config.import` property:

[source,properties]
----
spring.config.import[0]=optional:aws-secretsmanager=/secrets/required-secret
spring.config.import[1]=aws-secretsmanager=/secrets/optional-secret
----

Fetched secrets can be referenced with `@Value`, bound to `@ConfigurationProperties` classes, or referenced in `application.properties` file.

=== Using Key-Value (JSON) Secrets

Secrets resolved with `spring.config.import` can be also referenced in `application.properties`.
When a content of `SecretString` in a JSON, all top level JSON keys are added as properties to Spring Environment.

For example, with a file `mycreds.json` containing following JSON structure:

[source,json]
----
{
      "username": "saanvi",
      "password": "EXAMPLE-PASSWORD"
}
----

Secret is created with a command:

[source]
----
$ aws secretsmanager create-secret --name /secrets/database-secrets --secret-string file://mycreds.json
----

`spring.config.import` entry is added to `application.properties`:

[source, properties]
----
spring.config.import=aws-secretsmanager:/secrets/database-secrets
----

Secret values can be referenced by JSON key names:

[source, java]
----
@Value("${username}"
private String username;

@Value("${password}"
private String password;
----

=== Using plain text secrets

If a `SecretString` is a plain text or if you are using `SecretBinary` , use secret name to retrieve its value.
For example, we will JDBC saved as plain text secret type with name `/secrets/my-certificate`:

[source]
----
$ aws secretsmanager create-secret --name /secrets/prod/jdbc-url --secret-string jdbc:url
----

`spring.config.import` entry is added to `application.properties`:

[source, properties]
----
spring.config.import=aws-secretsmanager:/secrets/prod/jdbc-url
----

Secret value can be retrieved by referencing secret name:

[source,properties]
----
spring.datasource.url=${jdbc-url}
----

=== Adding prefix to property keys

To avoid property keys collisions it is possible to configure property key prefix that gets added to each resolved property from a secret.

As an example lets consider following JSON secret with a name `/secrets/database-secrets`:

[source,json]
----
{
      "username": "saanvi",
      "password": "EXAMPLE-PASSWORD"
}
----

By default, `usernmame` and `password` properties will be added to the Spring environment. To add a prefix to property keys configure `spring.config.import` property with `?prefix=` added to the secret name:

[source,properties]
----
spring.config.import=optional:aws-secretsmanager:/secrets/database-secrets?prefix=db.
----

With such config, properties `db.username` and `db.password` are added to the Spring environment.

NOTE: Prefixes are added as-is to all property names returned by Secrets Manager. If you want key names to be separated with a dot between the prefix and key name, make sure to add a trailing dot to the prefix.

== Using SecretsManagerClient

The starter automatically configures and registers a `SecretsManagerClient` bean in the Spring application context. The `SecretsManagerClient` bean can be used to create or retrieve secrets imperatively.

[source,java]
----
import org.springframework.stereotype.Component;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest;
...
@Autowired
private SecretsManagerClient secretsManagerClient;
...
secretsManagerClient.createSecret(CreateSecretRequest.builder().name(name).secretString(secret).build());
----

== Customizing SecretsManagerClient

To use custom `SecretsManagerClient` in `spring.config.import`, provide an implementation of `BootstrapRegistryInitializer`. For example:

[source,java]
----
package com.app;

import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;

import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistryInitializer;

public class SecretsManagerBootstrapConfiguration implements BootstrapRegistryInitializer {

    @Override
    public void initialize(BootstrapRegistry registry) {
        registry.register(SecretsManagerClient.class, context -> {
            AwsCredentialsProvider awsCredentialsProvider = StaticCredentialsProvider.create(AwsBasicCredentials.create("yourAccessKey", "yourSecretKey"));
            return SecretsManagerClient.builder().credentialsProvider(awsCredentialsProvider).region(Region.EU_WEST_2).build();
        });
    }
}
----

Note that this class must be listed under `org.springframework.boot.BootstrapRegistryInitializer` key in `META-INF/spring.factories`:

[source, properties]
----
org.springframework.boot.BootstrapRegistryInitializer=com.app.SecretsManagerBootstrapConfiguration
----

If you want to use autoconfigured `SecretsManagerClient` but change underlying SDKClient or ClientOverrideConfiguration you will need to register bean of type `AwsClientConfigurerSecretsManager`:
Autoconfiguration will configure `SecretsManagerClient` Bean with provided values after that, for example:

[source,java]
----
package com.app;

import io.awspring.cloud.autoconfigure.config.secretsmanager.AwsSecretsManagerClientCustomizer;
import java.time.Duration;
import org.springframework.boot.BootstrapRegistry;
import org.springframework.boot.BootstrapRegistryInitializer;
import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClientBuilder;

class SecretsManagerBootstrapConfiguration implements BootstrapRegistryInitializer {

	@Override
	public void initialize(BootstrapRegistry registry) {
		registry.register(AwsSecretsManagerClientCustomizer.class,
            context -> new AwsSecretsManagerClientCustomizer() {

                @Override
                public ClientOverrideConfiguration overrideConfiguration() {
                    return ClientOverrideConfiguration.builder().apiCallTimeout(Duration.ofMillis(500))
                            .build();
                }

                @Override
                public SdkHttpClient httpClient() {
                    return ApacheHttpClient.builder().connectionTimeout(Duration.ofMillis(1000)).build();
                }
            });
	}
}
----

== `PropertySource` Reload

Some applications may need to detect changes on external property sources and update their internal status to reflect the new configuration.
The reload feature of Spring Cloud AWS Secrets Manager integration is able to trigger an application reload when a related secret value changes.

By default, this feature is disabled. You can enable it by using the `spring.cloud.aws.secretsmanager.reload.strategy` configuration property (for example, in the `application.properties` file).

The following levels of reload are supported (by setting the `spring.cloud.aws.secretsmanager.reload.strategy` property):

* `refresh` (default): Only configuration beans annotated with `@ConfigurationProperties` or `@RefreshScope` are reloaded.
This reload level leverages the refresh feature of Spring Cloud Context.

* `restart_context`: the whole Spring `ApplicationContext` is gracefully restarted. Beans are recreated with the new configuration.
In order for the restart context functionality to work properly you must enable and expose the restart actuator endpoint
[source,yaml]
====
----
management:
  endpoint:
    restart:
      enabled: true
  endpoints:
    web:
      exposure:
        include: restart
----
====

Assuming that the reload feature is enabled with default settings (`refresh` mode), the following bean is refreshed when the secret changes:

====
[java, source]
----
@Configuration
@ConfigurationProperties(prefix = "bean")
public class MyConfig {

    private String message = "a message that can be changed live";

    // getter and setters

}
----
====

To see that changes effectively happen, you can create another bean that prints the message periodically, as follows

====
[source,java]
----
@Component
public class MyBean {

    @Autowired
    private MyConfig config;

    @Scheduled(fixedDelay = 5000)
    public void hello() {
        System.out.println("The message is: " + config.getMessage());
    }
}
----
====

The reload feature periodically re-creates the configuration from config maps and secrets to see if it has changed.
You can configure the polling period by using the `spring.cloud.aws.secretsmanager.reload.period` (default value is 1 minute).

== Configuration

The Spring Boot Starter for Secrets Manager provides the following configuration options:

[cols="2,3,1,1"]
|===
| Name | Description | Required | Default value
| `spring.cloud.aws.secretsmanager.endpoint` | Configures endpoint used by `SecretsManagerClient`. | No | `null`
| `spring.cloud.aws.secretsmanager.region` | Configures region used by `SecretsManagerClient`. | No | `null`
| `spring.cloud.aws.secretsmanager.reload.strategy` | `Enum` | `refresh` | The strategy to use when firing a reload (`refresh`, `restart_context`)
| `spring.cloud.aws.secretsmanager.reload.period` | `Duration`| `15s` | The period for verifying changes
| `spring.cloud.aws.secretsmanager.reload.max-wait-time-for-restart` | `Duration`| `2s`                        | The maximum time between the detection of changes in property source and the application context restart when `restart_context` strategy is used.
|===

== IAM Permissions
Following IAM permissions are required by Spring Cloud AWS:

[cols="2"]
|===
|  Get secret value:
| `secretsmanager:GetSecretValue`
|===

Sample IAM policy granting access to Secrets Manager:

[source,json,indent=0]
----
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "secretsmanager:GetSecretValue",
            "Resource": "yourArn"
        }
    ]
}
----

