In this article I demonstrate how to use the AWS Parameter Store to decouple and externalize configuration for a Spring Boot application. I show the basic usage pattern using Spring Cloud AWS and plain text configuration properties then I present a more secure pattern using Java Simplified Encryption (JASYPT) for more sensitive properties.
To make things fully reproducible for readers following along I'll utilize AWS Cloud Development Kit (CDK) for repeatable provisioning of the demo application presented and available on GitHub.
AWS CDK is a modern Open Source Infrastructure as Code (IaC) technology managed by AWS. If you've not done so and intend to follow along please install the AWS CDK as described in the CDK Getting Started documentation. For this project I'll be using the Typescript base implementation of CDK.
Once you've got your system setup with the necessary CDK and Typescript dependencies clone the project.
git clone https://github.com/amcquistan/springboot-aws-paramstore.git
cd springboot-aws-paramstore
yarn
yarn build
yarn synth
yarn deploy
✅ SpringAwsParamstoreStack
Outputs:
SpringAwsParamstoreStack.FargateLoadBalancerDNSA8D33CCB = Sprin-Farga-N8F4VT4CC26U-752002252.us-east-1.elb.amazonaws.com
SpringAwsParamstoreStack.FargateServiceURL8C558CA1 = http://Sprin-Farga-N8F4VT4CC26U-752002252.us-east-1.elb.amazonaws.com
SpringAwsParamstoreStack.FirstNameParamEndpoint = http://Sprin-Farga-N8F4VT4CC26U-752002252.us-east-1.elb.amazonaws.com/first-name
SpringAwsParamstoreStack.MiddleNameParamEndpoint = http://Sprin-Farga-N8F4VT4CC26U-752002252.us-east-1.elb.amazonaws.com/middle-name
curl http://Sprin-Farga-N8F4VT4CC26U-752002252.us-east-1.elb.amazonaws.com/first-name
{"firstName":"adam"}
curl http://Sprin-Farga-N8F4VT4CC26U-752002252.us-east-1.elb.amazonaws.com/middle-name
{"middleName":"James"}
yarn destroy
In this section I discuss how to use the base jasypt library CLI capabilities to encrypt a string value I want to protect via encryption keeping it from being stored in plain text. This is pretty silly because I'm exposing it via an unprotected REST API but, we are learning here and my middle name is not terribly sensitive information.
I start by downloading JASYPT via curl. If you don't have curl installed you can just as easily download using your browser by pasting in the URL. The commands below are all for a unix style machine so please use the corresponding operations if you're on a Windows machine.
curl -L -o jasypt.zip https://github.com/jasypt/jasypt/releases/download/jasypt-1.9.3/jasypt-1.9.3-dist.zip
unzip jasypt.zip
mv jasypt-1.9.3 jasypt
chmod +x jasypt/bin/*.sh
./jasypt/bin/encrypt.sh input=James password=Develop3r \
ivGeneratorClassName=org.jasypt.iv.RandomIvGenerator \
verbose=true \
algorithm=PBEWITHHMACSHA512ANDAES_256 \
keyObtentionIterations=1000
saltGeneratorClassName=org.jasypt.salt.RandomSaltGenerator
providerName=null \
providerClassName=null \
stringOutputType=base64
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter'
// these two are required to work with AWS Spring Cloud
implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap'
implementation 'io.awspring.cloud:spring-cloud-starter-aws-parameter-store-config:2.4.2'
// this is required to utilize JASYPT
implementation 'com.github.ulisesbocchio:jasypt-spring-boot-starter:3.0.5'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
aws:
paramstore:
enabled: true
spring:
application:
name: awsparams-demo
@Value("${author.first-name}")
String firstName;
@Value("${author.middle-name}")
String middleName;
jasypt:
encryptor:
password: Develop3r
iv-generator-classname: org.jasypt.iv.RandomIvGenerator
algorithm: PBEWITHHMACSHA512ANDAES_256
key-obtention-iterations: 1000
salt-generator-classname: org.jasypt.salt.RandomSaltGenerator
string-output-type: base64
Ok now we'll review the CDK code utilized to store these externalized configuration values within the AWS SSM Parameter Store. Inside the lib/spring-aws-paramstore-stack.ts file is where all the action happens but, I'll just focus on the SSM params and the IAM permissions required to access these parameters from the deployed ECS Fargate Task.
const firstNameParam = new ssm.StringParameter(this, 'FirstNameParam', {
parameterName: "/config/awsparams-demo/author.first-name",
stringValue: "adam"
});
const middleNameParam = new ssm.StringParameter(this, 'MiddleNameParam', {
parameterName: "/config/awsparams-demo/author.middle-name",
stringValue: "ENC(V/JV41F6DP4qCIOwQJNOR2+umiNJvfQdMZDv63OyY+ZTmMsQIkw5tm22wzE7Hj4L)"
});
const taskRole = new iam.Role(this, "TaskRole", {
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com")
});
taskRole.node.addDependency(firstNameParam, middleNameParam);
taskRole.addToPolicy(new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
actions: ["ssm:GetParametersByPath"],
resources: [
`arn:aws:ssm:${Aws.REGION}:${Aws.ACCOUNT_ID}:parameter/config/awsparams-demo/*`,
`arn:aws:ssm:${Aws.REGION}:${Aws.ACCOUNT_ID}:parameter/config/application/*`
]
}));
As always, I thank you for reading and please feel free to ask questions or critique in the comments section below.