Files
seaweedfs/test/s3/iam
895d49b55b s3/iam: manage roles through the IAM API, with an opt-in persistent role store (#11522)
* s3/iam: manage roles through the IAM API, with an opt-in persistent role store

Roles could only come from the IAM config file: the S3 server pinned the
role store to memory and the embedded IAM API had no role actions, so a
role could not be created, retrusted or revoked without editing the file
and restarting every gateway.

Role store
- Read the `roleStore` key (the IAMConfig field already existed). With an
  IAM config file the default stays memory; with none it is the filer, as
  for OIDC providers, so zero-config clusters keep runtime-created roles.
- Roles from the IAM config file never go into a persistent role store,
  which outlives the file and may be shared by S3 servers with different
  files. They are served from memory beneath the store, as OIDC providers
  are: a stored role of the same name takes precedence, and deleting it
  restores the file's. A config-file role cannot be changed or deleted
  through the API (UnmodifiableEntity), and removing one from the file
  removes it at the next start. An in-memory store holds them as records,
  as before. They have no creation time, so CreateDate is omitted rather
  than reporting when this server started. SetRoleStore installs a store
  the same way, so a store set after startup keeps the config-file roles,
  as SetOIDCProviderStore does for providers.
- Watch /etc/iam/roles and drop the cached role definitions on change. The
  cached filer store otherwise serves a peer's stale role for up to its 5m
  TTL, which keeps a revoked trust policy in force on the other gateways.
- Role stores wrap ErrRoleNotFound for a missing role; the filer store
  used to report any failed lookup as "role not found". CreateRole proceeds
  only on a confirmed absence, so an unreadable store cannot let it write
  over an existing role.

IAM actions
- CreateRole, GetRole, ListRoles, DeleteRole, UpdateAssumeRolePolicy,
  AttachRolePolicy, DetachRolePolicy, ListAttachedRolePolicies. The reads
  are allowed in read-only mode.
- A role defined in the config file is reloaded from it at every start, so
  changing or deleting it through the API is refused (UnmodifiableEntity)
  rather than silently reverted.
- DeleteRole with policies attached is refused (DeleteConflict), as on AWS.
- Role names follow AWS's rules ([\w+=,.@-]{1,64}); a role is stored as
  <name>.json in the filer, so this also keeps a name from leaving the role
  store's directory. At most 10 managed policies per role (AWS's default
  quota; MaxManagedPoliciesPerUser is 10 too), LimitExceeded beyond.
- DeletePolicy is refused (DeleteConflict) while a role attaches the
  policy, as it already is for users and groups: roles attach policies by
  name, so a policy created later under the deleted one's name would
  otherwise take effect on the role.
- Role paths other than "/" and role tags are not stored, so they are
  refused rather than dropped.

Role IDs and sessions
- Roles get a unique RoleId when first stored (random, AWS AROA form),
  kept across updates; a config-file role gets a stable ID derived from its
  name, since it is created again at every start.
- Sessions issued through AssumeRoleWithWebIdentity, AssumeRoleWithCredentials
  and AssumeRole carry the role's ID (claim "rid"), and a request under a role
  whose current ID differs is denied. Resolving a session's policies by role
  name let a session outlive its role: once a role was deleted, a role later
  created under the same name — with a different trust policy and different
  policies — revived every unexpired session of the old one with the new
  role's permissions. Sessions issued before this change carry no ID and are
  unaffected until they expire.

Integration test (test/s3/iam, run with `make start-services`):
TestWebIdentityWithProviderAndRoleManagedThroughIAMAPI configures an OIDC
provider, a managed policy and a role entirely through the IAM API against a
JWKS served by the test, then checks the trusted subject gets credentials
scoped to the attached policy; another subject, a token signed by another
key, an unsigned token and a token for another audience are refused; and UpdateAssumeRolePolicy moves the
trust at once.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* s3/iam: bind every role session to its role, and change roles atomically

Review follow-ups.

Session binding
- The role-ID check ran only when a session carried no policy names, and
  AssumeRole embeds the role's attached policies, so those sessions kept
  their permissions after the role was deleted or recreated. The check
  now runs for every session carrying a role ID, before policy selection.
- A named role that cannot be resolved at issuance gets no session,
  instead of one with no role ID (which nothing binds).
- A config-file role's ID is derived from its name and trust policy, not
  the name alone: a different role put in the file under the same name
  gets a new ID, while an unchanged role keeps its sessions across restarts.

Role writes
- RoleStore gains UpdateRole, a read-modify-write that lands only if the
  role is unchanged since the read, and otherwise re-reads and retries. The
  filer store uses the filer's write conditions (IF_NOT_EXISTS for a new
  role, IF_ENTRY_EQUAL otherwise). CreateRole, UpdateAssumeRolePolicy and
  Attach/DetachRolePolicy all go through it, so two gateways no longer
  overwrite each other's changes, a change racing a delete no longer
  writes the role back, and of two concurrent creates one gets
  EntityAlreadyExists.
- The filer store's ListRoles pages past 1,000 entries and fails on a
  broken stream instead of returning what arrived, so DeletePolicy's
  attachment check sees every role. ListRoles skips a role deleted between
  listing and reading it.
- CreateRole validates first; a failed write is ServiceFailure, not
  InvalidInput. Any Tags.* parameter is refused, not only the first key.
- ExecuteAction's skipPersist covers the S3ApiConfiguration only; the
  comment now says so. Role and OIDC provider actions write their own stores.

Each fix has a test that fails without it. Against a real filer with two
gateways, concurrent AttachRolePolicy calls lost 1-4 of 8 attachments per
run before this change and none after.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* s3/iam: one role snapshot per decision; DeleteRole is atomic; watch a custom role store path

Review follow-ups.

- Authorization evaluates the policies of the role definition the session's
  binding was checked against, instead of reading the role again: a role
  replaced in between cannot lend a session its policies.
- AssumeRole and AssumeRoleWithLDAPIdentity issue the session from the
  definition whose trust admits the caller (IAMManager.ResolveRoleForPrincipal),
  and take its ID, duration cap and embedded policies from that same
  definition. A role replaced after the caller's trust check by one that does
  not trust the caller now yields AccessDenied, not a session bound to the
  replacement.
- A RoleUpdate that returns nil deletes the role, on the same condition as a
  write: the filer store deletes with ObjectTransaction on IF_ENTRY_EQUAL,
  routed and locked like the conditional CreateEntry. DeleteRole decides
  against the role it deletes, so a policy attached meanwhile on another
  server is a DeleteConflict, and a delete never removes a role written
  after its check.
- S3 servers watch the role store's configured basePath, not only
  /etc/iam/roles, so a custom path also drops peers' cached roles on change.

Each has a test that fails without it. Live against a real filer: DeleteRole
refuses while a policy is attached and removes the entry once detached; all
test/s3/iam CI stages pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* s3/iam: state which roles DeletePolicy's attachment check can see

RolesAttachingPolicy sees the stored roles and this server's config-file
roles. A role defined only in another server's IAM config file is invisible
to it, so a config-file role that attaches a managed policy is protected
only on the servers whose file defines it. The doc comment now says so and
how to avoid it: keep such roles in every server's file, or attach only
config-file policies to config-file roles.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

* iam: note that a role store set after startup is not watched for peer changes

S3 servers build their metadata watch list once, at startup, from the role
store installed then. SetRoleStore's doc now says that a filer-backed store
installed later with a different basePath is not watched, so peers' changes
to it reach this server's cached roles only when the cache expires.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-30 20:45:47 +08:00
..
2026-08-17 15:39:20 -07:00
2025-08-30 11:15:48 -07:00
2025-08-30 11:15:48 -07:00
2026-03-09 11:54:32 -07:00
2026-08-17 15:39:20 -07:00
2025-10-13 18:05:17 -07:00
2025-10-13 18:05:17 -07:00

