Skip to content

Content filters (package allow/deny)

Content filters let you block or allow packages by name on a per-distribution basis — for example, keeping anything matching *-internal-* out of a public-facing distribution. This is a policy gate on package names, evaluated for every user regardless of role; it is not an access-control layer. For how it relates to distribution_access/machine_access, see The RBAC model.


1. Prerequisites

  • admin role to create, delete, or sweep filter rules.
  • Any authenticated user with read access to the distribution (subject to distribution_access if configured) can list rules and run a preview.
  • Decide your match strategy up front: exact (literal name match), glob (shell-style wildcards via fnmatch, e.g. *-internal-*), or regex (Python regular expression).

Never retroactive by itself

Adding or changing a rule has zero effect on packages already published in the distribution. New uploads/imports/promotions are evaluated against the current rules going forward; existing content is only affected if you explicitly run a sweep (Section 5).


2. Check the current rules

curl -s http://repod.example.com:8000/api/v1/distributions/jammy/filters \
  -H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" | jq .

An empty rules array means the distribution currently accepts any package name.


3. Add a rule

curl -s -X POST http://repod.example.com:8000/api/v1/distributions/jammy/filters \
  -H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "rule_type": "deny",
    "match_type": "glob",
    "pattern": "*-internal-*",
    "description": "Block anything with -internal- in the name from this public distribution"
  }' | jq .
Field Values Meaning
rule_type allow | deny Whether the rule permits or blocks a matching name
match_type exact | glob | regex How pattern is evaluated
pattern string The name pattern to match
description string (optional) Free-text note, defaults to empty

Response (201):

{
  "rule": {
    "id": "...",
    "codename": "jammy",
    "rule_type": "deny",
    "match_type": "glob",
    "pattern": "*-internal-*",
    "description": "Block anything with -internal- in the name from this public distribution",
    "created_by": "admin",
    "created_at": "..."
  }
}

Evaluation order (Katello-style)

  1. If any allow rules exist for the distribution, a package name must match at least one of them, or it is rejected.
  2. Regardless of the allow outcome, a matching deny rule always wins — a name matching both an allow rule and a deny rule is rejected.

A distribution with only deny rules and no allow rules behaves as a simple blocklist: everything is accepted except what a deny rule matches.

Worked example — block anything named *-internal-* from a public distribution:

The POST above already does this. From this point on:

  • POST /upload/ for a package named myapp-internal-tools targeting jammy is rejected before the file ever reaches pool/.
  • POST /import/fetch for a matching name is rejected before the artifact is even downloaded.
  • POST /distributions/promote with to_dist: "jammy" and a matching package name is rejected — the filter is gated on the destination distribution.
  • Packages already in jammy before the rule was added are unaffected until you run a sweep.

4. Preview a rule before committing

Test how a package name would be evaluated against the distribution's current rules without changing anything:

curl -s -X POST http://repod.example.com:8000/api/v1/distributions/jammy/filters/preview \
  -H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "myapp-internal-tools",
    "version": ""
  }' | jq .

version is optional and defaults to an empty string. This is the same evaluation logic used at upload/import/promote time — useful both to test a rule before saving it, and to explain after the fact why a real upload was rejected.


5. Sweep existing content (retroactive removal)

Content filters never touch existing packages automatically. To apply the current rules to what's already published in one distribution:

# Dry run (default) — lists what would be removed, changes nothing
curl -s -X POST "http://repod.example.com:8000/api/v1/distributions/jammy/filters/sweep?dry_run=true" \
  -H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" | jq .

# Actually remove non-compliant packages from this distribution only
curl -s -X POST "http://repod.example.com:8000/api/v1/distributions/jammy/filters/sweep?dry_run=false" \
  -H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" | jq .

dry_run defaults to true — you must explicitly pass dry_run=false to remove anything. The sweep is scoped to the one distribution you called it on; a package present in multiple distributions is only removed from the one you swept.

Sweep is a deliberate, explicit action

There is no automatic re-sweep when a rule changes. If you need existing content cleaned up after adding a deny rule, you must run the sweep yourself — this is by design, to keep filter changes predictable and auditable.


6. Remove a rule

curl -s -X DELETE http://repod.example.com:8000/api/v1/distributions/jammy/filters/<rule_id> \
  -H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx"

Returns 204 on success, 404 if the rule doesn't exist. Removing the last rule reopens the distribution to any package name; it does not restore packages already removed by a prior sweep.


7. Verify it worked

# Confirm the rule is active
curl -s http://repod.example.com:8000/api/v1/distributions/jammy/filters \
  -H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" | jq '.rules'

# Preview a name that should be blocked
curl -s -X POST http://repod.example.com:8000/api/v1/distributions/jammy/filters/preview \
  -H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "myapp-internal-tools"}' | jq .
# → {"allowed": false, "reason": "...", "matched_rule": {...}}

# Preview a name that should pass
curl -s -X POST http://repod.example.com:8000/api/v1/distributions/jammy/filters/preview \
  -H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -d '{"name": "myapp-public"}' | jq .
# → {"allowed": true, ...}

Then confirm end-to-end by attempting a real upload of a blocked name to jammy — it should fail validation before the package reaches pool/, with the rejection reason matching what the preview reported.