You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
HeaderContentGuard stores the expected secret in header_value on the guard record. When the shared secret is managed outside Pulp (injected by a reverse proxy, Kubernetes secret → pod env var, etc.), rotating it requires updating every guard that references that secret.
HeaderContentGuard expects the request header to be Base64-encoded and compares against the decoded value. That encoding keeps arbitrary UTF-8 secrets transport-safe over HTTP headers. We need the same wire-format behavior for env-backed secrets, while still reading the expected plaintext from os.environ at request time so rotation is a deployment/config change rather than a bulk API update across guard records.
Proposed solution
Add an EnvVarHeaderContentGuard content guard type to pulpcore (alongside HeaderContentGuard):
Field
Purpose
header_name
Request header to inspect (operator-configured)
env_var
Name of the environment variable holding the expected secret (plaintext UTF-8)
permit() behavior:
Read request.headers[header_name]; deny if missing.
Base64-decode the header value; deny if not valid Base64.
Decode the result as UTF-8; deny if not valid UTF-8.
Read os.environ[env_var]; deny if unset or empty (after strip).
Compare decoded header bytes to the env value using hmac.compare_digest on UTF-8-encoded strings (supports non-ASCII secrets).
Deny on mismatch; allow otherwise.
Proxies send base64(utf-8(secret)) in the header. The pod environment variable holds the plaintext secret.
API: New viewset at /pulp/api/v3/contentguards/core/envvar_header/ (or equivalent TYPE = "envvar_header" naming), with RBAC/access policy matching HeaderContentGuard.
v1 scope (suggested):
Base64-encoded header on the wire (plaintext secret in env var), matching HeaderContentGuard transport behavior.
No jq_filter (can be added later if needed).
No allowlist fields.
Use case
Any deployment where:
A trusted edge component injects a shared secret header on authorized requests.
The secret is provisioned via environment variables (K8s env, Clowder, etc.).
Operators want rotation without touching guard records in the database.
Secrets may include non-ASCII UTF-8 characters when Base64-encoded on the wire.
Alternatives considered
Extend HeaderContentGuard with an optional env_var field instead of a new type — fewer models, but mixes DB-stored and env-sourced secrets in one guard; harder to document and permission separately.
Continue using HeaderContentGuard + DB updates — rotation requires updating every guard instance; does not scale for shared secrets across many distributions.
Raw header compare (no Base64) — simpler for ASCII-only secrets, but breaks non-ASCII secrets and is less consistent with HeaderContentGuard and HTTP proxy behavior.
Acceptance criteria
EnvVarHeaderContentGuard model, migration, serializer, viewset registered in pulpcore
permit() Base64-decodes the header, then validates against os.environ[env_var] at request time
Missing header, invalid Base64, invalid UTF-8, unset/empty env var, and wrong value → deny (403 on content app)
Problem
HeaderContentGuardstores the expected secret inheader_valueon the guard record. When the shared secret is managed outside Pulp (injected by a reverse proxy, Kubernetes secret → pod env var, etc.), rotating it requires updating every guard that references that secret.HeaderContentGuardexpects the request header to be Base64-encoded and compares against the decoded value. That encoding keeps arbitrary UTF-8 secrets transport-safe over HTTP headers. We need the same wire-format behavior for env-backed secrets, while still reading the expected plaintext fromos.environat request time so rotation is a deployment/config change rather than a bulk API update across guard records.Proposed solution
Add an
EnvVarHeaderContentGuardcontent guard type to pulpcore (alongsideHeaderContentGuard):header_nameenv_varpermit()behavior:request.headers[header_name]; deny if missing.os.environ[env_var]; deny if unset or empty (after strip).hmac.compare_digeston UTF-8-encoded strings (supports non-ASCII secrets).Proxies send
base64(utf-8(secret))in the header. The pod environment variable holds the plaintext secret.API: New viewset at
/pulp/api/v3/contentguards/core/envvar_header/(or equivalentTYPE = "envvar_header"naming), with RBAC/access policy matchingHeaderContentGuard.v1 scope (suggested):
HeaderContentGuardtransport behavior.jq_filter(can be added later if needed).Use case
Any deployment where:
env, Clowder, etc.).Alternatives considered
HeaderContentGuardwith an optionalenv_varfield instead of a new type — fewer models, but mixes DB-stored and env-sourced secrets in one guard; harder to document and permission separately.pulp-service(PULP-2257: Add EnvVarHeaderContentGuard for env-backed header validation pulp-service#1420); workable but duplicates a generic content-guard primitive that belongs in core.HeaderContentGuard+ DB updates — rotation requires updating every guard instance; does not scale for shared secrets across many distributions.HeaderContentGuardand HTTP proxy behavior.Acceptance criteria
EnvVarHeaderContentGuardmodel, migration, serializer, viewset registered in pulpcorepermit()Base64-decodes the header, then validates againstos.environ[env_var]at request timepermit()edge cases (missing header, invalid Base64, wrong value, env unset/empty, trailing newline in env, non-ASCII UTF-8 secret)HeaderContentGuard)Related
HeaderContentGuard—pulpcore/app/models/publication.py