SeaweedFS S3 IAM Integration Tests

This directory contains comprehensive integration tests for the SeaweedFS S3 API with Advanced IAM (Identity and Access Management) system integration.

Overview

Important: The STS service uses a stateless JWT design where all session information is embedded directly in the JWT token. No external session storage is required.

The S3 IAM integration tests validate the complete end-to-end functionality of:

  • JWT Authentication: OIDC token-based authentication with S3 API
  • Policy Enforcement: Fine-grained access control for S3 operations
  • Stateless Session Management: JWT-based session token validation and expiration (no external storage)
  • Role-Based Access Control (RBAC): IAM roles with different permission levels
  • Bucket Policies: Resource-based access control integration
  • Multipart Upload IAM: Policy enforcement for multipart operations
  • Contextual Policies: IP-based, time-based, and conditional access control
  • Presigned URLs: IAM-integrated temporary access URL generation

Test Architecture

Components Tested

  1. S3 API Gateway - SeaweedFS S3-compatible API server with IAM integration
  2. IAM Manager - Core IAM orchestration and policy evaluation
  3. STS Service - Security Token Service for temporary credentials
  4. Policy Engine - AWS IAM-compatible policy evaluation
  5. Identity Providers - OIDC and LDAP authentication providers
  6. Policy Store - Persistent policy storage using SeaweedFS filer

Test Framework

  • S3IAMTestFramework: Comprehensive test utilities and setup
  • Mock OIDC Provider: In-memory OIDC server with JWT signing
  • Service Management: Automatic SeaweedFS service lifecycle management
  • Resource Cleanup: Automatic cleanup of buckets and test data

Test Scenarios

1. Authentication Tests (TestS3IAMAuthentication)

  • ✅ Valid JWT Token: Successful authentication with proper OIDC tokens
  • ✅ Invalid JWT Token: Rejection of malformed or invalid tokens
  • ✅ Expired JWT Token: Proper handling of expired authentication tokens

2. Policy Enforcement Tests (TestS3IAMPolicyEnforcement)

  • ✅ Read-Only Policy: Users can only read objects and list buckets
  • ✅ Write-Only Policy: Users can only create/delete objects but not read
  • ✅ Admin Policy: Full access to all S3 operations including bucket management

3. Session Expiration Tests (TestS3IAMSessionExpiration)

  • ✅ Short-Lived Sessions: Creation and validation of time-limited sessions
  • ✅ Manual Expiration: Testing session expiration enforcement
  • ✅ Expired Session Rejection: Proper access denial for expired sessions

4. Multipart Upload Tests (TestS3IAMMultipartUploadPolicyEnforcement)

  • ✅ Admin Multipart Access: Full multipart upload capabilities
  • ✅ Read-Only Denial: Rejection of multipart operations for read-only users
  • ✅ Complete Upload Flow: Initiate → Upload Parts → Complete workflow

5. Bucket Policy Tests (TestS3IAMBucketPolicyIntegration)

  • ✅ Public Read Policy: Bucket-level policies allowing public access
  • ✅ Explicit Deny Policy: Bucket policies that override IAM permissions
  • ✅ Policy CRUD Operations: Get/Put/Delete bucket policy operations

6. Contextual Policy Tests (TestS3IAMContextualPolicyEnforcement)

  • 🔧 IP-Based Restrictions: Source IP validation in policy conditions
  • 🔧 Time-Based Restrictions: Temporal access control policies
  • 🔧 User-Agent Restrictions: Request context-based policy evaluation

7. Presigned URL Tests (TestS3IAMPresignedURLIntegration)

  • ✅ URL Generation: IAM-validated presigned URL creation
  • ✅ Permission Validation: Ensuring users have required permissions
  • 🔧 HTTP Request Testing: Direct HTTP calls to presigned URLs

