pulp / pulp/pulpcore

Add EnvVarHeaderContentGuard for env-backed header secret validation

Open
#8,007 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Feature
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:

  1. Read request.headers[header_name]; deny if missing.
  2. Base64-decode the header value; deny if not valid Base64.
  3. Decode the result as UTF-8; deny if not valid UTF-8.
  4. Read os.environ[env_var]; deny if unset or empty (after strip).
  5. Compare decoded header bytes to the env value using hmac.compare_digest on UTF-8-encoded strings (supports non-ASCII secrets).
  6. 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

  1. 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.
  2. 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.
  3. Continue using HeaderContentGuard + DB updates — rotation requires updating every guard instance; does not scale for shared secrets across many distributions.
  4. 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)
  • 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

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.