Authentication
Handled by nginx – zero-subrequest map-based auth, or auth_request to an external service.
Map-based auth (nginx)
Two headers per request:
Two map lookups, both O(1) hash – pure in-process nginx memory, no syscall, no subrequest:
X-Api-Key + X-Api-Secret → $key_ok map → 401 if unknown/wrong
X-Api-Key + request_method + request_uri → $auth_ok map → 403 if out of scope
Keys file
Both maps live in a single file included by nginx:
# keys.conf
# Regenerate on key changes, nginx -s reload to apply
# Authentication: key + secret must match exactly
map "$http_x_api_key:$http_x_api_secret" $key_ok {
default 0;
"PUTFS_abc123:secret1" 1;
"PUTFS_xyz789:secret2" 1;
}
# Authorisation: key + method + URI in one regex
map "$http_x_api_key:$request_method:$request_uri" $auth_ok {
default 0;
"~^PUTFS_abc123:[^:]+:/acme/" 1; # acme, all methods
"~^PUTFS_xyz789:(GET|HEAD):/acme/invoices/" 1; # read-only, invoices
}
Method patterns
| Pattern | Meaning |
|---|---|
[^:]+ |
Any method (wildcard) |
(GET|HEAD) |
Read only |
(PUT|DELETE) |
Write only |
GET |
GET only |
Scope examples
map "$http_x_api_key:$request_method:$request_uri" $auth_ok {
# deny any other access
default 0;
# full dataset, all methods
"~^PUTFS_acme_rw:[^:]+:/acme/" 1;
# full dataset, read only
"~^PUTFS_acme_ro:(GET|HEAD):/acme/" 1;
# subfolder within dataset, all methods
"~^PUTFS_invoices:[^:]+:/acme/invoices/" 1;
# upload-only – PUT and DELETE, specific path
"~^PUTFS_upload:(PUT|DELETE):/acme/uploads/" 1;
# global admin – all methods, all paths
"~^PUTFS_admin:[^:]+:/.+" 1;
}
Key management
Issue a key:
KEY_ID="PUTFS_$(openssl rand -hex 8 | tr '[:lower:]' '[:upper:]')"
SECRET="$(openssl rand -hex 32)"
echo "Key ID: $KEY_ID"
echo "Secret: $SECRET"
# Add to keys.conf, then:
nginx -t && nginx -s reload
Revoke a key – remove the entries from keys.conf, then:
Revocation is immediate after reload. Reload is graceful – no connections dropped.
External auth service via auth_request
For dynamic auth (OAuth, LDAP, token validation):
auth_request /auth;
location = /auth {
internal;
proxy_pass http://auth-service/validate;
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
proxy_set_header X-Original-Method $request_method;
}
Returns 200 (allow) or 401/403 (deny). Fires a subrequest per connection – slower than map auth at high concurrency.
S3 SigV4
The S3 API verifies signatures via awssig. Key files in PUTFS_S3_KEYS_DIR – filename is the access key ID, content is the secret(s) with a glob-style path allowlist:
# /keys/s3/AKID_JANE
HJSFHJSAFDJKAKDSJHJKSDF:test-bucket/*
HJSFHJSAFDJKAKDSJHJKSDF:other-bucket/**/*.json
Security considerations
No upload size limit
Default client_max_body_size 0 – no limit. Protect with ZFS quotas (zfs set quota=500G), filesystem quotas, or set client_max_body_size in nginx.
No rate limiting by default
Add limit_req for public deployments. See Nginx config.
Key reload
Map-based keys and S3 key files are loaded at startup. Changes require nginx -s reload (map) or API restart (S3).
Further reading
- nginx
mapmodule – hash table lookups from request variables - nginx
auth_requestmodule – subrequest-based auth delegation