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¶
adminrole to create, delete, or sweep filter rules.- Any authenticated user with read access to the distribution (subject to
distribution_accessif configured) can list rules and run a preview. - Decide your match strategy up front:
exact(literal name match),glob(shell-style wildcards viafnmatch, e.g.*-internal-*), orregex(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)¶
- If any
allowrules exist for the distribution, a package name must match at least one of them, or it is rejected. - Regardless of the allow outcome, a matching
denyrule 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 namedmyapp-internal-toolstargetingjammyis rejected before the file ever reachespool/.POST /import/fetchfor a matching name is rejected before the artifact is even downloaded.POST /distributions/promotewithto_dist: "jammy"and a matching package name is rejected — the filter is gated on the destination distribution.- Packages already in
jammybefore 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.