Managing Application Secrets¶
Securely managing application secrets, such as API keys, database passwords, and tokens, is a critical aspect of building secure and maintainable software. This guide covers the best practices for handling secrets on the Contain Platform, focusing on how your application should consume them once they are in your Kubernetes namespace.
Why Proper Secret Management Matters¶
It is essential to treat secrets as distinct from your application's source code. You should never:
- Hardcode secrets directly in your code.
- Store secrets in plain text in your Git repository.
- Store secrets in your container images.
Storing secrets in these ways creates significant security risks and makes rotating secrets a difficult and error-prone process.
The Platform's Secrets Management Model¶
The platform provides a secure and automated workflow for managing secrets, which can be summarized in three steps:
- Store: A human or a system places a secret in a central, highly secure vault (e.g. OpenBao). This is the single source of truth.
- Synchronize: The Secrets Service (ExternalDNS)
automatically detects the secret in the vault and creates a corresponding
native Kubernetes
Secretobject in your application's namespace. - Consume: Your application's pod consumes the native Kubernetes
Secretas either an environment variable or a mounted file.
This model works well because your application remains completely decoupled from
the underlying secret store. It only needs to know how to read standard
Kubernetes Secret objects.
How to Consume Secrets in Your Pods¶
There are two primary patterns for consuming secrets in your application's pods. The one you choose depends on your application's needs and configuration.
Pattern 1: As Environment Variables¶
This is the most common pattern and is well-suited for applications that follow the 12-Factor App methodology. The secret's key-value pairs are injected directly into the container as environment variables.
Example¶
In this example, we assume a Kubernetes Secret named my-app-credentials
exists and contains the keys DATABASE_USER and DATABASE_PASSWORD.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-application
spec:
# ... other deployment specs
template:
spec:
containers:
- name: app
image: my-app-image
# This section tells Kubernetes to load all key-value pairs
# from the specified secret as environment variables.
envFrom:
- secretRef:
name: my-app-credentials
Your application can now access process.env.DATABASE_USER (in Node.js) or
os.Getenv("DATABASE_USER") (in Go) to get the secret value.
Pod Restart Required
If the secret value is updated in the Kubernetes Secret, the pod must be
restarted for the new environment variable value to be reflected.
Pattern 2: As Mounted Files¶
Mounting secrets as files in a volume is a more secure and flexible pattern. The secrets are exposed as files inside your container, and their values can often be updated automatically without requiring a pod restart.
Example¶
Using the same my-app-credentials secret, this Deployment mounts each key as
a separate file inside the /etc/secrets directory.
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-application
spec:
# ... other deployment specs
template:
spec:
containers:
- name: app
image: my-app-image
# Mount the volume containing the secrets into the container
volumeMounts:
- name: secret-volume
mountPath: "/etc/secrets"
readOnly: true
# Define the volume that will be populated by the secret's data
volumes:
- name: secret-volume
secret:
secretName: my-app-credentials
Inside the container, you would now have the following file structure:
Your application would then read the contents of
/etc/secrets/DATABASE_PASSWORD to get the secret value.
Next Step: Creating the Secret¶
This guide focuses on how your application should consume a Kubernetes
Secret. To learn the specific steps for creating the ExternalSecret resource
that populates this Secret from the central OpenBao store, please see the
service-specific guide: