Files
seaweedFS/weed/s3api/policy_engine/README_POLICY_ENGINE.md
Chris Lu d6d893c8c3 s3: add s3:ExistingObjectTag condition support for bucket policies (#7677)
* s3: add s3:ExistingObjectTag condition support in policy engine

Add support for s3:ExistingObjectTag/<tag-key> condition keys in bucket
policies, allowing access control based on object tags.

Changes:
- Add ObjectEntry field to PolicyEvaluationArgs (entry.Extended metadata)
- Update EvaluateConditions to handle s3:ExistingObjectTag/<key> format
- Extract tag value from entry metadata using X-Amz-Tagging-<key> prefix

This enables policies like:
{
  "Condition": {
    "StringEquals": {
      "s3:ExistingObjectTag/status": ["public"]
    }
  }
}

Fixes: https://github.com/seaweedfs/seaweedfs/issues/7447

* s3: update EvaluatePolicy to accept object entry for tag conditions

Update BucketPolicyEngine.EvaluatePolicy to accept objectEntry parameter
(entry.Extended metadata) for evaluating tag-based policy conditions.

Changes:
- Add objectEntry parameter to EvaluatePolicy method
- Update callers in auth_credentials.go and s3api_bucket_handlers.go
- Pass nil for objectEntry in auth layer (entry fetched later in handlers)

For tag-based conditions to work, handlers should call EvaluatePolicy
with the object's entry.Extended after fetching the entry from filer.

* s3: add tests for s3:ExistingObjectTag policy conditions

Add comprehensive tests for object tag-based policy conditions:

- TestExistingObjectTagCondition: Basic tag matching scenarios
  - Matching/non-matching tag values
  - Missing tags, no tags, empty tags
  - Multiple tags with one matching

- TestExistingObjectTagConditionMultipleTags: Multiple tag conditions
  - Both tags match
  - Only one tag matches

- TestExistingObjectTagDenyPolicy: Deny policies with tag conditions
  - Default allow without tag
  - Deny when specific tag present

* s3: document s3:ExistingObjectTag support and feature status

Update policy engine documentation:

- Add s3:ExistingObjectTag/<tag-key> to supported condition keys
- Add 'Object Tag-Based Access Control' section with examples
- Add 'Feature Status' section with implemented and planned features

Planned features for future implementation:
- s3:RequestObjectTag/<key>
- s3:RequestObjectTagKeys
- s3:x-amz-server-side-encryption
- Cross-account access

* Implement tag-based policy re-check in handlers

- Add checkPolicyWithEntry helper to S3ApiServer for handlers to re-check
  policy after fetching object entry (for s3:ExistingObjectTag conditions)
- Add HasPolicyForBucket method to policy engine for efficient check
- Integrate policy re-check in GetObjectHandler after entry is fetched
- Integrate policy re-check in HeadObjectHandler after entry is fetched
- Update auth_credentials.go comments to explain two-phase evaluation
- Update documentation with supported operations for tag-based conditions

This implements 'Approach 1' where handlers re-check the policy with
the object entry after fetching it, allowing tag-based conditions to
be properly evaluated.

* Add integration tests for s3:ExistingObjectTag conditions

- Add TestCheckPolicyWithEntry: tests checkPolicyWithEntry helper with various
  tag scenarios (matching tags, non-matching tags, empty entry, nil entry)
- Add TestCheckPolicyWithEntryNoPolicyForBucket: tests early return when no policy
- Add TestCheckPolicyWithEntryNilPolicyEngine: tests nil engine handling
- Add TestCheckPolicyWithEntryDenyPolicy: tests deny policies with tag conditions
- Add TestHasPolicyForBucket: tests HasPolicyForBucket method

These tests cover the Phase 2 policy evaluation with object entry metadata,
ensuring tag-based conditions are properly evaluated.

* Address code review nitpicks

- Remove unused extractObjectTags placeholder function (engine.go)
- Add clarifying comment about s3:ExistingObjectTag/<key> evaluation
- Consolidate duplicate tag-based examples in README
- Factor out tagsToEntry helper to package level in tests

* Address code review feedback

- Fix unsafe type assertions in GetObjectHandler and HeadObjectHandler
  when getting identity from context (properly handle type assertion failure)
- Extract getConditionContextValue helper to eliminate duplicated logic
  between EvaluateConditions and EvaluateConditionsLegacy
- Ensure consistent handling of missing condition keys (always return
  empty slice)

* Fix GetObjectHandler to match HeadObjectHandler pattern

Add safety check for nil objectEntryForSSE before tag-based policy
evaluation, ensuring tag-based conditions are always evaluated rather
than silently skipped if entry is unexpectedly nil.

Addresses review comment from Copilot.

* Fix HeadObject action name in docs for consistency

Change 'HeadObject' to 's3:HeadObject' to match other action names.

* Extract recheckPolicyWithObjectEntry helper to reduce duplication

Move the repeated identity extraction and policy re-check logic from
GetObjectHandler and HeadObjectHandler into a shared helper method.

* Add validation for empty tag key in s3:ExistingObjectTag condition

Prevent potential issues with malformed policies containing
s3:ExistingObjectTag/ (empty tag key after slash).
2025-12-09 09:48:13 -08:00

10 KiB

SeaweedFS Policy Evaluation Engine

This document describes the comprehensive policy evaluation engine that has been added to SeaweedFS, providing AWS S3-compatible policy support while maintaining full backward compatibility with existing identities.json configuration.

Overview

The policy engine provides:

  • Full AWS S3 policy compatibility - JSON policies with conditions, wildcards, and complex logic
  • Backward compatibility - Existing identities.json continues to work unchanged
  • Bucket policies - Per-bucket access control policies
  • IAM policies - User and group-level policies
  • Condition evaluation - IP restrictions, time-based access, SSL-only, etc.
  • AWS-compliant evaluation order - Explicit Deny > Explicit Allow > Default Deny

Architecture

Files Created

  1. policy_engine/types.go - Core policy data structures and validation
  2. policy_engine/conditions.go - Condition evaluators (StringEquals, IpAddress, etc.)
  3. policy_engine/engine.go - Main policy evaluation engine
  4. policy_engine/integration.go - Integration with existing IAM system
  5. policy_engine/engine_test.go - Comprehensive tests
  6. policy_engine/examples.go - Usage examples and documentation (excluded from builds)
  7. policy_engine/wildcard_matcher.go - Optimized wildcard pattern matching
  8. policy_engine/wildcard_matcher_test.go - Wildcard matching tests

Key Components

PolicyEngine
├── Bucket Policies (per-bucket JSON policies)
├── User Policies (converted from identities.json + new IAM policies)
├── Condition Evaluators (IP, time, string, numeric, etc.)
└── Evaluation Logic (AWS-compliant precedence)

Backward Compatibility

Existing identities.json (No Changes Required)

Your existing configuration continues to work exactly as before:

{
  "identities": [
    {
      "name": "readonly_user",
      "credentials": [{"accessKey": "key123", "secretKey": "secret123"}],
      "actions": ["Read:public-bucket/*", "List:public-bucket"]
    }
  ]
}

Legacy actions are automatically converted to AWS-style policies:

  • Read:bucket/*s3:GetObject on arn:aws:s3:::bucket/*
  • Write:buckets3:PutObject, s3:DeleteObject on arn:aws:s3:::bucket/*
  • Admins3:* on arn:aws:s3:::*

New Capabilities

1. Bucket Policies

Set bucket-level policies using standard S3 API:

# Set bucket policy
curl -X PUT "http://localhost:8333/bucket?policy" \
     -H "Authorization: AWS access_key:signature" \
     -d '{
       "Version": "2012-10-17",
       "Statement": [
         {
           "Effect": "Allow",
           "Principal": "*",
           "Action": "s3:GetObject",
           "Resource": "arn:aws:s3:::bucket/*"
         }
       ]
     }'

# Get bucket policy
curl "http://localhost:8333/bucket?policy"

# Delete bucket policy
curl -X DELETE "http://localhost:8333/bucket?policy"

2. Advanced Conditions

Support for all AWS condition operators:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::secure-bucket/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": ["192.168.1.0/24", "10.0.0.0/8"]
        },
        "Bool": {
          "aws:SecureTransport": "true"
        },
        "DateGreaterThan": {
          "aws:CurrentTime": "2023-01-01T00:00:00Z"
        }
      }
    }
  ]
}

3. Supported Condition Operators

  • String: StringEquals, StringNotEquals, StringLike, StringNotLike
  • Numeric: NumericEquals, NumericLessThan, NumericGreaterThan, etc.
  • Date: DateEquals, DateLessThan, DateGreaterThan, etc.
  • IP: IpAddress, NotIpAddress (supports CIDR notation)
  • Boolean: Bool
  • ARN: ArnEquals, ArnLike
  • Null: Null

4. Condition Keys

Standard AWS condition keys are supported:

  • aws:CurrentTime - Current request time
  • aws:SourceIp - Client IP address
  • aws:SecureTransport - Whether HTTPS is used
  • aws:UserAgent - Client user agent
  • s3:x-amz-acl - Requested ACL
  • s3:VersionId - Object version ID
  • s3:ExistingObjectTag/<tag-key> - Value of an existing object tag (see example below)
  • And many more...

5. Object Tag-Based Access Control

You can control access based on object tags using s3:ExistingObjectTag/<tag-key>:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*",
      "Condition": {
        "StringEquals": {
          "s3:ExistingObjectTag/status": ["public"]
        }
      }
    }
  ]
}

This allows anonymous access only to objects that have a tag status=public.

Deny access to confidential objects:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*"
    },
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::my-bucket/*",
      "Condition": {
        "StringEquals": {
          "s3:ExistingObjectTag/classification": ["confidential", "secret"]
        }
      }
    }
  ]
}

Supported Operations for Tag-Based Conditions:

Tag-based conditions (s3:ExistingObjectTag/<key>) are evaluated for the following operations:

  • s3:GetObject (GET object)
  • s3:GetObjectVersion (GET object with versionId)
  • s3:HeadObject (HEAD object)

Note: For these conditions to be evaluated, the object must exist and the policy engine re-checks access after fetching the object metadata.

Policy Evaluation

Evaluation Order (AWS-Compatible)

  1. Explicit Deny - If any policy explicitly denies access → DENY
  2. Explicit Allow - If any policy explicitly allows access → ALLOW
  3. Default Deny - If no policy matches → DENY

Policy Sources (Evaluated Together)

  1. Bucket Policies - Stored per-bucket, highest priority
  2. User Policies - Converted from identities.json + new IAM policies
  3. Legacy IAM - For backward compatibility (lowest priority)

Examples

Public Read Bucket

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicRead",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::public-bucket/*"
    }
  ]
}

IP-Restricted Bucket

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": "*",
      "Action": ["s3:GetObject", "s3:PutObject"],
      "Resource": "arn:aws:s3:::secure-bucket/*",
      "Condition": {
        "IpAddress": {
          "aws:SourceIp": "192.168.1.0/24"
        }
      }
    }
  ]
}

SSL-Only Access

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:*",
      "Resource": ["arn:aws:s3:::ssl-bucket/*", "arn:aws:s3:::ssl-bucket"],
      "Condition": {
        "Bool": {
          "aws:SecureTransport": "false"
        }
      }
    }
  ]
}

Integration

For Existing SeaweedFS Users

  1. No changes required - Your existing setup continues to work
  2. Optional enhancement - Add bucket policies for fine-grained control
  3. Gradual migration - Move to full AWS policies over time

For New Users

  1. Start with either identities.json or AWS-style policies
  2. Use bucket policies for complex access patterns
  3. Full feature parity with AWS S3 policies

Testing

Run the policy engine tests:

# Core policy tests
go test -v -run TestPolicyEngine

# Condition evaluator tests  
go test -v -run TestConditionEvaluators

# Legacy compatibility tests
go test -v -run TestConvertIdentityToPolicy

# Validation tests
go test -v -run TestPolicyValidation

Performance

  • Compiled patterns - Regex patterns are pre-compiled for fast matching
  • Cached policies - Policies are cached in memory with TTL
  • Early termination - Evaluation stops on first explicit deny
  • Minimal overhead - Backward compatibility with minimal performance impact

Migration Path

Phase 1: Backward Compatible (Current)

  • Keep existing identities.json unchanged
  • Add bucket policies as needed
  • Legacy actions automatically converted to AWS policies

Phase 2: Enhanced (Optional)

  • Add advanced conditions to policies
  • Use full AWS S3 policy features
  • Maintain backward compatibility

Phase 3: Full Migration (Future)

  • Migrate to pure IAM policies
  • Use AWS CLI/SDK for policy management
  • Complete AWS S3 feature parity

Compatibility

  • Full backward compatibility with existing identities.json
  • AWS S3 API compatibility for bucket policies
  • Standard condition operators and keys
  • Proper evaluation precedence (Deny > Allow > Default Deny)
  • Performance optimized with caching and compiled patterns

The policy engine provides a seamless upgrade path from SeaweedFS's existing simple IAM system to full AWS S3-compatible policies, giving you the best of both worlds: simplicity for basic use cases and power for complex enterprise scenarios.

Feature Status

Implemented

Feature Description
Bucket Policies Full AWS S3-compatible bucket policies
Condition Operators StringEquals, IpAddress, Bool, DateGreaterThan, etc.
aws:SourceIp IP-based access control with CIDR support
aws:SecureTransport Require HTTPS
aws:CurrentTime Time-based access control
s3:ExistingObjectTag/<key> Tag-based access control for existing objects
Wildcard Patterns Support for * and ? in actions and resources
Principal Matching *, account IDs, and user ARNs

Planned

Feature GitHub Issue
s3:RequestObjectTag/<key> For tag conditions on PUT requests
s3:RequestObjectTagKeys Check which tag keys are in request
s3:x-amz-content-sha256 Content hash condition
s3:x-amz-server-side-encryption SSE condition
s3:x-amz-storage-class Storage class condition
Cross-account access Access across different accounts
VPC Endpoint policies Network-level policies

For feature requests or to track progress, see the GitHub Issues.