pkg/urn. Action
validation and permission evaluation live in
pkg/rbac, which
delegates resource comparison to pkg/urn instead of defining its own.
Creation actions live on the nearest parent resource that already exists when
the operation runs. For example, create_key applies to the keyspace, while
read_key, update_key, and delete_key apply to concrete key resources or
key-resource patterns. This avoids inventing collection URNs only for create
operations.
Top-level resources are the exception. Their parent is the workspace itself,
which isn’t an expressible resource path in v1. For those actions, use the
top-level collection wildcard as the closest expressible scope. For example,
create_keyspace uses keyspaces/*, and create_role uses rbac/roles/*.
Format
A permission has a URN, a# separator, and an action.
Actions
Actions are explicit operation names. They use lowercase words separated by underscores.*,
which is reserved for the admin:* migration escape hatch and is valid only on
the global resource pattern **. There is no shorter global form: * matches
exactly one path segment everywhere it appears, so only ** covers every
resource in a workspace.
Create actions
Create actions authorize the parent resource that receives the child resource. Handlers must query the parent resource because the child ID may not exist until after the operation succeeds. For example, creating a key in a keyspace uses the keyspace resource:read_keyspace on keyspaces/ks_123. The action name determines which
operation is authorized.
Top-level create actions target the collection wildcard because there is no
workspace resource path to attach the action to:
rbac.U when the builder
represents the parent resource. Use typed actions from pkg/rbac/permissions,
where action structs are split by resource type:
Patterns
Stored permissions can contain resource patterns. Requested resources in audit logs and authorization checks must always use concrete URNs.v1 supports two resource-pattern operators.
| Operator | Meaning |
|---|---|
* | Matches exactly one complete path segment. |
/** | Matches descendants below the preceding path. |
/** operator is valid only at the end of a resource path. It matches the
path before /** and all descendants below that path.
After a resource path uses * for an ID selector, every descendant ID selector
must also use *. A permission can continue through child collection segments,
but it can’t select a specific child under a wildcard parent.
Valid:
Matching rules
Permission evaluation compares a requested{resource, action} tuple with the
permissions assigned to the principal.
A permission matches when all rules are true:
- The version matches.
- The workspace ID matches.
- The resource path matches exactly or by a valid resource pattern.
- The action matches exactly.
/ before
matching wildcards. Rules 2 and 3, workspace and resource matching, are
implemented by pkg/urn; pkg/rbac parses the action suffix and adds rule 4.
For example, this permission:
Descendant grants
Use trailing/** when a permission must apply to any public descendant of a
resource.
This permission grants delete_deployment for deployments below one project:
Common permissions
These examples show how broad and narrow permissions use the same grammar.Delete one deployment
Delete one deployment
Delete deployments in one environment
Delete deployments in one environment
Delete deployments in one project
Delete deployments in one project
Read all keys in one keyspace
Read all keys in one keyspace
Manage one role
Manage one role
Audit logs
Audit logs store concrete resources and actions. They can also store the permission that authorized the action when authorization was evaluated.Migration from tuple permissions
Existing tuple permissions map to resource permissions by expanding the old resource type, resource ID, and action into a URN path. Legacy API permissions map through the keyspace that replaces the API.| Existing permission | Resource permission |
|---|---|
api.*.create_api | unkey:v1:{workspace_id}:keyspaces/*#create_keyspace |
api.{api_id}.read_api | unkey:v1:{workspace_id}:keyspaces/{keyspace_id}#read_keyspace |
api.{api_id}.create_key | unkey:v1:{workspace_id}:keyspaces/{keyspace_id}#create_key |
api.{api_id}.read_key | unkey:v1:{workspace_id}:keyspaces/{keyspace_id}/keys/*#read_key |
api.{api_id}.verify_key | unkey:v1:{workspace_id}:keyspaces/{keyspace_id}/keys/*#verify_key |
identity.*.read_identity | unkey:v1:{workspace_id}:identities/*#read_identity |
ratelimit.*.delete_override | unkey:v1:{workspace_id}:ratelimits/namespaces/*/overrides/*#delete_override |
rbac.*.create_role | unkey:v1:{workspace_id}:rbac/roles/*#create_role |
Invalid examples
These permissions are invalid.| Value | Reason |
|---|---|
unkey:v1:ws_123:keyspaces/ks_123 | Missing the action suffix. |
unkey:v1:ws_123:keyspaces/ks_123.read_keyspace | Uses the old tuple separator. |
unkey:v1:ws_123:keyspaces/ks_123#* | Uses an action wildcard outside the global resource pattern. |
unkey:v1:ws_123:**/deployments/*#delete_deployment | Uses recursive wildcard outside the trailing position. |
unkey:v1:ws_123:projects/proj_123/**/deployments/*#delete_deployment | Uses recursive wildcard in the middle of the path. |
unkey:v1:ws_123:projects/*/apps/app_123#read_app | Selects a specific child under a wildcard parent. |
unkey:v1:ws_123:keyspaces/*/keys#read_key | Does not match a catalog path shape. |

