Spring Cloud AWS Parameter Store Integration Plus Optional JASYPT Encrypted Properties

By Adam McQuistan in DevOps  12/18/2022 Comment

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.

Cloning and Deploying the Demo Project

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
Next install, build, synthesize, and deploy the project like so.
yarn
yarn build
yarn synth
yarn deploy
The final output will show a couple of HTTP enabled endpoints for experimenting with the different property types.

Example output, yours will vary.
 ✅  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
One endpoint will return a JSON response containing my first name sourced from a plain text string AWS Parameter Store entry. The other endpoint will return a JSON response containing my middle name source from a JASYPT password based encrypted string AWS Parameter Store entry. However, my endpoint URLs will be different from yours so please be sure to use the endpoints generated when you deploy this project via the CDK.

Testing the first name plain text parameter response.
curl http://Sprin-Farga-N8F4VT4CC26U-752002252.us-east-1.elb.amazonaws.com/first-name
Output.
{"firstName":"adam"}
Testing the middle name encrypted parameter response.
curl http://Sprin-Farga-N8F4VT4CC26U-752002252.us-east-1.elb.amazonaws.com/middle-name
Output.
{"middleName":"James"}
When you're done playing with the application the AWS resources can be destroyed to cease incurring costs by issuing the following command to delete the underlying CloudFormation Stack.
yarn destroy
Now that we've verified things work lets move on to understand how it works.

Overview of JASYPT Encryption Technique

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
I then encrypt the sensitive info, my middle name of James, using the password Develop3r and the helper shell utilities available within the JASYPT project with Password Based Encryption (PBE).
./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
Take the output value and wrap it in ENC(encrypted-output-goes-here) which is what will be stored in AWS Parameter Store or in a regular application.yml if not using AWS Param Store. The ENC(...) wrapper is used to tell JASYPT which properties to decrypt inside the Spring Boot application.
 

Spring Dependencies and Conventions

In order to reap the benefits of Spring Cloud AWS's simple integration with AWS SSM Parameter Store and JASYPT encryption/decryption we must add the dependencies to our Spring Boot project. Here I'm using Gradle for my build tool and dependency manager so these go in build.gradle as shown below.
 
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'
}
Now on to configuring these dependencies to work properly in my Spring Boot app. The first change required is to add a bootstrap.yml file within the resources directory to explicitly enable the AWS Parameter Store integration and provide an application name which is used to lookup application specific parameters.
 
aws:
  paramstore:
    enabled: true

spring:
  application:
    name: awsparams-demo
The Spring Cloud AWS project uses a default (but configurable) convention to look up parameters in AWS by paths following the form "/config/app-name/some.property.name" where app-name corresponds to the property "spring.application.name" and "some.property.name" is any property you wish to utilize in your app. For example, I'll source my first name under the parameter path "author.first-name" and my middle name as "author.middle-name" from my AwsparamsApplication class like so.
 
@Value("${author.first-name}")
String firstName;

@Value("${author.middle-name}")
String middleName;
They will be saved in the AWS SSM Parameter store under the paths /config/awsparams-demo/author.first-name and /config/awsparams-demo/author.middle-name which we'll see in the next section when reviewing the CDK code. The AWS Spring Cloud project also looks under the default base path of /config/application for any generic non-application specific configuration that can be shared across multiple apps.
 

Configuring the JASYPT integration is as simple as adding the same set of properties that were used to encrypt the senstive value via the CLI tool described previously. Here are the properties I added to application.yml specific to JASYPT.
 
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
 

Provisioning AWS SSM Parameters with CDK

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/*`
  ]
}));
Note that the "sensitive" value representing my middle name previously encrypted using JASYPT and wrapped in ENC(...) is used for the middle name param. This provides the benefit of not having the sensitive value stored in code or in the AWS Parameter Store as plain text.

Conclusion

In this article I reviewed how to utilize the AWS Spring Cloud and JASYPT projects within a Spring Boot application to externalize standard and sensitive application configuration. Its worth noting that the AWS Spring Cloud project also provides an integration for AWS Secrets Manager which is a more AWS native approach to storing and accessing sensitive information similar to what was accomplished in this article.

As always, I thank you for reading and please feel free to ask questions or critique in the comments section below.

Share with friends and colleagues

[[ likes ]] likes

Navigation

Community favorites for DevOps

theCodingInterface