Add S3 Policy Variables documentation

Document policy variables feature including:
- AWS context variables (username, userid, principaltype, PrincipalAccount)
- JWT claim variables
- LDAP claim variables
- Principal ARN parsing behavior
- Clarify that IAM Roles do NOT have aws:userid or aws:PrincipalAccount
- Usage examples and best practices
Chris Lu
2026-01-15 17:29:13 -08:00
parent fe9499e9c4
commit 03f9430df6
+379
@@ -0,0 +1,379 @@
# 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:userid` or `aws:PrincipalAccount` variables. These are only available for IAM Users and Assumed Roles.
### JWT Claim Variables
Access any JWT claim using the `${jwt:claim-name}` syntax:
| Variable | Description |
|----------|-------------|
| `${jwt:preferred_username}` | Preferred username from JWT |
| `${jwt:sub}` | Subject (user ID) from JWT |
| `${jwt:email}` | Email address from JWT |
| `${jwt:*}` | Any custom JWT claim |
### 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:
```json
{
"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:
```json
{
"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:
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": "*",
"Action": "s3:PutObject",
"Resource": "arn:aws:s3:::uploads/${jwt:preferred_username}/*"
}
]
}
```
### Variables in Conditions
Use variables in condition blocks:
```json
{
"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:
```json
{
"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:
```json
{
"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` = `alice`
- `aws:userid` = `alice`
- `aws:principaltype` = `IAMUser`
- `aws:PrincipalAccount` = `123456789012`
### IAM User with Path
```
arn:aws:iam::123456789012:user/division/team/alice
```
Extracts:
- `aws:username` = `alice` (last segment)
- `aws:userid` = `alice`
- `aws:principaltype` = `IAMUser`
- `aws:PrincipalAccount` = `123456789012`
### IAM Role ARN
```
arn:aws:iam::123456789012:role/MyRole
```
Extracts:
- `aws:username` = `MyRole`
- `aws: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-alice`
- `aws:principaltype` = `AssumedRole`
- `aws:PrincipalAccount` = `123456789012`
---
## Best Practices
### 1. Use Explicit Deny for Security
Combine Allow and Deny statements to prevent unauthorized access:
```json
{
"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:
```bash
# Test as alice
aws s3 cp file.txt s3://bucket/alice/file.txt --profile alice
# Test as bob
aws s3 cp file.txt s3://bucket/bob/file.txt --profile bob
# Verify isolation
aws s3 ls s3://bucket/alice/ --profile bob # Should fail
```
### 3. Use Conditions for Complex Logic
Combine variables with conditions for fine-grained control:
```json
{
"Condition": {
"StringLike": {
"s3:prefix": ["${aws:username}/*"]
},
"StringEquals": {
"aws:principaltype": "IAMUser"
}
}
}
```
### 4. Document Variable Expectations
Clearly document which JWT/LDAP claims your policies expect:
```json
{
"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:
```json
{
"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