Sharing a Secret with a Tomcat Service in ECS
AWS is pretty good, with a lot of documentation. However, often you need to spot the detail you need among a bunch of documentation that’s not wrong, but isn’t quite going to cover your use case.
Providing the low-level detail doesn’t always provide enough information to assemble a complete coherent solution.
I’ve recently been constructing some Cloudformation deployments. We want our tomcat instance, running inside a fargate container in ECS, to be able to point to the database for the deployed environment, with secret credentials.
This opens a world of pain in terms of how to safely get credentials from the right secret in secrets manager, into the config in the server.xml
.
How Tomcat Can Read Environment Variables
Bear in mind that my Tomcat runs in docker… I can add this to the Dockerfile
:
1 | ENV JAVA_OPTS=-Dorg.apache.tomcat.util.digester.PROPERTY_SOURCE=org.apache.tomcat.util.digester.EnvironmentPropertySource |
This turns on interpolation of environment variables in tomcat config XML file reading. So if my server.xml
does something like this:
1 2 3 4 5 6 7 | < Resource name = "jdbc/mydatabase" auth = "Container" type = "javax.sql.DataSource" factory = "org.apache.tomcat.jdbc.pool.DataSourceFactory" driverClassName = "com.mysql.cj.jdbc.Driver" ... username = "${MYSQL_USER}" password = "${MYSQL_PASSWORD}" url = "jdbc:mysql://${MYSQL_HOST}/mydb?useSSL=false" ... /> |
This means that the MYSQL_
environment variables from the container are injected into the JDBC settings.
It involved a lot of searching to find the above.
Then We Read the Secret
The secret I’m using contains the username, password and host. It’s made automatically by Cloudformation, and has its own rotation schedule. All I need to do is pluck username
, password
and host
from it, provided I know which secret to use.
Let’s put an environment map in our Cloudformation with the secret in:
1 2 3 4 5 | Mappings: dev: Database: # the secret dictates usernames/passwords/endpoints Secret: arn:aws:secretsmanager:eu-west- 1 : 12345678 :secret:database-secret-abRK8x |
We have a StageName
variable which dictates which environment we’re in. So now, in our fargate container definition, we just need to pluck the values from the secrets into the environment variables:
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | MyEcsTask: Type: AWS::ECS::TaskDefinition Properties: RequiresCompatibilities: - FARGATE ContainerDefinitions: - Name: !Sub ${AWS::StackName}-tomcat Image: my-tomcat-container:latest PortMappings: - ContainerPort: 8080 HostPort: 8080 Secrets: - Name: MYSQL_USER ValueFrom: !Sub - '${secret}:username::' - secret: !FindInMap [!Ref StageName, Database, Secret] - Name: MYSQL_PASSWORD ValueFrom: !Sub - '${secret}:password::' - secret: !FindInMap [!Ref StageName, Database, Secret] - Name: MYSQL_HOST ValueFrom: !Sub - '${secret}:host::' - secret: !FindInMap [!Ref StageName, Database, Secret] |
There’s a lot going on in the Secrets
section there. The ValueFrom
is using a !Sub
expression to work out the secret to use, based on the current StageName
using the environment map to find the right secret.
What’s also happening, though, is that the username
, password
and host
keys from the secret are being read individually. This involves a syntax of <secret-name>:<desired-json-key>::
– those trailing ::
s are very important (or it doesn’t work!).
Conclusion
The above information is available in the docs if you know where to look.
This recipe will hopefully be of some use to someone.
Published on Java Code Geeks with permission by Ashley Frieze, partner at our JCG program. See the original article here: Sharing a Secret with a Tomcat Service in ECS Opinions expressed by Java Code Geeks contributors are their own. |