Skip to content

Add EnvVarHeaderContentGuard for env-backed header secret validation #8007

Description

@decko

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 also expects the request header to be base64-encoded and compares against the decoded value. Deployments that pass the secret as a raw header (common for simple shared-key proxies) cannot use the guard without encoding at the proxy.

We have a concrete need for content access protected by a header whose expected value is read 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

permit() behavior:

  1. Read request.headers[header_name]; deny if missing.
  2. Read os.environ[env_var]; deny if unset or empty (after strip).
  3. Compare header value to env value with hmac.compare_digest on stripped strings.
  4. Deny on mismatch; allow otherwise.

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

  • Raw header compare (no base64 requirement).
  • 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.

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 (PULP-2257: Add EnvVarHeaderContentGuard for env-backed header validation pulp-service#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.

Acceptance criteria

  • EnvVarHeaderContentGuard model, migration, serializer, viewset registered in pulpcore
  • permit() validates header against os.environ[env_var] at request time
  • Missing header, unset/empty env var, and wrong value → deny (403 on content app)
  • Correct header value → allow
  • Unit tests for permit() edge cases (missing header, wrong value, env unset/empty, trailing newline in env)
  • Functional test: guarded distribution denies without header, allows with matching header when env is set
  • Access policy registered for the new viewset (same pattern as HeaderContentGuard)
  • OpenAPI / client bindings updated

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions