Restrict distribution & machine access¶
By default any authenticated user whose global role permits an action can reach every distribution and every inventory machine. This guide shows how to scope that down to specific roles or groups — for a distribution's package tree (distribution_access) and/or a set of inventory machines (machine_access). For how these two layers combine with the global role system, see The RBAC model; this page only covers the setup steps.
1. Prerequisites¶
- You must be logged in as
admin— bothdistribution_accessandmachine_accessmanagement endpoints are admin-only, and onlyadminbypasses the restrictions once created. - A custom role or group already created, to use as the
principal_id. List existing ones with:Note thecurl -s http://repod.example.com:8000/api/v1/roles \ -H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" | jq . curl -s http://repod.example.com:8000/api/v1/groups \ -H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" | jq .idof the role or group you want to grant access to — that value is theprincipal_idin every request below.
This is opt-in and irreversible-by-omission
A distribution or machine with zero access rules stays fully open. The moment you add the first rule for a given codename or machine/tag, it becomes restricted to only the principals listed in rules for it. Make sure admin accounts (which always bypass) or the intended team's role/group is covered before you add the first rule — there is no separate "default allow" entry to fall back on.
2. Restrict a distribution¶
2.1 Check the current state¶
curl -s http://repod.example.com:8000/api/v1/distributions/jammy/access \
-H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" | jq .
An empty access array means the distribution is currently open to everyone.
2.2 Add an access rule¶
curl -s -X POST http://repod.example.com:8000/api/v1/distributions/jammy/access \
-H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"principal_type": "group",
"principal_id": "grp_a1b2c3d4"
}' | jq .
| Field | Values | Meaning |
|---|---|---|
principal_type |
role | group |
Whether principal_id refers to a custom role or a group |
principal_id |
string | custom_roles.id or groups.id — the id you looked up in step 1 |
Response (201):
{
"access": {
"id": "...",
"codename": "jammy",
"principal_type": "group",
"principal_id": "grp_a1b2c3d4",
"created_by": "admin",
"created_at": "..."
}
}
Worked example — give the security-team group write access to jammy only:
- Look up the group's id:
GET /api/v1/groups→ findsecurity-team, note itsid. - Add one rule for
jammywith thatprincipal_id(as above). No other distribution is affected —distribution_accessrules are per-codename, sonobleandalmalinux9remain open until you add rules for them too. - Any user not in
security-team(and notadmin) now gets404 Not FoundonGET /distributions/jammy/packages,POST /upload/targetingjammy, and promote/migrate operations touchingjammyas either source or destination.
Multiple rules for the same codename combine as a union — any one matching role or group grant is enough. There is no intersection mode.
2.3 Remove an access rule¶
curl -s -X DELETE http://repod.example.com:8000/api/v1/distributions/jammy/access/<entry_id> \
-H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx"
Returns 204 on success, 404 if the entry doesn't exist. Removing the last rule for a codename reopens it to everyone.
3. Restrict a machine or a tag of machines¶
Machine-level access uses a separate table with two independent axes: who gets access (user_principal_type/user_principal_id, same role/group vocabulary as distributions) and which machine(s) the rule applies to (machine_principal_type/machine_principal_id, either tag or client).
3.1 Check the current state¶
curl -s "http://repod.example.com:8000/api/v1/inventory/machine-access" \
-H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" | jq .
Filter by machine principal with query params:
curl -s "http://repod.example.com:8000/api/v1/inventory/machine-access?machine_principal_type=tag&machine_principal_id=prod" \
-H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" | jq .
3.2 Add a machine-access rule¶
curl -s -X POST http://repod.example.com:8000/api/v1/inventory/machine-access \
-H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"user_principal_type": "group",
"user_principal_id": "grp_a1b2c3d4",
"machine_principal_type": "tag",
"machine_principal_id": "prod"
}' | jq .
| Field | Values | Meaning |
|---|---|---|
user_principal_type |
role | group |
Who the rule grants access to |
user_principal_id |
string | custom_roles.id or groups.id |
machine_principal_type |
tag | client |
Whether the rule targets every machine carrying a tag, or one specific machine |
machine_principal_id |
string | The tag name (e.g. prod), or the client's id |
Worked example — give the ops-team group access to all machines tagged prod:
curl -s -X POST http://repod.example.com:8000/api/v1/inventory/machine-access \
-H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{
"user_principal_type": "group",
"user_principal_id": "<ops-team group id>",
"machine_principal_type": "tag",
"machine_principal_id": "prod"
}' | jq .
Once this rule exists, any machine tagged prod is restricted to ops-team members (plus admin). Machines without the prod tag remain open, unless they carry another tag that also has a rule.
Client override replaces tag rules, it never merges with them
If you add a machine_principal_type: "client" rule for one specific machine, that rule set becomes the entire effective rule set for that machine — its tag-derived rules are ignored completely, not combined with the client rule. Without a client-level override, all rules from every tag the machine carries combine as a union (any one matching grant is sufficient). See The RBAC model for the full reasoning behind this choice.
3.3 Remove a machine-access rule¶
curl -s -X DELETE http://repod.example.com:8000/api/v1/inventory/machine-access/<entry_id> \
-H "Authorization: Bearer repod_xxxxxxxxxxxxxxxx"
Returns 204 on success, 404 if the entry doesn't exist.
4. Verify it worked¶
As a user who is not in the granted role/group and not admin:
# Distribution restriction — expect 404, not 403
curl -s -o /dev/null -w "%{http_code}\n" \
http://repod.example.com:8000/api/v1/distributions/jammy/packages \
-H "Authorization: Bearer <token of an unauthorized user>"
# → 404
# Machine restriction — expect the client filtered out of the list, not an error
curl -s http://repod.example.com:8000/api/v1/inventory/clients \
-H "Authorization: Bearer <token of an unauthorized user>" | jq '.items[] | select(.id=="<restricted client id>")'
# → no output — the client is silently omitted, not returned with an error
As a user who is in the granted role/group:
curl -s -o /dev/null -w "%{http_code}\n" \
http://repod.example.com:8000/api/v1/distributions/jammy/packages \
-H "Authorization: Bearer <token of an authorized user>"
# → 200
A 404 on a restricted resource is deliberate and identical to the response for a resource that genuinely doesn't exist — this is the anti-leak design described in The RBAC model. Don't use a 404 here as a signal that you mistyped a codename or client id; check GET /distributions/{codename}/access or GET /inventory/machine-access first to confirm whether a restriction is actually in place.