In a previous post I mentioned the approach I took to secure an RDS server behind a bastion server being hosted on AWS. Using SSH to create a tunnel to access this resources is well documented and so there is no need to labour on that. However I also needed to access these resources from applications that are running on Docker containers running elsewhere. I am sure there is more than one way to achieve this but I will describe the approach I took and has so far proven to be pretty stable.

I dismissed any code based options as they just made the application more complex to develop and test and instead went for an approach that was purely container based. To do this I used two applications

  1. AutoSSH - to set up the SSH tunnel and maintain it,
  2. Supervisor - to execute and run/monitor multiple programs.

AutoSSH is obvious but why did I need Supervisor? Well a container only has a single ENTRYPOINT (or CMD) which can be used to run an application or a script but in this case I needed to run several (at least two) applications at the same time and for both applications to stay running. This could be managed by scripts but I decided to give Supervisor a try instead.

Adding Supervisor and AutoSSH to the container

Initially I was running a dotnet application and launching it like so in my Dockerfile

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "My.Application.dll"]

I updated this section to fetch AutoSSH and Superisor and install them

FROM base AS final
# install additional packages
RUN mkdir -p /var/log/supervisor /run/sshd
RUN apt-get update && apt-get install -y supervisor autossh

# configure supervisor
RUN mkdir -p /var/log/supervisor 
COPY ["web/My.Application/supervisord.conf", "/etc/supervisor/conf.d/supervisord.conf"] 

# install certificates
COPY ["web/My.Application/certs/rds-ca-2019-root.crt", "/usr/local/share/ca-certificates/"]
RUN chmod 644 /usr/local/share/ca-certificates/rds-ca-2019-root.crt
COPY ["web/My.Application/certs/rds-ca-2019-ap-southeast-2.crt", "/usr/local/share/ca-certificates/"]
RUN chmod 644 /usr/local/share/ca-certificates/rds-ca-2019-ap-southeast-2.crt
RUN update-ca-certificates

# copy application
WORKDIR /app
COPY --from=publish /app/publish .

ENTRYPOINT ["/usr/bin/supervisord", "-c", "/etc/supervisor/conf.d/supervisord.conf"]

Finally in the supervisord.conf I configured it to launch AutoSSH and the original dotnet application.

[supervisord]
nodaemon=true
logfile=/share/conf/logs/supervisord/supervisord.log

[program:dotnet]
command=/usr/bin/dotnet /app/My.Application.dll

[program:autossh]
command=autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" -i %(ENV_SSH_ARGS_KEYPATH)s -N -L %(ENV_SSH_ARGS_MAPPING)s "-o UserKnownHostsFile=/dev/null" "-o StrictHostKeyChecking=no" %(ENV_SSH_ARGS_HOSTNAME)s
stdout_logfile=/share/conf/logs/supervisord/autossh.log
redirect_stderr=true

The /share folder is actually a file mount so that we can preserve the logs for later analysis. The key file is also hosted on a secured file mount and passed in via the environment variable SSH_ARGS_KEYPATH.

It is probably overkill to use Supervisor like this but it was a lot simpler (and I feel easier for others to follow) than writing lots of shell scripts to achieve the same.

As always your feedback is appreciated.