Clone
2
S3 Policy Variables
Chris Lu edited this page 2026-01-29 22:49:54 -08:00

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:

{
  "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}/*"
    }
  ]
}

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 = 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:

{
  "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.