mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-09-20 13:30:46 +02:00
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
@@ -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
|
||||
Reference in New Issue
Block a user