Production Secrets Management in OpenTelemetry .NET
In OpenTelemetry .NET, secrets management in production environment is crucial to ensure the security and integrity of sensitive information. This documentation outlines the strategy for storing and managing secrets when using OpenTelemetry with Docker in a production setup.
Environment Variables
A common practice to manage secrets in production is through the use of environment variables. This is employed in the docker-compose.yml
file, where various services are defined, including the web API and worker service. The sensitive information such as service credentials can be injected into containers at runtime via environment variables, making them accessible to the applications without hardcoding them.
services:
webapi:
environment:
- ASPNETCORE_ENVIRONMENT=Development
- RABBITMQ_HOSTNAME=rabbitmq
- RABBITMQ_DEFAULT_USER=guest
- RABBITMQ_DEFAULT_PASS=guest
- ZIPKIN_HOSTNAME=zipkin
For production environments, it is advisable to leverage secrets management tools or systems to populate these variables securely. For example, you can utilize tools like Docker Secrets, Kubernetes Secrets, or Azure Key Vault, depending on your infrastructure.
Using Docker Secrets
If deploying your application with Docker Swarm, Docker Secrets can be used to manage sensitive data. It’s essential to create secrets that can be referenced in the docker-compose.yml
. An example of creating and using a secret for RabbitMQ credentials can be like so:
Create the Secret:
echo "my_secure_password" | docker secret create rabbitmq_secret -
Reference it in the
docker-compose.yml
:services: webapi: secrets: - rabbitmq_secret secrets: rabbitmq_secret: external: true
Access it in your Application: Inside your application, you can access the secrets typically from the file system at
/run/secrets/rabbitmq_secret
.
Configuration Files and Jinja Templates
For more complex configurations, sometimes Jinja templates can be utilized to dynamically populate configuration settings. This is especially useful when you need to substitute sensitive information in configuration files.
Example of a Jinja template for configuration:
RABBITMQ_DEFAULT_USER={{ lookup('env', 'RABBITMQ_USER') }}
RABBITMQ_DEFAULT_PASS={{ lookup('env', 'RABBITMQ_PASS') }}
The rendered configuration file would use environment variables or other secret management patterns to populate these fields.
Container Entrypoint Script
Another method to manage secrets effectively can involve using an entrypoint script to pull secrets from a secure location at container startup, setting them as environment variables. Below is a simple shell script that simulates this:
#!/bin/sh
# entrypoint.sh
RABBITMQ_USER=$(cat /run/secrets/rabbitmq_user)
export RABBITMQ_DEFAULT_USER=$RABBITMQ_USER
RABBITMQ_PASS=$(cat /run/secrets/rabbitmq_pass)
export RABBITMQ_DEFAULT_PASS=$RABBITMQ_PASS
exec "$@"
Then, in your Dockerfile
, you must copy this script into the container and set it as the entrypoint:
FROM mcr.microsoft.com/dotnet/aspnet:${SDK_VERSION} AS runtime
WORKDIR /app
COPY --from=build /out ./
COPY entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
Conclusion
By employing the strategies outlined—using environment variables, Docker secrets, Jinja templates for dynamic configurations, and entrypoint scripts—OpenTelemetry .NET applications can manage sensitive information effectively in production environments. It is crucial to keep sensitive data out of code repositories and ensure that access to secrets is secured and restricted to essential applications only.
Sources:
- docker-compose.yml
- Dockerfile