Access policies
Access policies define what authenticated users can do in S3 Lens — which providers, buckets, prefixes, and actions they may use. Policies are built into validated CompiledPolicy rule sets at startup in the s3lens-policy crate; changes require a restart.
See Authentication for OIDC setup and identity mapping. For route-to-action mapping and permission endpoints, see Authorization reference.
How authorization works
Section titled “How authorization works”When policy.enabled is true (and auth.enabled is true):
- Every API request must include a valid session
- Middleware resolves the caller’s policies and roles once per request into Grants
- Handlers check the requested operation against allow and deny rules
- Explicit deny beats allow; if nothing matches, access is denied
When policy.enabled is false, authenticated users can perform any action. When auth.enabled is false, all requests are allowed (local development only).
Policy format
Section titled “Policy format”A resource is a path — provider/bucket/prefix — and scope is implied by depth:
on: selector |
Covers |
|---|---|
"*" |
everything — all providers, buckets, and objects |
"prod" |
provider prod, all its buckets, and all their objects |
"prod/media" |
that bucket and every object in it |
"prod/media/uploads/*" |
only keys under uploads/ in that bucket |
"prod/team-*" |
every team-* bucket on prod, and their objects |
policy: enabled: true defaults: true # built-ins: viewer, editor, admin
policies: garage-viewer: allow: - actions: [providers:read, buckets:read, objects:read] on: "garage-local"
superuser: allow: - actions: ["*"] on: "*"
media-editor: allow: - actions: [objects:read, objects:write, objects:presign] on: "garage-local/media" deny: - actions: [objects:delete] on: "garage-local/media"
team-a-space: allow: - actions: [buckets:create, buckets:delete] on: "prod/team-a-*"
uploader: allow: - actions: [providers:read] on: "garage-local" - actions: [buckets:read] on: "garage-local/demo" - actions: [objects:read, objects:write] on: "garage-local/demo/uploads/*"
legacy-readonly: iam: file: policies/legacy-readonly.json
inline-iam: iam: inline: | { "Version": "2012-10-17", "Statement": [{ "Effect": "Allow", "Action": ["s3:GetObject"], "Resource": "arn:aws:s3:::demo/public/*" }] }Actions
Section titled “Actions”Use canonical names only plus "*" for all actions. Word aliases (read, write, delete, admin) are rejected at startup with an error listing valid actions.
| Action | What it allows |
|---|---|
providers:read |
See a provider in listings |
buckets:read |
See a bucket in listings |
buckets:create |
Create buckets (scoped by bucket pattern in on:) |
buckets:delete |
Delete buckets |
buckets:policy |
Read, replace, and remove native S3 bucket policies |
objects:read |
List, download, bulk download |
objects:write |
Upload, copy, rename |
objects:delete |
Delete and bulk delete |
objects:presign |
Mint presigned URLs (composite — see below) |
Built-in defaults
Section titled “Built-in defaults”When defaults: true, S3 Lens registers three named policies using the same format as user policies:
| Policy | Grants |
|---|---|
viewer |
providers:read, buckets:read, objects:read on "*" |
editor |
viewer actions plus objects:write, objects:delete, objects:presign, buckets:create, buckets:policy on "*" |
admin |
actions: ["*"] on "*" |
Set defaults: false to omit built-ins and define every policy yourself.
Visibility is explicit
Section titled “Visibility is explicit”Providers and buckets appear in listings only when providers:read / buckets:read cover them. Object-only grants do not imply listing visibility — include the read grants in your recipes.
Boilerplate for prefix-scoped uploaders (MinIO/AWS-style):
allow: - actions: [providers:read] on: "garage-local" # provider appears in provider list - actions: [buckets:read] on: "garage-local/demo" # bucket appears in bucket list - actions: [objects:read, objects:write] on: "garage-local/demo/uploads/*"Prefix-scoped object grants still allow entering a bucket’s object listing; results are filtered per key (key_readable). Deep links to allowed keys work even without buckets:read.
Deny wins; bucket scope covers objects
Section titled “Deny wins; bucket scope covers objects”A deny on on: "prod/media" blocks object writes and deletes in that bucket even when another policy would allow them. Deny wins across all bound compiled policies — the first matching deny short-circuits.
Bucket create/delete by name pattern
Section titled “Bucket create/delete by name pattern”buckets:create and buckets:delete match against the bucket name in the selector’s bucket segment. Use trailing wildcards for multi-tenant self-service:
team-a-space: allow: - actions: [buckets:create, buckets:delete] on: "prod/team-a-*" # may create/delete team-a-docs, team-a-media, …Reserve a namespace with deny:
deny: - actions: [buckets:create] on: "prod/prod-*" # block buckets named prod-*Presign is composite
Section titled “Presign is composite”objects:presign is a capability modifier, not a superset of read or write:
- Presigned download — route requires
objects:presignandobjects:readon the key - Presigned upload — requires
objects:presignandobjects:writeon the key
A deny on objects:read blocks presigned downloads even when objects:presign is allowed.
Wildcards
Section titled “Wildcards”"*"as a full segment matches anything- Trailing
"team-*"matches prefix patterns on bucket or provider names - Mid-segment wildcards like
"log*s"are startup errors, never silent no-match
Prefix trap: "uploads/*" covers keys under uploads/; "uploads*" (no slash) is a string prefix that also matches uploads-evil.txt — always use the slash form for directory prefixes.
Roles and bindings
Section titled “Roles and bindings”roles: editor: policies: [garage-viewer, media-editor] contributor: policies: [uploader]
auth: bindings: - groups: [s3lens-editors] role: editor - subjects: [alice] policies: [uploader]Roles are flat — a role bundles policies, never other roles.
IAM JSON policies
Section titled “IAM JSON policies”IAM JSON is an alternate policy body under iam: file or iam: inline (not a separate config section). Supported: Version: 2012-10-17, Allow/Deny, Action, Resource. Unsupported fields fail at startup:
| Field | Status |
|---|---|
Condition |
Rejected |
Principal |
Rejected |
NotAction |
Rejected |
NotResource |
Rejected |
Resource ARNs map to on: selectors (arn:aws:s3:::bucket → bucket scope; arn:aws:s3:::bucket/prefix/* → object prefix scope).
Evaluation summary
Section titled “Evaluation summary”- Deny anywhere wins across all bound compiled policies
PolicyEngine+ request-scopedGrants(resolved once in middleware)- Permission endpoints use a single
allowed_actionspass per resource context