Using AWS Secrets Manager with Spring Boot


In AWS (ECS / EC 2), when using database password or RSA secret key, It is good to obtain from AWS Secrets Manager

Terraform settings

AWS Secrets Manager

Create AWS Secrets Manager with terraform

resource "aws_secretsmanager_secret" "something" {
  name       = "${var.app_name}/${terraform.workspace}/something"
  kms_key_id = "${aws_kms_key.main.key_id}"
}

resource "aws_secretsmanager_secret_version" "something" {
  secret_id     = "${aws_secretsmanager_secret.something.id}"
  secret_string = "{}"

  lifecycle {
    ignore_changes = ["secret_string"]
  }
}

IAM Role

Add permissions to read/write values to AWS Secrets Manager.
Since the data of AWS Secrets Manager is encrypted, Access permissions to KMS are also required for decryption.

data "aws_caller_identity" "current" {}


resource "aws_iam_role_policy" "something" {
  name = "${var.app_name}_${terraform.workspace}_something_secrets"
  role = "${aws_iam_role.something.id}"

  policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [ "secretsmanager:GetSecretValue" ],
      "Resource": "arn:aws:secretsmanager:${var.aws_region}:${data.aws_caller_identity.current.account_id}:secret:${var.app_name}/${terraform.workspace}/something-*"
    },
    {
      "Action": [
        "kms:Decrypt"
      ],
      "Resource": "${aws_kms_key.main.arn}",
      "Effect": "Allow"
    }
  ]
}
POLICY
}

ECS

Information to access AWS Secrets Manager from Spring Boot is set to environment variable.

resource "aws_ecs_task_definition" "something" {
  container_definitions = <<CONTAINER_DEF
  [
    {
      ...
      "environment": [
        {
          "name": "AWS_SECRETSMANAGER_ENABLED",
          "value": "true"
        },
        {
          "name": "AWS_SECRETSMANAGER_NAME",
          "value": "${var.app_name}/${terraform.workspace}/something"
        },
        {
          "name": "AWS_SECRETSMANAGER_REGION",
          "value": "${var.aws_region}"
        }
      ]
    }
  ]
  CONTAINER_DEF
}

Dependencies

Add AWS SDK for Java to dependencies.

// build.gradle
buildscript {
  dependencies {
    classpath "io.spring.gradle:dependency-management-plugin:1.0.3.RELEASE"
  }
}
apply plugin: "io.spring.dependency-management"

dependencyManagement {
    imports {
        mavenBom 'com.amazonaws:aws-java-sdk-bom:1.11.343'
    }
}
dependencies {
  compile "com.amazonaws:aws-java-sdk-secretsmanager"
}

Set data acquired from AWS Secrets Manager to Spring’s Environment

package org.example

import com.amazonaws.services.secretsmanager.*
import com.amazonaws.services.secretsmanager.model.*
import com.fasterxml.jackson.databind.*
import org.springframework.boot.*
import org.springframework.boot.env.*
import org.springframework.core.env.*

class AwsSecretsManagerEnvironmentPostProcessor : EnvironmentPostProcessor {
    override fun postProcessEnvironment(env: ConfigurableEnvironment, application: SpringApplication) {
        // Use AWS Secrets Manager only when running on AWS
        if (!env.getProperty("aws.secretsmanager.enabled", Boolean::class.java, false)) {
            return
        }

        val req = GetSecretValueRequest()
        req.secretId = env.getProperty("aws.secretsmanager.name") ?: return

        val region = env.getProperty("aws.secretsmanager.region") ?: return
        val client = AWSSecretsManagerClientBuilder.standard()
                .withRegion(region)
                .build()
        val secretString = client.getSecretValue(req).secretString

        @Suppress("UNCHECKED_CAST")
        val map = ObjectMapper().readValue(secretString, Map::class.java) as Map<String, Any>
        env.propertySources.addFirst(MapPropertySource("secrets", map))
    }
}

Register EnvironmentPostProcessor in Spring

Append to src/main/resources/META-INF/spring.factories

org.springframework.boot.env.EnvironmentPostProcessor=org.example.AwsSecretsManagerEnvironmentPostProcessor

See also