Table of Contents
- S3 Policy Variables
- Overview
- Supported Variable Types
- Usage Examples
- User Isolation with ${aws:username}
- Combining Allow and Deny
- JWT Claims in Resources
- GitLab CI Jobs Uploading to Their Own Project Folder
- Variables in Conditions
- Account-Based Access Control
- LDAP Integration
- Principal ARN Parsing
- Best Practices
- 1. Use Explicit Deny for Security
- 2. Test Policies Thoroughly
- 3. Use Conditions for Complex Logic
- 4. Document Variable Expectations
- Troubleshooting
- Related Documentation
S3 Policy Variables
SeaweedFS supports AWS S3 policy variables, allowing you to create dynamic, flexible bucket policies that adapt based on the requester's identity and context.
Overview
Policy variables enable you to write a single policy that applies to multiple users by substituting values at request time. Instead of creating separate policies for each user, you can use variables like ${aws:username} that get replaced with the actual username during policy evaluation.
Supported Variable Types
AWS Context Variables
These variables are automatically extracted from the principal ARN:
| Variable | Description | Available For |
|---|---|---|
${aws:username} |
Username or role name | IAM Users, IAM Roles, Assumed Roles |
${aws:userid} |
User ID | IAM Users, Assumed Roles only |
${aws:principaltype} |
Type of principal | IAM Users, IAM Roles, Assumed Roles |
${aws:PrincipalAccount} |
AWS account ID | IAM Users, Assumed Roles only |
Principal Type Values
IAMUser- For IAM user ARNs (arn:aws:iam::account:user/username)IAMRole- For IAM role ARNs (arn:aws:iam::account:role/rolename)AssumedRole- For assumed role ARNs (arn:aws:sts::account:assumed-role/role/session)
Important
: IAM Roles do NOT have
aws:useridoraws:PrincipalAccountvariables. These are only available for IAM Users and Assumed Roles.
JWT Claim Variables
Access any JWT claim using the ${jwt:claim-name} syntax — claim-name can be any claim present in the JWT issued by the identity provider (e.g., OIDC ID tokens, GitLab CI job tokens). Numeric and boolean claims are stringified, so values like project_id: 301 expand to "301".
| Variable | Description |
|---|---|
${jwt:sub} |
Subject (user ID) from JWT |
${jwt:iss} |
Issuer of the JWT |
${jwt:aud} |
Audience of the JWT |
${jwt:preferred_username} |
Preferred username (common OIDC claim) |
${jwt:email} |
Email address from JWT |
${jwt:<any-other-claim>} |
Any other claim carried by the JWT (e.g., project_path, department, tenant_id) |
If a claim is missing from the JWT, the
${jwt:...}placeholder is left in place so the policy statement does not match — instead of silently expanding to an empty string.
SAML and OIDC Claim Variables
The same pattern applies for SAML and OIDC prefixes: ${saml:<claim>} and ${oidc:<claim>} expand from the corresponding attributes on a validated federated assertion.
LDAP Claim Variables
Access LDAP attributes using the ${ldap:attribute} syntax:
| Variable | Description |
|---|---|
${ldap:username} |
LDAP username |
${ldap:dn} |
LDAP distinguished name |
${ldap:*} |
Any custom LDAP attribute |
S3 Request Variables
Standard S3 condition variables can also be used:
| Variable | Description |
|---|---|
${s3:prefix} |
Prefix parameter from ListBucket |
${aws:SourceIp} |
Source IP address |
${aws:SecureTransport} |
Whether request uses HTTPS |
Usage Examples
User Isolation with ${aws:username}
Allow each user to access only their own folder:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/${aws:username}/*"
}
]
}
When user alice makes a request, the policy evaluates as:
- Resource:
arn:aws:s3:::my-bucket/alice/*
When user bob makes a request:
- Resource:
arn:aws:s3:::my-bucket/bob/*
Combining Allow and Deny
Explicitly allow own folder and deny everything else:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowOwnFolder",
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:aws:s3:::my-bucket/${aws:username}/*"
},
{
"Sid": "DenyOtherFolders",
"Effect": "Deny",
"Principal": "*",
"Action": ["s3:GetObject", "s3:PutObject"],
"NotResource": "arn:aws:s3:::my-bucket/${aws:username}/*"
}
]
}
JWT Claims in Resources
Use JWT claims for dynamic path isolation:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::uploads/${jwt:preferred_username}/*"
}
]
}
GitLab CI Jobs Uploading to Their Own Project Folder
GitLab CI issues ID tokens whose claims include project_path, namespace_path, and project_id (see the GitLab ID token docs). A single policy can confine every project to its own prefix:
{
"name": "GitlabProjectUploadPolicy",
"document": {
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowPutInOwnFolder",
"Effect": "Allow",
"Action": ["s3:PutObject"],
"Resource": ["arn:aws:s3:::softs/${jwt:project_path}/*"]
}
]
}
}
From a .gitlab-ci.yml, request an ID token with aud matching the S3 gateway's client ID and upload with it:
upload:
image: alpine:latest
id_tokens:
SEAWEED_TOKEN:
aud: "seaweedfs-s3"
script:
- apk add -U curl
- |
curl -X PUT --fail-with-body \
-H "Authorization: Bearer $SEAWEED_TOKEN" \
-T some-file.zip \
"https://seaweedfs.example.com/softs/${CI_PROJECT_PATH}/some-file.zip"
Each project can only write under softs/<its-own-project-path>/ because the policy resource expands to the requesting token's project_path claim.
Variables in Conditions
Use variables in condition blocks:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:ListBucket",
"Resource": "arn:aws:s3:::my-bucket",
"Condition": {
"StringLike": {
"s3:prefix": ["${aws:username}/*"]
}
}
}
]
}
Account-Based Access Control
Restrict access to specific AWS accounts:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Deny",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::my-bucket/*",
"Condition": {
"StringNotEquals": {
"aws:PrincipalAccount": ["123456789012"]
}
}
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:*",
"Resource": "arn:aws:s3:::my-bucket/*"
}
]
}
LDAP Integration
Use LDAP attributes for access control:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::data/${ldap:username}/*"
},
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::data/*",
"Condition": {
"StringEquals": {
"ldap:dn": "cn=manager,dc=example,dc=org"
}
}
}
]
}
Principal ARN Parsing
SeaweedFS automatically extracts variables from principal ARNs:
IAM User ARN
arn:aws:iam::123456789012:user/alice
Extracts:
aws:username=aliceaws:userid=aliceaws:principaltype=IAMUseraws:PrincipalAccount=123456789012
IAM User with Path
arn:aws:iam::123456789012:user/division/team/alice
Extracts:
aws:username=alice(last segment)aws:userid=aliceaws:principaltype=IAMUseraws:PrincipalAccount=123456789012
IAM Role ARN
arn:aws:iam::123456789012:role/MyRole
Extracts:
aws:username=MyRoleaws:principaltype=IAMRole
Note: IAM Roles do NOT have aws:userid or aws:PrincipalAccount.
Assumed Role ARN
arn:aws:sts::123456789012:assumed-role/MyRole/session-alice
Extracts:
aws:username=session-alice(session name)aws:userid=session-aliceaws:principaltype=AssumedRoleaws:PrincipalAccount=123456789012
Best Practices
1. Use Explicit Deny for Security
Combine Allow and Deny statements to prevent unauthorized access:
{
"Statement": [
{
"Effect": "Allow",
"Action": "s3:*",
"Resource": "arn:aws:s3:::bucket/${aws:username}/*"
},
{
"Effect": "Deny",
"Action": "s3:*",
"NotResource": "arn:aws:s3:::bucket/${aws:username}/*"
}
]
}
2. Test Policies Thoroughly
Test with different users to ensure variables substitute correctly (ensure $S3_ENDPOINT is set, e.g., export S3_ENDPOINT=http://localhost:8333):
# Test as alice
aws --endpoint-url $S3_ENDPOINT s3 cp file.txt s3://bucket/alice/file.txt --profile alice
# Test as bob
aws --endpoint-url $S3_ENDPOINT s3 cp file.txt s3://bucket/bob/file.txt --profile bob
# Verify isolation
aws --endpoint-url $S3_ENDPOINT s3 ls s3://bucket/alice/ --profile bob # Should fail
3. Use Conditions for Complex Logic
Combine variables with conditions for fine-grained control:
{
"Condition": {
"StringLike": {
"s3:prefix": ["${aws:username}/*"]
},
"StringEquals": {
"aws:principaltype": "IAMUser"
}
}
}
4. Document Variable Expectations
Clearly document which JWT/LDAP claims your policies expect:
{
"Comment": "Requires JWT claims: preferred_username, department",
"Statement": [
{
"Resource": "arn:aws:s3:::data/${jwt:department}/${jwt:preferred_username}/*"
}
]
}
Troubleshooting
Variables Not Substituting
Problem: Variables appear as literal strings in logs
Solution: Ensure the variable exists in the request context. Check:
- Principal ARN format is correct
- JWT claims are present in the token
- LDAP attributes are mapped correctly
Access Denied Despite Matching Path
Problem: User can't access their own folder
Solution: Check for conflicting Deny statements. Remember that Deny always wins:
{
"Statement": [
{
"Effect": "Allow",
"Resource": "arn:aws:s3:::bucket/${aws:username}/*"
},
{
"Effect": "Deny",
"NotResource": "arn:aws:s3:::bucket/${aws:username}/*"
}
]
}
IAM Role Variables Missing
Problem: aws:userid or aws:PrincipalAccount not available for IAM Roles
Solution: This is expected behavior. IAM Roles only have aws:username and aws:principaltype. Use Assumed Roles if you need these variables.
Related Documentation
- Amazon S3 API - S3 API compatibility
- S3 Credentials - Managing S3 credentials
- OIDC Integration - JWT-based authentication
- Amazon IAM API - IAM API support
Introduction
- Quick Start with weed mini
- Simplest S3 Bucket and User Setup
- Components
- Getting Started
- Production Setup
- A typical step‐by‐step example
- Benchmarks
- FAQ
- Applications
API
Configuration
- Replication
- Store file with a Time To Live
- Failover Master Server
- Erasure coding for warm storage
- EC Bitrot Detection
- Server Startup via Systemd
- Environment Variables
Filer
- Filer Setup
- Directories and Files
- File Operations Quick Reference
- Data Structure for Large Files
- Filer Data Encryption
- Filer Commands and Operations
- Filer JWT Use
- TUS Resumable Uploads
Filer Stores
- Filer Cassandra Setup
- Filer Redis Setup
- Super Large Directories
- Path-Specific Filer Store
- Choosing a Filer Store
- Customize Filer Store
Management
Advanced Filer Configurations
- Migrate to Filer Store
- Add New Filer Store
- Filer Store Replication
- Filer Active Active cross cluster continuous synchronization
- Filer as a Key-Large-Value Store
- Path Specific Configuration
- Filer Change Data Capture
- Filer Operation Serialization
FUSE Mount
- FIO benchmark
- fstab and systemd mount
- POSIX Compliance
- Distributed POSIX Locks
- P2P reading in weed mount
WebDAV
SFTP Server
Cloud Drive
- Cloud Drive Benefits
- Cloud Drive Architecture
- Configure Remote Storage
- Mount Remote Storage
- Cache Remote Storage
- Cloud Drive Quick Setup
- Gateway to Remote Object Storage
AWS S3 API
- Amazon S3 API
- Supported APIs vs Minio
- S3 Lifecycle
- S3 Lifecycle vs Volume TTL
- S3 Conditional Operations
- S3 CORS
- S3 Object Lock and Retention
- S3 Object Versioning
- S3 API Benchmark
- S3 API FAQ
- S3 Bucket Quota
- S3 Rate Limiting
- S3 API Audit log
- S3 Nginx Proxy
- Docker Compose for S3
S3 Table Bucket
- S3 Table Bucket
- S3 Table Bucket Commands
- S3 Tables Security
- SeaweedFS Iceberg Catalog
- Iceberg Table Maintenance
Iceberg Integrations
- Spark Iceberg Integration
- Trino Iceberg Integration
- Dremio Iceberg Integration
- DuckDB Iceberg Integration
- Doris Iceberg Integration
- RisingWave Iceberg Integration
- Lakekeeper Iceberg Integration
S3 Authentication & IAM
- S3 Configuration - Start Here
- S3 Credentials (
-s3.config) - OIDC Integration (
-s3.iam.config) - Kubernetes ServiceAccount Authentication (IRSA-style)
- S3 Policy Variables
- S3 Policy Conditions
- S3 Bucket Policies
- Amazon IAM API
- AWS IAM CLI
- weed shell - Shell IAM Commands
Server-Side Encryption
S3 Client Tools
- AWS CLI with SeaweedFS
- s3cmd with SeaweedFS
- rclone with SeaweedFS
- restic with SeaweedFS
- nodejs with Seaweed S3
Machine Learning
HDFS
- Hadoop Compatible File System
- run Spark on SeaweedFS
- run HBase on SeaweedFS
- run Presto on SeaweedFS
- Hadoop Benchmark
- HDFS via S3 connector
Replication and Backup
- Async Replication to another Filer [Deprecated]
- Async Backup
- Async Filer Metadata Backup
- Async Replication to Cloud [Deprecated]
- Kubernetes Backups and Recovery with K8up
Metadata Change Events
Messaging
- Structured Data Lake with SMQ and SQL
- Seaweed Message Queue
- SQL Queries on Message Queue
- SQL Quick Reference
- PostgreSQL-compatible Server weed db
- Pub-Sub to SMQ to SQL
- Kafka to Kafka Gateway to SMQ to SQL
Use Cases
Operations
- System Metrics
- weed shell
- Data Backup
- Deployment to Kubernetes and Minikube
- Deployment with seaweed-up
Rust Volume Server
Advanced
- Large File Handling
- Optimization
- Optimization for Many Small Buckets
- Volume Management
- Tiered Storage
- Cloud Tier
- Cloud Monitoring
- Load Command Line Options from a file
- SRV Service Discovery
- Volume Files Structure
Security
- Security Overview
- Security Configuration
- Cryptography and FIPS Compliance
- Run Blob Storage on Public Internet