Access rules¶
AWS-format policies know only the * and ? wildcards. Access rules are an additional, per-bucket
layer, simpler to write and with prefix, glob and regex (RE2) patterns. They are evaluated by
the same engine as the IAM policies and the bucket policy:
- an applicable
denyin any source (IAM policies, bucket policy, rules) → denied; - otherwise an
allowfrom at least one source → allowed; - otherwise denied.
Rules never override Object Lock or the restrictions of the inline policies of access keys. They are
managed from the Access rules tab of the bucket in the console, with nexoctl access or from the Admin
API (/api/v1/buckets/{bucket}/access-rules), and take effect immediately on every node.
Anatomy of a rule¶
| Field | Values |
|---|---|
id |
unique name within the bucket |
priority |
evaluation order, lowest first (default 100) |
effect |
allow or deny |
principals |
* (everyone, anonymous users included), user:NAME, group:NAME, accesskey:KEY, distribution:ID |
actions |
read (GET/HEAD), list, write (PUT/POST/multipart), delete, tag, all, or S3 actions (s3:GetObject) |
match |
prefix, glob or regex on the key (optional: without it, the whole bucket) |
| conditions | client IP or CIDR (--source-ip, --not-source-ip), --referer, --host, --secure-transport, time window (--from, --to), --method, --header, named groups of the regex (--capture) |
Patterns¶
| Type | Syntax | Example |
|---|---|---|
prefix |
literal prefix | public/ |
glob |
* (one segment), ** (several segments), ?, [abc], {a,b} |
docs/**/*.pdf, {img,video}/* |
regex |
RE2 (no backreferences: linear time, no ReDoS) | ^users/[a-z0-9_-]+/avatar\.png$ |
- Keys are matched decoded and normalized (Unicode NFC,
--key-normalization):%2e%2e/, double encodings or Unicode variants do not bypass the rules. - A regex is validated when saved (error with position), 1024 characters at most; at most 200
rules per bucket (
--access-rules-max). --ignore-casemakes the match case-insensitive.
Examples¶
Public images, private folders always denied¶
$ nexoctl access rule add local/media --id img-pub --allow --principal '*' --action read \
--regex '^img/[0-9]{4}/.*\.(jpe?g|png)$' --ignore-case
$ nexoctl access rule add local/media --id no-private --deny --principal '*' --action all \
--glob '**/private/**' --priority 5
$ nexoctl access rule list local/media
ID PRIORITY EFFECT PRINCIPALS ACTIONS MATCH
no-private 5 deny * all glob **/private/**
img-pub 100 allow * read regex(i) ^img/[0-9]{4}/.*\.(jpe?g|png)$
| Anonymous request | Outcome |
|---|---|
GET /media/img/2026/a.jpg |
200 |
GET /media/img/2026/b.PNG |
200 (--ignore-case) |
GET /media/img/old/c.jpg |
403 (the year does not have 4 digits) |
GET /media/docs/private/x.pdf |
403 (deny) |
Home directories with a regex¶
Every member of the employees group accesses only home/<their own name>/: the named group user of the
regex must equal ${aws:username}.
nexoctl access rule add local/home --id home --allow --principal 'group:employees' --action all \
--regex '^(?P<user>[^/]+)/' --capture 'user=${aws:username}'
Access from a corporate network, HTTPS only¶
nexoctl access rule add local/report --id lan --allow --principal 'group:analysts' --action read \
--source-ip 10.0.0.0/8 --source-ip 2001:db8::/32 --secure-transport true
The client IP is the real one derived from the trusted proxies (--trusted-proxies); an X-Forwarded-For
header from an untrusted proxy is ignored.
Traffic from the proxy only (origin secret)¶
# on the server: NEXO_SECRET_PROXY_TOKEN=<random value>
nexoctl access rule add local/cdn --id via-proxy --allow --principal '*' --action read \
--header 'X-Origin-Secret: ${secret:proxy-token}'
The ${secret:proxy-token} reference is resolved on every node from NEXO_SECRET_PROXY_TOKEN (or _FILE):
the secret never ends up in the configuration. A reference that cannot be resolved grants nothing.
Time window¶
nexoctl access rule add local/press --id embargo --allow --principal '*' --action read \
--prefix releases/2026-11/ --from 2026-11-01T09:00:00Z
Checking before applying¶
nexoctl access test evaluates requests without executing them, with every source (IAM, bucket policy,
rules):
$ nexoctl access test local/media/img/2026/a.jpg local/media/img/old/c.jpg --principal anonymous --ip 1.2.3.4
img/2026/a.jpg: allowed by access rule `img-pub` (anonymous, s3:GetObject)
rules: allow (matched: img-pub)
img/old/c.jpg: denied (no statement allows it) (anonymous, s3:GetObject)
rules: none
-v shows every rule with the reason why it does not apply; --host, --header, --referer, --secure,
--distribution simulate the rest of the request. The console has the same tester, with the sample keys of
the bucket highlighted.
Order, edit, export¶
nexoctl access rule move local/media --id img-pub --priority 10
nexoctl access rule info local/media --id img-pub
nexoctl access rule add local/media --id img-pub --replace … # replaces the rule
nexoctl access rule remove local/media --id img-pub
nexoctl access export local/media rules.yaml # YAML for GitOps
nexoctl access import local/media-copy rules.yaml
Every change replaces the rules of the bucket atomically and fails if they changed in the meantime.
YAML format (the same as the export):
bucket: media
rules:
- id: img-pub
priority: 100
effect: allow
principals:
- '*'
actions:
- read
match:
type: regex
pattern: ^img/[0-9]{4}/.*\.(jpe?g|png)$
caseInsensitive: true
- id: no-private
priority: 5
effect: deny
principals:
- '*'
actions:
- all
match:
type: glob
pattern: '**/private/**'
Public presets and block public access¶
Preset (nexoctl access public set …) |
Effect |
|---|---|
private |
no anonymous rule |
public-read |
anonymous read of the whole bucket |
public-read-list |
read and list |
public-prefix |
read of a prefix, glob or regex (--prefix, --glob, --regex) |
public-write |
anonymous write (requires --confirm) |
Block public access (per bucket or cluster) rejects or ignores the rules with principal *: see
Sharing and anonymous access.