Quick Start

Prerequisites

  1. Go 1.26+ with modules enabled
  2. SeaweedFS Binary (weed) built with IAM support
  3. Test Dependencies:
    go get github.com/stretchr/testify
    go get github.com/aws/aws-sdk-go
    go get github.com/golang-jwt/jwt/v5
    

Running Tests

Complete Test Suite

# Run all tests with service management
make test

# Quick test run (assumes services running)
make test-quick

Specific Test Categories

# Test only authentication
make test-auth

# Test only policy enforcement  
make test-policy

# Test only session expiration
make test-expiration

# Test only multipart uploads
make test-multipart

# Test only bucket policies
make test-bucket-policy

Development & Debugging

# Start services and keep running
make debug

# Show service logs
make logs

# Check service status
make status

# Watch for changes and re-run tests
make watch

Manual Service Management

If you prefer to manage services manually:

# Start services
make start-services

# Wait for services to be ready
make wait-for-services

# Run tests
make run-tests

# Stop services
make stop-services

Configuration

Test Configuration (test_config.json)

The test configuration defines:

  • Identity Providers: OIDC and LDAP configurations
  • IAM Roles: Role definitions with trust policies
  • IAM Policies: Permission policies for different access levels
  • Policy Stores: Persistent storage configurations for IAM policies and roles

Service Ports

Service Port Purpose
Master 9333 Cluster coordination
Volume 8080 Object storage
Filer 8888 Metadata & IAM storage
S3 API 8333 S3-compatible API with IAM

Environment Variables

# SeaweedFS binary location
export WEED_BINARY=../../../weed

# Service ports (optional)
export S3_PORT=8333
export FILER_PORT=8888  
export MASTER_PORT=9333
export VOLUME_PORT=8080

# Test timeout
export TEST_TIMEOUT=30m

# Log level (0-4)
export LOG_LEVEL=2

Test Data & Cleanup

Automatic Cleanup

The test framework automatically:

  • 🗑️ Deletes test buckets created during tests
  • 🗑️ Removes test objects and multipart uploads
  • 🗑️ Cleans up IAM sessions and temporary tokens
  • 🗑️ Stops services after test completion

Manual Cleanup

# Clean everything
make clean

# Clean while keeping services running
rm -rf test-volume-data/

Extending Tests

Adding New Test Scenarios

  1. Create Test Function:

    func TestS3IAMNewFeature(t *testing.T) {
        framework := NewS3IAMTestFramework(t)
        defer framework.Cleanup()
    
        // Test implementation
    }
    
  2. Use Test Framework:

    // Create authenticated S3 client
    s3Client, err := framework.CreateS3ClientWithJWT("user", "TestRole")
    require.NoError(t, err)
    
    // Test S3 operations
    err = framework.CreateBucket(s3Client, "test-bucket")
    require.NoError(t, err)
    
  3. Add to Makefile:

    test-new-feature: ## Test new feature
    	go test -v -run TestS3IAMNewFeature ./...
    

Creating Custom Policies

Add policies to test_config.json:

{
  "policies": {
    "CustomPolicy": {
      "Version": "2012-10-17",
      "Statement": [
        {
          "Effect": "Allow",
          "Action": ["s3:GetObject"],
          "Resource": ["arn:aws:s3:::specific-bucket/*"],
          "Condition": {
            "StringEquals": {
              "s3:prefix": ["allowed-prefix/"]
            }
          }
        }
      ]
    }
  }
}

Adding Identity Providers

  1. Mock Provider Setup:

    // In test framework
    func (f *S3IAMTestFramework) setupCustomProvider() {
        provider := custom.NewCustomProvider("test-custom")
        // Configure and register
    }
    
  2. Configuration:

    {
      "providers": {
        "custom": {
          "test-custom": {
            "endpoint": "http://localhost:8080",
            "clientId": "custom-client"
          }
        }
      }
    }
    

