Environment Variables¶
ShadowDNS can expand process environment variables in string values in shadowdns.yaml. This lets one configuration template describe multiple deployments while keeping selected runtime values outside the file.
Use environment expansion for deployment-specific values such as listen addresses and for secrets only when the destination field is not normally logged. The primary secret use case is ephemeral_api.token.
Warning
Do not inject secrets into fields whose values may appear in normal operational logs, validation messages, metrics, or generated output. Environment expansion prevents values from being disclosed by configuration-load errors, but it does not provide global redaction after a successful load. For secrets, use only fields such as ephemeral_api.token that are not normally logged.
Process environment¶
Set variables in the environment of the ShadowDNS process, then reference them from value-side YAML strings:
export API_HOST='192.0.2.10'
export API_PORT='8053'
export API_ALLOW='192.0.2.0/24'
export API_TOKEN='synthetic-secret'
go run ./cmd/shadowdns \
--config ./shadowdns.yaml \
--dry-run
# shadowdns.yaml
ephemeral_api:
listen: "${API_HOST}:${API_PORT}"
allow:
- "${API_ALLOW}"
token: "${API_TOKEN}"
aliases:
example.com:
members:
- "${BACKUP_DOMAIN:-backup.example.net}"
${NAME} requires a non-empty value. If the variable is unset or empty, configuration loading fails. ${NAME:-default} uses the literal default when the variable is unset or empty. Expansion happens once from left to right: text introduced by an environment value or default is not expanded again.
Use $$ when a string must contain a literal dollar sign before braced text. For example, $${API_TOKEN} loads as the literal ${API_TOKEN}. See shadowdns.yaml for the complete grammar, validation behavior, and diagnostics contract.
Note
Expansion reads the environment of the ShadowDNS process, not the environment of the shell that edits the configuration. When ShadowDNS runs under systemd, a container runtime, or Kubernetes, define variables in that service or workload specification.
Kubernetes: Secret to Pod environment to ConfigMap YAML¶
Keep non-secret configuration structure in a ConfigMap and the bearer token in a Secret. The Pod injects the Secret as an environment variable; the mounted shadowdns.yaml references that variable.
1. Store the token in a Secret¶
stringData is convenient for an example because Kubernetes encodes it when creating the Secret. The resulting Secret is not encrypted merely because it is base64-encoded; enable encryption at rest and restrict RBAC access in real clusters.
apiVersion: v1
kind: Secret
metadata:
name: shadowdns-runtime
namespace: dns-example
stringData:
ephemeral-api-token: "synthetic-secret"
2. Store the configuration template in a ConfigMap¶
Only ephemeral_api.token comes from the Secret. The remaining values are ordinary, non-secret configuration.
apiVersion: v1
kind: ConfigMap
metadata:
name: shadowdns-config
namespace: dns-example
data:
shadowdns.yaml: |
aliases:
example.com:
members:
- backup.example.net
ephemeral_api:
listen: "192.0.2.10:8053"
allow:
- "192.0.2.0/24"
token: "${API_TOKEN}"
3. Inject the Secret and mount the ConfigMap¶
The container receives API_TOKEN through secretKeyRef, while the ConfigMap is mounted as the unified configuration file.
apiVersion: apps/v1
kind: Deployment
metadata:
name: shadowdns
namespace: dns-example
spec:
replicas: 1
selector:
matchLabels:
app: shadowdns
template:
metadata:
labels:
app: shadowdns
spec:
containers:
- name: shadowdns
image: registry.example.org/shadowdns:0.1.0
args:
- "--config"
- "/etc/shadowdns/shadowdns.yaml"
env:
- name: API_TOKEN
valueFrom:
secretKeyRef:
name: shadowdns-runtime
key: ephemeral-api-token
volumeMounts:
- name: config
mountPath: /etc/shadowdns
readOnly: true
volumes:
- name: config
configMap:
name: shadowdns-config
At process startup, Kubernetes constructs the container environment from the Secret. ShadowDNS then reads /etc/shadowdns/shadowdns.yaml and resolves ${API_TOKEN} from that process environment. If the Secret key is missing or resolves to an empty value, the required expression makes startup fail closed.
Updating a Kubernetes Secret¶
Updating a Secret does not change the environment of an already-running container. A SIGHUP asks ShadowDNS to reload its configuration, but the process still sees the old API_TOKEN; SIGHUP alone therefore does not refresh an env-backed Secret.
After changing the Secret, restart the Pods so Kubernetes creates new process environments:
kubectl -n dns-example rollout restart deployment/shadowdns
kubectl -n dns-example rollout status deployment/shadowdns
A ConfigMap mounted as a volume may be updated independently, subject to Kubernetes projection timing. However, any configuration value sourced through Pod environment variables still requires a Pod restart. Plan token rotation as a rollout, not as a SIGHUP-only reload.
Security checklist¶
- Put secrets only in fields that are not normally logged, such as
ephemeral_api.token. - Keep the Secret separate from the ConfigMap; do not copy token values into
shadowdns.yaml. - Restrict Kubernetes RBAC access to Secrets and enable Secret encryption at rest.
- Use
${NAME}for required secrets so missing or empty values fail closed. - Restart Pods after updating env-backed Secrets; do not rely on SIGHUP to refresh the process environment.
- Use synthetic values and RFC-reserved domains and addresses in shared examples and diagnostics.