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:

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:

<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:

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:

  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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s