Add EnvVarHeaderContentGuard for env-backed header secret validation
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 598
- Forks
- 168
- Avg merge
- 1d 4h
- Merged PRs (30d)
- 86
Description
Problem
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_digeston 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
HeaderContentGuardtransport 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
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. - Keep as a downstream plugin only — we prototyped this in
pulp-service(https://github.com/pulp/pulp-service/pull/1420); workable but duplicates a generic content-guard primitive that belongs in core. - 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
HeaderContentGuardand HTTP proxy behavior.
Acceptance criteria
-
EnvVarHeaderContentGuardmodel, migration, serializer, viewset registered in pulpcore -
permit()Base64-decodes the header, then validates againstos.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)
- Correct Base64-encoded header matching env plaintext → allow
- Unit tests for
permit()edge cases (missing header, invalid Base64, wrong value, env unset/empty, trailing newline in env, non-ASCII UTF-8 secret) - Functional test: guarded distribution denies without header, allows with matching Base64-encoded header when env is set
- Access policy registered for the new viewset (same pattern as
HeaderContentGuard) - OpenAPI / client bindings updated
Related
HeaderContentGuard—pulpcore/app/models/publication.py- Hosted prototype: https://github.com/pulp/pulp-service/pull/1420 (PULP-2257) — implements Base64 decode + UTF-8 byte compare; can be adapted upstream or replaced once core ships this type
- Documentation: https://gitlab.cee.redhat.com/hosted-pulp/pulp-docs/-/blob/main/content-guards.md (Env Var Header Content Guard section)
- Related guard lifecycle discussion: #7990 (deleting guards in use)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reading HeaderContentGuard in pulpcore/app/models/publication.py and reviewing the hosted pulp-service prototype linked in the issue. Trace the existing registration, access-policy, serializer, migration, and test patterns. Done means the new guard is exposed through the API, validates Base64-encoded UTF-8 headers against the environment value, and passes the listed edge-case and functional tests.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python
- Domain
- api, backend, security
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 68/100