Troubleshooting

Common Issues

1. Services Not Starting

# Check if ports are available
netstat -an | grep -E "(8333|8888|9333|8080)"

# Check service logs
make logs

# Try different ports
export S3_PORT=18333
make start-services

2. JWT Token Issues

# Verify OIDC mock server
curl http://localhost:8080/.well-known/openid_configuration

# Check JWT token format in logs
make logs | grep -i jwt

3. Permission Denied Errors

# Verify IAM configuration
cat test_config.json | jq '.policies'

# Check policy evaluation in logs  
export LOG_LEVEL=4
make start-services

4. Test Timeouts

# Increase timeout
export TEST_TIMEOUT=60m
make test

# Run individual tests
make test-auth

Debug Mode

Start services in debug mode to inspect manually:

# Start and keep running
make debug

# In another terminal, run specific operations
aws s3 ls --endpoint-url http://localhost:8333

# Stop when done (Ctrl+C in debug terminal)

Log Analysis

# Service-specific logs
tail -f weed-s3.log       # S3 API server
tail -f weed-filer.log    # Filer (IAM storage)  
tail -f weed-master.log   # Master server
tail -f weed-volume.log   # Volume server

# Filter for IAM-related logs
make logs | grep -i iam
make logs | grep -i jwt
make logs | grep -i policy

Performance Testing

Benchmarks

# Run performance benchmarks
make benchmark

# Profile memory usage  
go test -bench=. -memprofile=mem.prof
go tool pprof mem.prof

Load Testing

For load testing with IAM:

  1. Create Multiple Clients:

    // Generate multiple JWT tokens
    tokens := framework.GenerateMultipleJWTTokens(100)
    
    // Create concurrent clients
    var wg sync.WaitGroup
    for _, token := range tokens {
        wg.Add(1)
        go func(token string) {
            defer wg.Done()
            // Perform S3 operations
        }(token)
    }
    wg.Wait()
    
  2. Measure Performance:

    # Run with verbose output
    go test -v -bench=BenchmarkS3IAMOperations
    

CI/CD Integration

GitHub Actions

name: S3 IAM Integration Tests
on: [push, pull_request]

jobs:
  s3-iam-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: actions/setup-go@v3
        with:
          go-version-file: 'go.mod'
      
      - name: Build SeaweedFS
        run: go build -o weed ./main.go
      
      - name: Run S3 IAM Tests  
        run: |
          cd test/s3/iam
          make ci

Jenkins Pipeline

pipeline {
    agent any
    stages {
        stage('Build') {
            steps {
                sh 'go build -o weed ./main.go'
            }
        }
        stage('S3 IAM Tests') {
            steps {
                dir('test/s3/iam') {
                    sh 'make ci'
                }
            }
            post {
                always {
                    dir('test/s3/iam') {
                        sh 'make clean'
                    }
                }
            }
        }
    }
}

Contributing

Adding New Tests

  1. Follow Test Patterns:

    • Use S3IAMTestFramework for setup
    • Include cleanup with defer framework.Cleanup()
    • Use descriptive test names and subtests
    • Assert both success and failure cases
  2. Update Documentation:

    • Add test descriptions to this README
    • Include Makefile targets for new test categories
    • Document any new configuration options
  3. Ensure Test Reliability:

    • Tests should be deterministic and repeatable
    • Include proper error handling and assertions
    • Use appropriate timeouts for async operations

Code Style

  • Follow standard Go testing conventions
  • Use require.NoError() for critical assertions
  • Use assert.Equal() for value comparisons
  • Include descriptive error messages in assertions

Support

For issues with S3 IAM integration tests:

  1. Check Logs: Use make logs to inspect service logs
  2. Verify Configuration: Ensure test_config.json is correct
  3. Test Services: Run make status to check service health
  4. Clean Environment: Try make clean && make test

License

This test suite is part of the SeaweedFS project and follows the same licensing terms.