12. Credentials via Environment, ${ENV} Interpolation, and Password Files
Status
Accepted
Context
Each array requires a username and password to authenticate the gopowerstore
client (ADR-0003). Storing those secrets as plaintext in config.yaml is poor
practice: the file is often committed, baked into images, or mounted read-only,
and any of those leaks the credential. The exporter needs a way to keep secrets
out of the config file while staying easy to run on bare metal, under systemd,
in Docker Compose, and in Kubernetes — each of which injects secrets
differently (env vars, mounted files, .env files).
Decision
Secrets are resolved from the environment and the filesystem, never required to be inline in the config file:
${ENV}interpolation.utils.ResolveSecretsexpands${VAR}references in each array'sendpoint,username, andpasswordfields viautils.ExpandEnv. A reference to an unset variable is an error, not a silent empty string — misconfiguration fails loudly at startup rather than producing a confusing auth failure later.- Password files. When
passwordis empty andpasswordFileis set, the password is read from that file. This supports Kubernetes/Docker secret mounts and systemd credentials without putting the value in any env var or config field. .envconvenience loading, env-wins precedence.utils.LoadDotEnvloads a.envfile (working directory first, then the config file's directory) before interpolation, so thecp .env.example .envquickstart works for bare-metal and systemd runs, mirroring what Docker Compose does natively. Crucially,godotenv.Loadonly sets variables that are not already present — real environment and secret injection always take precedence and can never be shadowed by a stray.envfile. A missing.envis a no-op. See ADR-0010 for how this composes with reload.- Masking in logs.
ArrayConfig.MaskPasswordrenders a masked form (ab****yz, or fully masked when short) for any log line that references a password. The--tracetransport additionally never logs request/response headers, where Basic-auth credentials and the DELL-EMC-TOKEN live (see ADR-0009 /trace_transport.go).
The resolution is wrapped in models.NewSafeConfig(cfg, utils.ResolveSecrets)
so the resolver re-runs on reload, keeping rotated secrets honored.
Consequences
config.yamlcan be committed and image-baked safely: it carries${VAR}references orpasswordFilepaths, not secrets.- The same config works across deployment models — env vars (
PSTORE1_PASSWORD), mounted secret files, and.envquickstart — without per-environment forks. - Unset
${VAR}references fail fast at startup, surfacing misconfiguration before the first collection cycle rather than as an opaque login error. .envis strictly a convenience fallback; production secret injection via the real environment or a mounted file always wins, so.envcan never accidentally override a deployed credential.- Passwords are masked in normal logs and absent from
--traceoutput, reducing the chance of credential leakage through observability surfaces.