Last active
November 28, 2018 01:15
-
-
Save jcyuyi/bfd19029d5cd238a42dd780b54947036 to your computer and use it in GitHub Desktop.
Use AWS Secrets Manager to config secret properties in Spring using EnvironmentPostProcessor
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package io.nayu.spring.secretsdemo | |
import com.amazonaws.auth.AWSStaticCredentialsProvider | |
import com.amazonaws.auth.BasicAWSCredentials | |
import com.amazonaws.services.secretsmanager.AWSSecretsManagerClientBuilder | |
import com.amazonaws.services.secretsmanager.model.GetSecretValueRequest | |
import org.springframework.boot.SpringApplication | |
import org.springframework.boot.context.event.ApplicationFailedEvent | |
import org.springframework.boot.context.event.ApplicationReadyEvent | |
import org.springframework.boot.context.event.SpringApplicationEvent | |
import org.springframework.core.env.ConfigurableEnvironment | |
import org.springframework.boot.env.EnvironmentPostProcessor | |
import org.springframework.boot.json.JsonParserFactory | |
import org.springframework.boot.logging.DeferredLog | |
import org.springframework.context.ApplicationListener | |
import org.springframework.core.env.MapPropertySource | |
/** | |
* Use [AWS Secrets Manager](https://aws.amazon.com/secrets-manager/) to config secret properties. | |
* | |
* Configurable property: | |
* - `aws.secretsmanager.enabled` : Enable AWS Secrets Manager or not | |
* - `aws.secretsmanager.secrets` : Secret id list | |
* - `aws.secretsmanager.region` : AWS region | |
* | |
* EnvironmentPostProcessor implementations have to be registered in `META-INF/spring.factories`, | |
* using the fully qualified name of this class as the key. | |
**/ | |
class AWSSecretsProcessor : EnvironmentPostProcessor, ApplicationListener<SpringApplicationEvent> { | |
// Use deferred log since application is not ready yet | |
private val logger = DeferredLog() | |
private val parser = JsonParserFactory.getJsonParser() | |
private val clientBuilder: AWSSecretsManagerClientBuilder | |
constructor() { | |
clientBuilder = AWSSecretsManagerClientBuilder.standard() | |
} | |
constructor(awsSecretsManagerClientBuilder: AWSSecretsManagerClientBuilder) { | |
clientBuilder = awsSecretsManagerClientBuilder | |
} | |
override fun postProcessEnvironment(env: ConfigurableEnvironment, application: SpringApplication) { | |
// add SpringApplication listener to replay logs | |
application.addListeners(this) | |
// Use AWS Secrets Manager only when running on AWS | |
if (!env.getProperty(AWS_SECRETSMANAGER_ENABLED_PROPERTY, Boolean::class.java, false)) { | |
logger.info("AWS Secrets Manager is not enabled") | |
return | |
} | |
val secretIds = env.getProperty(AWS_SECRETSMANAGER_SECRETS_PROPERTY, Array<String>::class.java) | |
?: throw IllegalStateException("Failed to load property: $AWS_SECRETSMANAGER_SECRETS_PROPERTY") | |
val region = env.getProperty(AWS_SECRETSMANAGER_REGION_PROPERTY) | |
?: throw IllegalStateException("Failed to load property: $AWS_SECRETSMANAGER_REGION_PROPERTY") | |
val client = clientBuilder | |
.withRegion(region) | |
.build() | |
secretIds.forEach { secretId -> | |
logger.info("fetch secretId: $secretId") | |
val req = GetSecretValueRequest().withSecretId(secretId) | |
val secretString = client.getSecretValue(req).secretString | |
val map = parser.parseMap(secretString) | |
if (!map.isEmpty()) { | |
val propertySourceName = "$AWS_SECRETSMANAGER_SECRETS_PROPERTY/$secretId" | |
env.propertySources.addFirst(MapPropertySource(propertySourceName, map)) | |
logger.info("${map.size} secrets added to propertySource: $propertySourceName, keys: " + map.keys) | |
} | |
} | |
} | |
override fun onApplicationEvent(event: SpringApplicationEvent) { | |
if (event is ApplicationReadyEvent || event is ApplicationFailedEvent) | |
this.logger.switchTo(javaClass) | |
} | |
companion object { | |
/** | |
* Name of the *aws.secretsmanager.enabled* property. | |
*/ | |
const val AWS_SECRETSMANAGER_ENABLED_PROPERTY = "aws.secretsmanager.enabled" | |
/** | |
* Name of the *aws.secretsmanager.secrets* property. | |
*/ | |
const val AWS_SECRETSMANAGER_SECRETS_PROPERTY = "aws.secretsmanager.secrets" | |
/** | |
* Name of the *aws.secretsmanager.region* property. | |
*/ | |
const val AWS_SECRETSMANAGER_REGION_PROPERTY = "aws.secretsmanager.region" | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
AWSSecretsProcessorTest
see Mock the unmockable: opt-in mocking of final classes/metho