Browse Source

arn:seaweed => arn:aws

pull/7471/head
chrislu 2 months ago
parent
commit
9d1d2c7b64
  1. 2
      test/s3/iam/README-Docker.md
  2. 2
      test/s3/iam/README.md
  3. 2
      test/s3/iam/STS_DISTRIBUTED.md
  4. 40
      test/s3/iam/iam_config.github.json
  5. 40
      test/s3/iam/iam_config.json
  6. 40
      test/s3/iam/iam_config.local.json
  7. 14
      test/s3/iam/iam_config_distributed.json
  8. 14
      test/s3/iam/iam_config_docker.json
  9. 4
      test/s3/iam/s3_iam_framework.go
  10. 4
      test/s3/iam/s3_iam_integration_test.go
  11. 28
      test/s3/iam/test_config.json
  12. 44
      weed/iam/integration/iam_integration_test.go
  13. 2
      weed/iam/integration/iam_manager.go
  14. 6
      weed/iam/integration/role_store_test.go
  15. 6
      weed/iam/oidc/oidc_provider_test.go
  16. 2
      weed/iam/policy/policy_engine.go
  17. 30
      weed/iam/policy/policy_engine_distributed_test.go
  18. 48
      weed/iam/policy/policy_engine_test.go
  19. 10
      weed/iam/sts/cross_instance_token_test.go
  20. 18
      weed/iam/sts/session_policy_test.go
  21. 4
      weed/iam/sts/sts_service.go
  22. 18
      weed/iam/sts/sts_service_test.go
  23. 6
      weed/iam/sts/token_utils.go
  24. 12
      weed/iam/utils/arn_utils.go
  25. 8
      weed/s3api/auth_credentials.go
  26. 6
      weed/s3api/auth_credentials_test.go
  27. 26
      weed/s3api/s3_end_to_end_test.go
  28. 8
      weed/s3api/s3_iam_middleware.go
  29. 16
      weed/s3api/s3_iam_simple_test.go
  30. 20
      weed/s3api/s3_jwt_auth_test.go
  31. 14
      weed/s3api/s3_multipart_iam_test.go
  32. 56
      weed/s3api/s3_policy_templates.go
  33. 32
      weed/s3api/s3_policy_templates_test.go
  34. 4
      weed/s3api/s3_presigned_url_iam.go
  35. 12
      weed/s3api/s3_presigned_url_iam_test.go
  36. 20
      weed/s3api/s3api_bucket_handlers.go
  37. 104
      weed/s3api/s3api_bucket_policy_arn_test.go
  38. 4
      weed/s3api/s3api_bucket_policy_handlers.go

2
test/s3/iam/README-Docker.md

@ -170,7 +170,7 @@ The `setup_keycloak_docker.sh` script automatically generates `iam_config.json`
{
"claim": "roles",
"value": "s3-admin",
"role": "arn:seaweed:iam::role/KeycloakAdminRole"
"role": "arn:aws:iam::role/KeycloakAdminRole"
}
```

2
test/s3/iam/README.md

@ -257,7 +257,7 @@ Add policies to `test_config.json`:
{
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": ["arn:seaweed:s3:::specific-bucket/*"],
"Resource": ["arn:aws:s3:::specific-bucket/*"],
"Condition": {
"StringEquals": {
"s3:prefix": ["allowed-prefix/"]

2
test/s3/iam/STS_DISTRIBUTED.md

@ -248,7 +248,7 @@ services:
3. User calls SeaweedFS STS AssumeRoleWithWebIdentity
POST /sts/assume-role-with-web-identity
{
"RoleArn": "arn:seaweed:iam::role/S3AdminRole",
"RoleArn": "arn:aws:iam::role/S3AdminRole",
"WebIdentityToken": "eyJ0eXAiOiJKV1QiLCJhbGc...",
"RoleSessionName": "user-session"
}

40
test/s3/iam/iam_config.github.json

@ -35,25 +35,25 @@
{
"claim": "roles",
"value": "s3-admin",
"role": "arn:seaweed:iam::role/KeycloakAdminRole"
"role": "arn:aws:iam::role/KeycloakAdminRole"
},
{
"claim": "roles",
"value": "s3-read-only",
"role": "arn:seaweed:iam::role/KeycloakReadOnlyRole"
"role": "arn:aws:iam::role/KeycloakReadOnlyRole"
},
{
"claim": "roles",
"value": "s3-write-only",
"role": "arn:seaweed:iam::role/KeycloakWriteOnlyRole"
"role": "arn:aws:iam::role/KeycloakWriteOnlyRole"
},
{
"claim": "roles",
"value": "s3-read-write",
"role": "arn:seaweed:iam::role/KeycloakReadWriteRole"
"role": "arn:aws:iam::role/KeycloakReadWriteRole"
}
],
"defaultRole": "arn:seaweed:iam::role/KeycloakReadOnlyRole"
"defaultRole": "arn:aws:iam::role/KeycloakReadOnlyRole"
}
}
}
@ -64,7 +64,7 @@
"roles": [
{
"roleName": "TestAdminRole",
"roleArn": "arn:seaweed:iam::role/TestAdminRole",
"roleArn": "arn:aws:iam::role/TestAdminRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -82,7 +82,7 @@
},
{
"roleName": "TestReadOnlyRole",
"roleArn": "arn:seaweed:iam::role/TestReadOnlyRole",
"roleArn": "arn:aws:iam::role/TestReadOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -100,7 +100,7 @@
},
{
"roleName": "TestWriteOnlyRole",
"roleArn": "arn:seaweed:iam::role/TestWriteOnlyRole",
"roleArn": "arn:aws:iam::role/TestWriteOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -118,7 +118,7 @@
},
{
"roleName": "KeycloakAdminRole",
"roleArn": "arn:seaweed:iam::role/KeycloakAdminRole",
"roleArn": "arn:aws:iam::role/KeycloakAdminRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -136,7 +136,7 @@
},
{
"roleName": "KeycloakReadOnlyRole",
"roleArn": "arn:seaweed:iam::role/KeycloakReadOnlyRole",
"roleArn": "arn:aws:iam::role/KeycloakReadOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -154,7 +154,7 @@
},
{
"roleName": "KeycloakWriteOnlyRole",
"roleArn": "arn:seaweed:iam::role/KeycloakWriteOnlyRole",
"roleArn": "arn:aws:iam::role/KeycloakWriteOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -172,7 +172,7 @@
},
{
"roleName": "KeycloakReadWriteRole",
"roleArn": "arn:seaweed:iam::role/KeycloakReadWriteRole",
"roleArn": "arn:aws:iam::role/KeycloakReadWriteRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -220,8 +220,8 @@
"s3:ListBucket"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
},
{
@ -243,8 +243,8 @@
"s3:*"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
},
{
@ -254,8 +254,8 @@
"s3:ListBucket"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
},
{
@ -277,8 +277,8 @@
"s3:*"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
},
{

40
test/s3/iam/iam_config.json

@ -35,25 +35,25 @@
{
"claim": "roles",
"value": "s3-admin",
"role": "arn:seaweed:iam::role/KeycloakAdminRole"
"role": "arn:aws:iam::role/KeycloakAdminRole"
},
{
"claim": "roles",
"value": "s3-read-only",
"role": "arn:seaweed:iam::role/KeycloakReadOnlyRole"
"role": "arn:aws:iam::role/KeycloakReadOnlyRole"
},
{
"claim": "roles",
"value": "s3-write-only",
"role": "arn:seaweed:iam::role/KeycloakWriteOnlyRole"
"role": "arn:aws:iam::role/KeycloakWriteOnlyRole"
},
{
"claim": "roles",
"value": "s3-read-write",
"role": "arn:seaweed:iam::role/KeycloakReadWriteRole"
"role": "arn:aws:iam::role/KeycloakReadWriteRole"
}
],
"defaultRole": "arn:seaweed:iam::role/KeycloakReadOnlyRole"
"defaultRole": "arn:aws:iam::role/KeycloakReadOnlyRole"
}
}
}
@ -64,7 +64,7 @@
"roles": [
{
"roleName": "TestAdminRole",
"roleArn": "arn:seaweed:iam::role/TestAdminRole",
"roleArn": "arn:aws:iam::role/TestAdminRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -82,7 +82,7 @@
},
{
"roleName": "TestReadOnlyRole",
"roleArn": "arn:seaweed:iam::role/TestReadOnlyRole",
"roleArn": "arn:aws:iam::role/TestReadOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -100,7 +100,7 @@
},
{
"roleName": "TestWriteOnlyRole",
"roleArn": "arn:seaweed:iam::role/TestWriteOnlyRole",
"roleArn": "arn:aws:iam::role/TestWriteOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -118,7 +118,7 @@
},
{
"roleName": "KeycloakAdminRole",
"roleArn": "arn:seaweed:iam::role/KeycloakAdminRole",
"roleArn": "arn:aws:iam::role/KeycloakAdminRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -136,7 +136,7 @@
},
{
"roleName": "KeycloakReadOnlyRole",
"roleArn": "arn:seaweed:iam::role/KeycloakReadOnlyRole",
"roleArn": "arn:aws:iam::role/KeycloakReadOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -154,7 +154,7 @@
},
{
"roleName": "KeycloakWriteOnlyRole",
"roleArn": "arn:seaweed:iam::role/KeycloakWriteOnlyRole",
"roleArn": "arn:aws:iam::role/KeycloakWriteOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -172,7 +172,7 @@
},
{
"roleName": "KeycloakReadWriteRole",
"roleArn": "arn:seaweed:iam::role/KeycloakReadWriteRole",
"roleArn": "arn:aws:iam::role/KeycloakReadWriteRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -220,8 +220,8 @@
"s3:ListBucket"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
},
{
@ -243,8 +243,8 @@
"s3:*"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
},
{
@ -254,8 +254,8 @@
"s3:ListBucket"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
},
{
@ -277,8 +277,8 @@
"s3:*"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
},
{

40
test/s3/iam/iam_config.local.json

@ -39,25 +39,25 @@
{
"claim": "roles",
"value": "s3-admin",
"role": "arn:seaweed:iam::role/KeycloakAdminRole"
"role": "arn:aws:iam::role/KeycloakAdminRole"
},
{
"claim": "roles",
"value": "s3-read-only",
"role": "arn:seaweed:iam::role/KeycloakReadOnlyRole"
"role": "arn:aws:iam::role/KeycloakReadOnlyRole"
},
{
"claim": "roles",
"value": "s3-write-only",
"role": "arn:seaweed:iam::role/KeycloakWriteOnlyRole"
"role": "arn:aws:iam::role/KeycloakWriteOnlyRole"
},
{
"claim": "roles",
"value": "s3-read-write",
"role": "arn:seaweed:iam::role/KeycloakReadWriteRole"
"role": "arn:aws:iam::role/KeycloakReadWriteRole"
}
],
"defaultRole": "arn:seaweed:iam::role/KeycloakReadOnlyRole"
"defaultRole": "arn:aws:iam::role/KeycloakReadOnlyRole"
}
}
}
@ -68,7 +68,7 @@
"roles": [
{
"roleName": "TestAdminRole",
"roleArn": "arn:seaweed:iam::role/TestAdminRole",
"roleArn": "arn:aws:iam::role/TestAdminRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -90,7 +90,7 @@
},
{
"roleName": "TestReadOnlyRole",
"roleArn": "arn:seaweed:iam::role/TestReadOnlyRole",
"roleArn": "arn:aws:iam::role/TestReadOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -112,7 +112,7 @@
},
{
"roleName": "TestWriteOnlyRole",
"roleArn": "arn:seaweed:iam::role/TestWriteOnlyRole",
"roleArn": "arn:aws:iam::role/TestWriteOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -134,7 +134,7 @@
},
{
"roleName": "KeycloakAdminRole",
"roleArn": "arn:seaweed:iam::role/KeycloakAdminRole",
"roleArn": "arn:aws:iam::role/KeycloakAdminRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -156,7 +156,7 @@
},
{
"roleName": "KeycloakReadOnlyRole",
"roleArn": "arn:seaweed:iam::role/KeycloakReadOnlyRole",
"roleArn": "arn:aws:iam::role/KeycloakReadOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -178,7 +178,7 @@
},
{
"roleName": "KeycloakWriteOnlyRole",
"roleArn": "arn:seaweed:iam::role/KeycloakWriteOnlyRole",
"roleArn": "arn:aws:iam::role/KeycloakWriteOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -200,7 +200,7 @@
},
{
"roleName": "KeycloakReadWriteRole",
"roleArn": "arn:seaweed:iam::role/KeycloakReadWriteRole",
"roleArn": "arn:aws:iam::role/KeycloakReadWriteRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -260,8 +260,8 @@
"s3:ListBucket"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
},
{
@ -287,8 +287,8 @@
"s3:*"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
},
{
@ -298,8 +298,8 @@
"s3:ListBucket"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
},
{
@ -325,8 +325,8 @@
"s3:*"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
},
{

14
test/s3/iam/iam_config_distributed.json

@ -40,7 +40,7 @@
"roles": [
{
"roleName": "S3AdminRole",
"roleArn": "arn:seaweed:iam::role/S3AdminRole",
"roleArn": "arn:aws:iam::role/S3AdminRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -63,7 +63,7 @@
},
{
"roleName": "S3ReadOnlyRole",
"roleArn": "arn:seaweed:iam::role/S3ReadOnlyRole",
"roleArn": "arn:aws:iam::role/S3ReadOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -86,7 +86,7 @@
},
{
"roleName": "S3ReadWriteRole",
"roleArn": "arn:seaweed:iam::role/S3ReadWriteRole",
"roleArn": "arn:aws:iam::role/S3ReadWriteRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -137,8 +137,8 @@
"s3:ListBucketVersions"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
}
]
@ -162,8 +162,8 @@
"s3:ListBucketVersions"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
}
]

14
test/s3/iam/iam_config_docker.json

@ -25,7 +25,7 @@
"roles": [
{
"roleName": "S3AdminRole",
"roleArn": "arn:seaweed:iam::role/S3AdminRole",
"roleArn": "arn:aws:iam::role/S3AdminRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -48,7 +48,7 @@
},
{
"roleName": "S3ReadOnlyRole",
"roleArn": "arn:seaweed:iam::role/S3ReadOnlyRole",
"roleArn": "arn:aws:iam::role/S3ReadOnlyRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -71,7 +71,7 @@
},
{
"roleName": "S3ReadWriteRole",
"roleArn": "arn:seaweed:iam::role/S3ReadWriteRole",
"roleArn": "arn:aws:iam::role/S3ReadWriteRole",
"trustPolicy": {
"Version": "2012-10-17",
"Statement": [
@ -122,8 +122,8 @@
"s3:ListBucketVersions"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
}
]
@ -147,8 +147,8 @@
"s3:ListBucketVersions"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
}
]

4
test/s3/iam/s3_iam_framework.go

@ -369,9 +369,9 @@ func (f *S3IAMTestFramework) generateSTSSessionToken(username, roleName string,
sessionId := fmt.Sprintf("test-session-%s-%s-%d", username, roleName, now.Unix())
// Create session token claims exactly matching STSSessionClaims struct
roleArn := fmt.Sprintf("arn:seaweed:iam::role/%s", roleName)
roleArn := fmt.Sprintf("arn:aws:iam::role/%s", roleName)
sessionName := fmt.Sprintf("test-session-%s", username)
principalArn := fmt.Sprintf("arn:seaweed:sts::assumed-role/%s/%s", roleName, sessionName)
principalArn := fmt.Sprintf("arn:aws:sts::assumed-role/%s/%s", roleName, sessionName)
// Use jwt.MapClaims but with exact field names that STSSessionClaims expects
sessionClaims := jwt.MapClaims{

4
test/s3/iam/s3_iam_integration_test.go

@ -410,7 +410,7 @@ func TestS3IAMBucketPolicyIntegration(t *testing.T) {
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject"],
"Resource": ["arn:seaweed:s3:::%s/*"]
"Resource": ["arn:aws:s3:::%s/*"]
}
]
}`, bucketName)
@ -455,7 +455,7 @@ func TestS3IAMBucketPolicyIntegration(t *testing.T) {
"Effect": "Deny",
"Principal": "*",
"Action": ["s3:DeleteObject"],
"Resource": ["arn:seaweed:s3:::%s/*"]
"Resource": ["arn:aws:s3:::%s/*"]
}
]
}`, bucketName)

28
test/s3/iam/test_config.json

@ -164,8 +164,8 @@
"Effect": "Allow",
"Action": ["s3:*"],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
}
]
@ -184,8 +184,8 @@
"s3:GetBucketVersioning"
],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
]
}
]
@ -207,7 +207,7 @@
"s3:ListMultipartUploadParts"
],
"Resource": [
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*/*"
]
}
]
@ -227,7 +227,7 @@
"s3:PutBucketVersioning"
],
"Resource": [
"arn:seaweed:s3:::*"
"arn:aws:s3:::*"
]
}
]
@ -239,8 +239,8 @@
"Effect": "Allow",
"Action": ["s3:*"],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
],
"Condition": {
"IpAddress": {
@ -257,8 +257,8 @@
"Effect": "Allow",
"Action": ["s3:GetObject", "s3:ListBucket"],
"Resource": [
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*"
"arn:aws:s3:::*",
"arn:aws:s3:::*/*"
],
"Condition": {
"DateGreaterThan": {
@ -281,7 +281,7 @@
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:seaweed:s3:::example-bucket/*"
"Resource": "arn:aws:s3:::example-bucket/*"
}
]
},
@ -294,8 +294,8 @@
"Principal": "*",
"Action": ["s3:DeleteObject", "s3:DeleteBucket"],
"Resource": [
"arn:seaweed:s3:::example-bucket",
"arn:seaweed:s3:::example-bucket/*"
"arn:aws:s3:::example-bucket",
"arn:aws:s3:::example-bucket/*"
]
}
]
@ -308,7 +308,7 @@
"Effect": "Allow",
"Principal": "*",
"Action": ["s3:GetObject", "s3:PutObject"],
"Resource": "arn:seaweed:s3:::example-bucket/*",
"Resource": "arn:aws:s3:::example-bucket/*",
"Condition": {
"IpAddress": {
"aws:SourceIp": ["203.0.113.0/24"]

44
weed/iam/integration/iam_integration_test.go

@ -34,23 +34,23 @@ func TestFullOIDCWorkflow(t *testing.T) {
}{
{
name: "successful role assumption with policy validation",
roleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
roleArn: "arn:aws:iam::role/S3ReadOnlyRole",
sessionName: "oidc-session",
webToken: validJWTToken,
expectedAllow: true,
testAction: "s3:GetObject",
testResource: "arn:seaweed:s3:::test-bucket/file.txt",
testResource: "arn:aws:s3:::test-bucket/file.txt",
},
{
name: "role assumption denied by trust policy",
roleArn: "arn:seaweed:iam::role/RestrictedRole",
roleArn: "arn:aws:iam::role/RestrictedRole",
sessionName: "oidc-session",
webToken: validJWTToken,
expectedAllow: false,
},
{
name: "invalid token rejected",
roleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
roleArn: "arn:aws:iam::role/S3ReadOnlyRole",
sessionName: "oidc-session",
webToken: invalidJWTToken,
expectedAllow: false,
@ -113,17 +113,17 @@ func TestFullLDAPWorkflow(t *testing.T) {
}{
{
name: "successful LDAP role assumption",
roleArn: "arn:seaweed:iam::role/LDAPUserRole",
roleArn: "arn:aws:iam::role/LDAPUserRole",
sessionName: "ldap-session",
username: "testuser",
password: "testpass",
expectedAllow: true,
testAction: "filer:CreateEntry",
testResource: "arn:seaweed:filer::path/user-docs/*",
testResource: "arn:aws:filer::path/user-docs/*",
},
{
name: "invalid LDAP credentials",
roleArn: "arn:seaweed:iam::role/LDAPUserRole",
roleArn: "arn:aws:iam::role/LDAPUserRole",
sessionName: "ldap-session",
username: "testuser",
password: "wrongpass",
@ -181,7 +181,7 @@ func TestPolicyEnforcement(t *testing.T) {
// Create a session for testing
ctx := context.Background()
assumeRequest := &sts.AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
RoleArn: "arn:aws:iam::role/S3ReadOnlyRole",
WebIdentityToken: validJWTToken,
RoleSessionName: "policy-test-session",
}
@ -202,35 +202,35 @@ func TestPolicyEnforcement(t *testing.T) {
{
name: "allow read access",
action: "s3:GetObject",
resource: "arn:seaweed:s3:::test-bucket/file.txt",
resource: "arn:aws:s3:::test-bucket/file.txt",
shouldAllow: true,
reason: "S3ReadOnlyRole should allow GetObject",
},
{
name: "allow list bucket",
action: "s3:ListBucket",
resource: "arn:seaweed:s3:::test-bucket",
resource: "arn:aws:s3:::test-bucket",
shouldAllow: true,
reason: "S3ReadOnlyRole should allow ListBucket",
},
{
name: "deny write access",
action: "s3:PutObject",
resource: "arn:seaweed:s3:::test-bucket/newfile.txt",
resource: "arn:aws:s3:::test-bucket/newfile.txt",
shouldAllow: false,
reason: "S3ReadOnlyRole should deny write operations",
},
{
name: "deny delete access",
action: "s3:DeleteObject",
resource: "arn:seaweed:s3:::test-bucket/file.txt",
resource: "arn:aws:s3:::test-bucket/file.txt",
shouldAllow: false,
reason: "S3ReadOnlyRole should deny delete operations",
},
{
name: "deny filer access",
action: "filer:CreateEntry",
resource: "arn:seaweed:filer::path/test",
resource: "arn:aws:filer::path/test",
shouldAllow: false,
reason: "S3ReadOnlyRole should not allow filer operations",
},
@ -261,7 +261,7 @@ func TestSessionExpiration(t *testing.T) {
// Create a short-lived session
assumeRequest := &sts.AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
RoleArn: "arn:aws:iam::role/S3ReadOnlyRole",
WebIdentityToken: validJWTToken,
RoleSessionName: "expiration-test",
DurationSeconds: int64Ptr(900), // 15 minutes
@ -276,7 +276,7 @@ func TestSessionExpiration(t *testing.T) {
allowed, err := iamManager.IsActionAllowed(ctx, &ActionRequest{
Principal: response.AssumedRoleUser.Arn,
Action: "s3:GetObject",
Resource: "arn:seaweed:s3:::test-bucket/file.txt",
Resource: "arn:aws:s3:::test-bucket/file.txt",
SessionToken: sessionToken,
})
require.NoError(t, err)
@ -296,7 +296,7 @@ func TestSessionExpiration(t *testing.T) {
allowed, err = iamManager.IsActionAllowed(ctx, &ActionRequest{
Principal: response.AssumedRoleUser.Arn,
Action: "s3:GetObject",
Resource: "arn:seaweed:s3:::test-bucket/file.txt",
Resource: "arn:aws:s3:::test-bucket/file.txt",
SessionToken: sessionToken,
})
require.NoError(t, err, "Session should still be valid in stateless system")
@ -318,7 +318,7 @@ func TestTrustPolicyValidation(t *testing.T) {
}{
{
name: "OIDC user allowed by trust policy",
roleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
roleArn: "arn:aws:iam::role/S3ReadOnlyRole",
provider: "oidc",
userID: "test-user-id",
shouldAllow: true,
@ -326,7 +326,7 @@ func TestTrustPolicyValidation(t *testing.T) {
},
{
name: "LDAP user allowed by different role",
roleArn: "arn:seaweed:iam::role/LDAPUserRole",
roleArn: "arn:aws:iam::role/LDAPUserRole",
provider: "ldap",
userID: "testuser",
shouldAllow: true,
@ -334,7 +334,7 @@ func TestTrustPolicyValidation(t *testing.T) {
},
{
name: "Wrong provider for role",
roleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
roleArn: "arn:aws:iam::role/S3ReadOnlyRole",
provider: "ldap",
userID: "testuser",
shouldAllow: false,
@ -442,8 +442,8 @@ func setupTestPoliciesAndRoles(t *testing.T, manager *IAMManager) {
Effect: "Allow",
Action: []string{"s3:GetObject", "s3:ListBucket"},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
},
@ -461,7 +461,7 @@ func setupTestPoliciesAndRoles(t *testing.T, manager *IAMManager) {
Effect: "Allow",
Action: []string{"filer:*"},
Resource: []string{
"arn:seaweed:filer::path/user-docs/*",
"arn:aws:filer::path/user-docs/*",
},
},
},

2
weed/iam/integration/iam_manager.go

@ -213,7 +213,7 @@ func (m *IAMManager) CreateRole(ctx context.Context, filerAddress string, roleNa
// Set role ARN if not provided
if roleDef.RoleArn == "" {
roleDef.RoleArn = fmt.Sprintf("arn:seaweed:iam::role/%s", roleName)
roleDef.RoleArn = fmt.Sprintf("arn:aws:iam::role/%s", roleName)
}
// Validate trust policy

6
weed/iam/integration/role_store_test.go

@ -18,7 +18,7 @@ func TestMemoryRoleStore(t *testing.T) {
// Test storing a role
roleDef := &RoleDefinition{
RoleName: "TestRole",
RoleArn: "arn:seaweed:iam::role/TestRole",
RoleArn: "arn:aws:iam::role/TestRole",
Description: "Test role for unit testing",
AttachedPolicies: []string{"TestPolicy"},
TrustPolicy: &policy.PolicyDocument{
@ -42,7 +42,7 @@ func TestMemoryRoleStore(t *testing.T) {
retrievedRole, err := store.GetRole(ctx, "", "TestRole")
require.NoError(t, err)
assert.Equal(t, "TestRole", retrievedRole.RoleName)
assert.Equal(t, "arn:seaweed:iam::role/TestRole", retrievedRole.RoleArn)
assert.Equal(t, "arn:aws:iam::role/TestRole", retrievedRole.RoleArn)
assert.Equal(t, "Test role for unit testing", retrievedRole.Description)
assert.Equal(t, []string{"TestPolicy"}, retrievedRole.AttachedPolicies)
@ -112,7 +112,7 @@ func TestDistributedIAMManagerWithRoleStore(t *testing.T) {
// Test creating a role
roleDef := &RoleDefinition{
RoleName: "DistributedTestRole",
RoleArn: "arn:seaweed:iam::role/DistributedTestRole",
RoleArn: "arn:aws:iam::role/DistributedTestRole",
Description: "Test role for distributed IAM",
AttachedPolicies: []string{"S3ReadOnlyPolicy"},
}

6
weed/iam/oidc/oidc_provider_test.go

@ -210,15 +210,15 @@ func TestOIDCProviderAuthentication(t *testing.T) {
{
Claim: "email",
Value: "*@example.com",
Role: "arn:seaweed:iam::role/UserRole",
Role: "arn:aws:iam::role/UserRole",
},
{
Claim: "groups",
Value: "admins",
Role: "arn:seaweed:iam::role/AdminRole",
Role: "arn:aws:iam::role/AdminRole",
},
},
DefaultRole: "arn:seaweed:iam::role/GuestRole",
DefaultRole: "arn:aws:iam::role/GuestRole",
},
}

2
weed/iam/policy/policy_engine.go

@ -95,7 +95,7 @@ type EvaluationContext struct {
// Action being requested (e.g., "s3:GetObject")
Action string `json:"action"`
// Resource being accessed (e.g., "arn:seaweed:s3:::bucket/key")
// Resource being accessed (e.g., "arn:aws:s3:::bucket/key")
Resource string `json:"resource"`
// RequestContext contains additional request information

30
weed/iam/policy/policy_engine_distributed_test.go

@ -47,13 +47,13 @@ func TestDistributedPolicyEngine(t *testing.T) {
Sid: "AllowS3Read",
Effect: "Allow",
Action: []string{"s3:GetObject", "s3:ListBucket"},
Resource: []string{"arn:seaweed:s3:::test-bucket/*", "arn:seaweed:s3:::test-bucket"},
Resource: []string{"arn:aws:s3:::test-bucket/*", "arn:aws:s3:::test-bucket"},
},
{
Sid: "DenyS3Write",
Effect: "Deny",
Action: []string{"s3:PutObject", "s3:DeleteObject"},
Resource: []string{"arn:seaweed:s3:::test-bucket/*"},
Resource: []string{"arn:aws:s3:::test-bucket/*"},
},
},
}
@ -83,9 +83,9 @@ func TestDistributedPolicyEngine(t *testing.T) {
t.Run("evaluation_consistency", func(t *testing.T) {
// Create evaluation context
evalCtx := &EvaluationContext{
Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
Principal: "arn:aws:sts::assumed-role/TestRole/session",
Action: "s3:GetObject",
Resource: "arn:seaweed:s3:::test-bucket/file.txt",
Resource: "arn:aws:s3:::test-bucket/file.txt",
RequestContext: map[string]interface{}{
"sourceIp": "192.168.1.100",
},
@ -118,9 +118,9 @@ func TestDistributedPolicyEngine(t *testing.T) {
// Test explicit deny precedence
t.Run("deny_precedence_consistency", func(t *testing.T) {
evalCtx := &EvaluationContext{
Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
Principal: "arn:aws:sts::assumed-role/TestRole/session",
Action: "s3:PutObject",
Resource: "arn:seaweed:s3:::test-bucket/newfile.txt",
Resource: "arn:aws:s3:::test-bucket/newfile.txt",
}
// All instances should consistently apply deny precedence
@ -146,9 +146,9 @@ func TestDistributedPolicyEngine(t *testing.T) {
// Test default effect consistency
t.Run("default_effect_consistency", func(t *testing.T) {
evalCtx := &EvaluationContext{
Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
Principal: "arn:aws:sts::assumed-role/TestRole/session",
Action: "filer:CreateEntry", // Action not covered by any policy
Resource: "arn:seaweed:filer::path/test",
Resource: "arn:aws:filer::path/test",
}
result1, err1 := instance1.Evaluate(ctx, "", evalCtx, []string{"TestPolicy"})
@ -196,9 +196,9 @@ func TestPolicyEngineConfigurationConsistency(t *testing.T) {
// Test with an action not covered by any policy
evalCtx := &EvaluationContext{
Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
Principal: "arn:aws:sts::assumed-role/TestRole/session",
Action: "uncovered:action",
Resource: "arn:seaweed:test:::resource",
Resource: "arn:aws:test:::resource",
}
result1, _ := instance1.Evaluate(context.Background(), "", evalCtx, []string{})
@ -277,9 +277,9 @@ func TestPolicyStoreDistributed(t *testing.T) {
require.NoError(t, err)
evalCtx := &EvaluationContext{
Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
Principal: "arn:aws:sts::assumed-role/TestRole/session",
Action: "s3:GetObject",
Resource: "arn:seaweed:s3:::bucket/key",
Resource: "arn:aws:s3:::bucket/key",
}
// Evaluate with non-existent policies
@ -350,7 +350,7 @@ func TestPolicyEvaluationPerformance(t *testing.T) {
Sid: fmt.Sprintf("Statement%d", i),
Effect: "Allow",
Action: []string{"s3:GetObject", "s3:ListBucket"},
Resource: []string{fmt.Sprintf("arn:seaweed:s3:::bucket%d/*", i)},
Resource: []string{fmt.Sprintf("arn:aws:s3:::bucket%d/*", i)},
},
},
}
@ -361,9 +361,9 @@ func TestPolicyEvaluationPerformance(t *testing.T) {
// Test evaluation performance
evalCtx := &EvaluationContext{
Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
Principal: "arn:aws:sts::assumed-role/TestRole/session",
Action: "s3:GetObject",
Resource: "arn:seaweed:s3:::bucket5/file.txt",
Resource: "arn:aws:s3:::bucket5/file.txt",
}
policyNames := make([]string, 10)

48
weed/iam/policy/policy_engine_test.go

@ -71,7 +71,7 @@ func TestPolicyDocumentValidation(t *testing.T) {
Sid: "AllowS3Read",
Effect: "Allow",
Action: []string{"s3:GetObject", "s3:ListBucket"},
Resource: []string{"arn:seaweed:s3:::mybucket/*"},
Resource: []string{"arn:aws:s3:::mybucket/*"},
},
},
},
@ -84,7 +84,7 @@ func TestPolicyDocumentValidation(t *testing.T) {
{
Effect: "Allow",
Action: []string{"s3:GetObject"},
Resource: []string{"arn:seaweed:s3:::mybucket/*"},
Resource: []string{"arn:aws:s3:::mybucket/*"},
},
},
},
@ -108,7 +108,7 @@ func TestPolicyDocumentValidation(t *testing.T) {
{
Effect: "Maybe",
Action: []string{"s3:GetObject"},
Resource: []string{"arn:seaweed:s3:::mybucket/*"},
Resource: []string{"arn:aws:s3:::mybucket/*"},
},
},
},
@ -146,8 +146,8 @@ func TestPolicyEvaluation(t *testing.T) {
Effect: "Allow",
Action: []string{"s3:GetObject", "s3:ListBucket"},
Resource: []string{
"arn:seaweed:s3:::public-bucket/*", // For object operations
"arn:seaweed:s3:::public-bucket", // For bucket operations
"arn:aws:s3:::public-bucket/*", // For object operations
"arn:aws:s3:::public-bucket", // For bucket operations
},
},
},
@ -163,7 +163,7 @@ func TestPolicyEvaluation(t *testing.T) {
Sid: "DenyS3Delete",
Effect: "Deny",
Action: []string{"s3:DeleteObject"},
Resource: []string{"arn:seaweed:s3:::*"},
Resource: []string{"arn:aws:s3:::*"},
},
},
}
@ -182,7 +182,7 @@ func TestPolicyEvaluation(t *testing.T) {
context: &EvaluationContext{
Principal: "user:alice",
Action: "s3:GetObject",
Resource: "arn:seaweed:s3:::public-bucket/file.txt",
Resource: "arn:aws:s3:::public-bucket/file.txt",
RequestContext: map[string]interface{}{
"sourceIP": "192.168.1.100",
},
@ -195,7 +195,7 @@ func TestPolicyEvaluation(t *testing.T) {
context: &EvaluationContext{
Principal: "user:alice",
Action: "s3:DeleteObject",
Resource: "arn:seaweed:s3:::public-bucket/file.txt",
Resource: "arn:aws:s3:::public-bucket/file.txt",
},
policies: []string{"read-policy", "deny-policy"},
want: EffectDeny,
@ -205,7 +205,7 @@ func TestPolicyEvaluation(t *testing.T) {
context: &EvaluationContext{
Principal: "user:alice",
Action: "s3:PutObject",
Resource: "arn:seaweed:s3:::public-bucket/file.txt",
Resource: "arn:aws:s3:::public-bucket/file.txt",
},
policies: []string{"read-policy"},
want: EffectDeny,
@ -215,7 +215,7 @@ func TestPolicyEvaluation(t *testing.T) {
context: &EvaluationContext{
Principal: "user:admin",
Action: "s3:ListBucket",
Resource: "arn:seaweed:s3:::public-bucket",
Resource: "arn:aws:s3:::public-bucket",
},
policies: []string{"read-policy"},
want: EffectAllow,
@ -249,7 +249,7 @@ func TestConditionEvaluation(t *testing.T) {
Sid: "AllowFromOfficeIP",
Effect: "Allow",
Action: []string{"s3:*"},
Resource: []string{"arn:seaweed:s3:::*"},
Resource: []string{"arn:aws:s3:::*"},
Condition: map[string]map[string]interface{}{
"IpAddress": {
"seaweed:SourceIP": []string{"192.168.1.0/24", "10.0.0.0/8"},
@ -272,7 +272,7 @@ func TestConditionEvaluation(t *testing.T) {
context: &EvaluationContext{
Principal: "user:alice",
Action: "s3:GetObject",
Resource: "arn:seaweed:s3:::mybucket/file.txt",
Resource: "arn:aws:s3:::mybucket/file.txt",
RequestContext: map[string]interface{}{
"sourceIP": "192.168.1.100",
},
@ -284,7 +284,7 @@ func TestConditionEvaluation(t *testing.T) {
context: &EvaluationContext{
Principal: "user:alice",
Action: "s3:GetObject",
Resource: "arn:seaweed:s3:::mybucket/file.txt",
Resource: "arn:aws:s3:::mybucket/file.txt",
RequestContext: map[string]interface{}{
"sourceIP": "8.8.8.8",
},
@ -296,7 +296,7 @@ func TestConditionEvaluation(t *testing.T) {
context: &EvaluationContext{
Principal: "user:alice",
Action: "s3:PutObject",
Resource: "arn:seaweed:s3:::mybucket/newfile.txt",
Resource: "arn:aws:s3:::mybucket/newfile.txt",
RequestContext: map[string]interface{}{
"sourceIP": "10.1.2.3",
},
@ -325,32 +325,32 @@ func TestResourceMatching(t *testing.T) {
}{
{
name: "exact match",
policyResource: "arn:seaweed:s3:::mybucket/file.txt",
requestResource: "arn:seaweed:s3:::mybucket/file.txt",
policyResource: "arn:aws:s3:::mybucket/file.txt",
requestResource: "arn:aws:s3:::mybucket/file.txt",
want: true,
},
{
name: "wildcard match",
policyResource: "arn:seaweed:s3:::mybucket/*",
requestResource: "arn:seaweed:s3:::mybucket/folder/file.txt",
policyResource: "arn:aws:s3:::mybucket/*",
requestResource: "arn:aws:s3:::mybucket/folder/file.txt",
want: true,
},
{
name: "bucket wildcard",
policyResource: "arn:seaweed:s3:::*",
requestResource: "arn:seaweed:s3:::anybucket/file.txt",
policyResource: "arn:aws:s3:::*",
requestResource: "arn:aws:s3:::anybucket/file.txt",
want: true,
},
{
name: "no match different bucket",
policyResource: "arn:seaweed:s3:::mybucket/*",
requestResource: "arn:seaweed:s3:::otherbucket/file.txt",
policyResource: "arn:aws:s3:::mybucket/*",
requestResource: "arn:aws:s3:::otherbucket/file.txt",
want: false,
},
{
name: "prefix match",
policyResource: "arn:seaweed:s3:::mybucket/documents/*",
requestResource: "arn:seaweed:s3:::mybucket/documents/secret.txt",
policyResource: "arn:aws:s3:::mybucket/documents/*",
requestResource: "arn:aws:s3:::mybucket/documents/secret.txt",
want: true,
},
}

10
weed/iam/sts/cross_instance_token_test.go

@ -153,7 +153,7 @@ func TestCrossInstanceTokenUsage(t *testing.T) {
mockToken := createMockJWT(t, "http://test-mock:9999", "test-user")
assumeRequest := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/CrossInstanceTestRole",
RoleArn: "arn:aws:iam::role/CrossInstanceTestRole",
WebIdentityToken: mockToken, // JWT token for mock provider
RoleSessionName: "cross-instance-test-session",
DurationSeconds: int64ToPtr(3600),
@ -198,7 +198,7 @@ func TestCrossInstanceTokenUsage(t *testing.T) {
mockToken := createMockJWT(t, "http://test-mock:9999", "test-user")
assumeRequest := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/RevocationTestRole",
RoleArn: "arn:aws:iam::role/RevocationTestRole",
WebIdentityToken: mockToken,
RoleSessionName: "revocation-test-session",
}
@ -240,7 +240,7 @@ func TestCrossInstanceTokenUsage(t *testing.T) {
// Try to assume role with same token on different instances
assumeRequest := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/ProviderTestRole",
RoleArn: "arn:aws:iam::role/ProviderTestRole",
WebIdentityToken: testToken,
RoleSessionName: "provider-consistency-test",
}
@ -452,7 +452,7 @@ func TestSTSRealWorldDistributedScenarios(t *testing.T) {
mockToken := createMockJWT(t, "http://test-mock:9999", "production-user")
assumeRequest := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/ProductionS3User",
RoleArn: "arn:aws:iam::role/ProductionS3User",
WebIdentityToken: mockToken, // JWT token from mock provider
RoleSessionName: "user-production-session",
DurationSeconds: int64ToPtr(7200), // 2 hours
@ -470,7 +470,7 @@ func TestSTSRealWorldDistributedScenarios(t *testing.T) {
sessionInfo2, err := gateway2.ValidateSessionToken(ctx, sessionToken)
require.NoError(t, err, "Gateway 2 should validate session from Gateway 1")
assert.Equal(t, "user-production-session", sessionInfo2.SessionName)
assert.Equal(t, "arn:seaweed:iam::role/ProductionS3User", sessionInfo2.RoleArn)
assert.Equal(t, "arn:aws:iam::role/ProductionS3User", sessionInfo2.RoleArn)
// Simulate S3 request validation on Gateway 3
sessionInfo3, err := gateway3.ValidateSessionToken(ctx, sessionToken)

18
weed/iam/sts/session_policy_test.go

@ -47,7 +47,7 @@ func TestAssumeRoleWithWebIdentity_SessionPolicy(t *testing.T) {
testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")
request := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/TestRole",
RoleArn: "arn:aws:iam::role/TestRole",
WebIdentityToken: testToken,
RoleSessionName: "test-session",
DurationSeconds: nil, // Use default
@ -69,7 +69,7 @@ func TestAssumeRoleWithWebIdentity_SessionPolicy(t *testing.T) {
testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")
request := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/TestRole",
RoleArn: "arn:aws:iam::role/TestRole",
WebIdentityToken: testToken,
RoleSessionName: "test-session",
DurationSeconds: nil, // Use default
@ -93,7 +93,7 @@ func TestAssumeRoleWithWebIdentity_SessionPolicy(t *testing.T) {
testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")
request := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/TestRole",
RoleArn: "arn:aws:iam::role/TestRole",
WebIdentityToken: testToken,
RoleSessionName: "test-session",
Policy: nil, // ← Explicitly nil
@ -113,7 +113,7 @@ func TestAssumeRoleWithWebIdentity_SessionPolicy(t *testing.T) {
emptyPolicy := "" // Empty string, but still a non-nil pointer
request := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/TestRole",
RoleArn: "arn:aws:iam::role/TestRole",
WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
RoleSessionName: "test-session",
Policy: &emptyPolicy, // ← Non-nil pointer to empty string
@ -160,7 +160,7 @@ func TestAssumeRoleWithWebIdentity_SessionPolicy_ErrorMessage(t *testing.T) {
testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")
request := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/TestRole",
RoleArn: "arn:aws:iam::role/TestRole",
WebIdentityToken: testToken,
RoleSessionName: "test-session-with-complex-policy",
Policy: &complexPolicy,
@ -196,7 +196,7 @@ func TestAssumeRoleWithWebIdentity_SessionPolicy_EdgeCases(t *testing.T) {
malformedPolicy := `{"Version": "2012-10-17", "Statement": [` // Incomplete JSON
request := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/TestRole",
RoleArn: "arn:aws:iam::role/TestRole",
WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
RoleSessionName: "test-session",
Policy: &malformedPolicy,
@ -215,7 +215,7 @@ func TestAssumeRoleWithWebIdentity_SessionPolicy_EdgeCases(t *testing.T) {
whitespacePolicy := " \t\n " // Only whitespace
request := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/TestRole",
RoleArn: "arn:aws:iam::role/TestRole",
WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
RoleSessionName: "test-session",
Policy: &whitespacePolicy,
@ -260,7 +260,7 @@ func TestAssumeRoleWithCredentials_NoSessionPolicySupport(t *testing.T) {
// This is the expected behavior since session policies are typically only
// supported with web identity (OIDC/SAML) flows in AWS STS
request := &AssumeRoleWithCredentialsRequest{
RoleArn: "arn:seaweed:iam::role/TestRole",
RoleArn: "arn:aws:iam::role/TestRole",
Username: "testuser",
Password: "testpass",
RoleSessionName: "test-session",
@ -269,7 +269,7 @@ func TestAssumeRoleWithCredentials_NoSessionPolicySupport(t *testing.T) {
// The struct should compile and work without a Policy field
assert.NotNil(t, request)
assert.Equal(t, "arn:seaweed:iam::role/TestRole", request.RoleArn)
assert.Equal(t, "arn:aws:iam::role/TestRole", request.RoleArn)
assert.Equal(t, "testuser", request.Username)
// This documents that credential-based assume role does NOT support session policies

4
weed/iam/sts/sts_service.go

@ -683,7 +683,7 @@ func (s *STSService) validateRoleAssumptionForWebIdentity(ctx context.Context, r
}
// Basic role ARN format validation
expectedPrefix := "arn:seaweed:iam::role/"
expectedPrefix := "arn:aws:iam::role/"
if len(roleArn) < len(expectedPrefix) || roleArn[:len(expectedPrefix)] != expectedPrefix {
return fmt.Errorf("invalid role ARN format: got %s, expected format: %s*", roleArn, expectedPrefix)
}
@ -720,7 +720,7 @@ func (s *STSService) validateRoleAssumptionForCredentials(ctx context.Context, r
}
// Basic role ARN format validation
expectedPrefix := "arn:seaweed:iam::role/"
expectedPrefix := "arn:aws:iam::role/"
if len(roleArn) < len(expectedPrefix) || roleArn[:len(expectedPrefix)] != expectedPrefix {
return fmt.Errorf("invalid role ARN format: got %s, expected format: %s*", roleArn, expectedPrefix)
}

18
weed/iam/sts/sts_service_test.go

@ -95,7 +95,7 @@ func TestAssumeRoleWithWebIdentity(t *testing.T) {
}{
{
name: "successful role assumption",
roleArn: "arn:seaweed:iam::role/TestRole",
roleArn: "arn:aws:iam::role/TestRole",
webIdentityToken: createSTSTestJWT(t, "test-issuer", "test-user-id"),
sessionName: "test-session",
durationSeconds: nil, // Use default
@ -104,21 +104,21 @@ func TestAssumeRoleWithWebIdentity(t *testing.T) {
},
{
name: "invalid web identity token",
roleArn: "arn:seaweed:iam::role/TestRole",
roleArn: "arn:aws:iam::role/TestRole",
webIdentityToken: "invalid-token",
sessionName: "test-session",
wantErr: true,
},
{
name: "non-existent role",
roleArn: "arn:seaweed:iam::role/NonExistentRole",
roleArn: "arn:aws:iam::role/NonExistentRole",
webIdentityToken: createSTSTestJWT(t, "test-issuer", "test-user"),
sessionName: "test-session",
wantErr: true,
},
{
name: "custom session duration",
roleArn: "arn:seaweed:iam::role/TestRole",
roleArn: "arn:aws:iam::role/TestRole",
webIdentityToken: createSTSTestJWT(t, "test-issuer", "test-user"),
sessionName: "test-session",
durationSeconds: int64Ptr(7200), // 2 hours
@ -182,7 +182,7 @@ func TestAssumeRoleWithLDAP(t *testing.T) {
}{
{
name: "successful LDAP role assumption",
roleArn: "arn:seaweed:iam::role/LDAPRole",
roleArn: "arn:aws:iam::role/LDAPRole",
username: "testuser",
password: "testpass",
sessionName: "ldap-session",
@ -190,7 +190,7 @@ func TestAssumeRoleWithLDAP(t *testing.T) {
},
{
name: "invalid LDAP credentials",
roleArn: "arn:seaweed:iam::role/LDAPRole",
roleArn: "arn:aws:iam::role/LDAPRole",
username: "testuser",
password: "wrongpass",
sessionName: "ldap-session",
@ -231,7 +231,7 @@ func TestSessionTokenValidation(t *testing.T) {
// First, create a session
request := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/TestRole",
RoleArn: "arn:aws:iam::role/TestRole",
WebIdentityToken: createSTSTestJWT(t, "test-issuer", "test-user"),
RoleSessionName: "test-session",
}
@ -275,7 +275,7 @@ func TestSessionTokenValidation(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, session)
assert.Equal(t, "test-session", session.SessionName)
assert.Equal(t, "arn:seaweed:iam::role/TestRole", session.RoleArn)
assert.Equal(t, "arn:aws:iam::role/TestRole", session.RoleArn)
}
})
}
@ -289,7 +289,7 @@ func TestSessionTokenPersistence(t *testing.T) {
// Create a session first
request := &AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/TestRole",
RoleArn: "arn:aws:iam::role/TestRole",
WebIdentityToken: createSTSTestJWT(t, "test-issuer", "test-user"),
RoleSessionName: "test-session",
}

6
weed/iam/sts/token_utils.go

@ -207,11 +207,11 @@ func GenerateSessionId() (string, error) {
// generateAssumedRoleArn generates the ARN for an assumed role user
func GenerateAssumedRoleArn(roleArn, sessionName string) string {
// Convert role ARN to assumed role user ARN
// arn:seaweed:iam::role/RoleName -> arn:seaweed:sts::assumed-role/RoleName/SessionName
// arn:aws:iam::role/RoleName -> arn:aws:sts::assumed-role/RoleName/SessionName
roleName := utils.ExtractRoleNameFromArn(roleArn)
if roleName == "" {
// This should not happen if validation is done properly upstream
return fmt.Sprintf("arn:seaweed:sts::assumed-role/INVALID-ARN/%s", sessionName)
return fmt.Sprintf("arn:aws:sts::assumed-role/INVALID-ARN/%s", sessionName)
}
return fmt.Sprintf("arn:seaweed:sts::assumed-role/%s/%s", roleName, sessionName)
return fmt.Sprintf("arn:aws:sts::assumed-role/%s/%s", roleName, sessionName)
}

12
weed/iam/utils/arn_utils.go

@ -5,8 +5,8 @@ import "strings"
// ExtractRoleNameFromPrincipal extracts role name from principal ARN
// Handles both STS assumed role and IAM role formats
func ExtractRoleNameFromPrincipal(principal string) string {
// Handle STS assumed role format: arn:seaweed:sts::assumed-role/RoleName/SessionName
stsPrefix := "arn:seaweed:sts::assumed-role/"
// Handle STS assumed role format: arn:aws:sts::assumed-role/RoleName/SessionName
stsPrefix := "arn:aws:sts::assumed-role/"
if strings.HasPrefix(principal, stsPrefix) {
remainder := principal[len(stsPrefix):]
// Split on first '/' to get role name
@ -17,8 +17,8 @@ func ExtractRoleNameFromPrincipal(principal string) string {
return remainder
}
// Handle IAM role format: arn:seaweed:iam::role/RoleName
iamPrefix := "arn:seaweed:iam::role/"
// Handle IAM role format: arn:aws:iam::role/RoleName
iamPrefix := "arn:aws:iam::role/"
if strings.HasPrefix(principal, iamPrefix) {
return principal[len(iamPrefix):]
}
@ -29,9 +29,9 @@ func ExtractRoleNameFromPrincipal(principal string) string {
}
// ExtractRoleNameFromArn extracts role name from an IAM role ARN
// Specifically handles: arn:seaweed:iam::role/RoleName
// Specifically handles: arn:aws:iam::role/RoleName
func ExtractRoleNameFromArn(roleArn string) string {
prefix := "arn:seaweed:iam::role/"
prefix := "arn:aws:iam::role/"
if strings.HasPrefix(roleArn, prefix) && len(roleArn) > len(prefix) {
return roleArn[len(prefix):]
}

8
weed/s3api/auth_credentials.go

@ -63,7 +63,7 @@ type Identity struct {
Account *Account
Credentials []*Credential
Actions []Action
PrincipalArn string // ARN for IAM authorization (e.g., "arn:seaweed:iam::user/username")
PrincipalArn string // ARN for IAM authorization (e.g., "arn:aws:iam::user/username")
}
// Account represents a system user, a system user can
@ -384,11 +384,11 @@ func generatePrincipalArn(identityName string) string {
// Handle special cases
switch identityName {
case AccountAnonymous.Id:
return "arn:seaweed:iam::user/anonymous"
return "arn:aws:iam::user/anonymous"
case AccountAdmin.Id:
return "arn:seaweed:iam::user/admin"
return "arn:aws:iam::user/admin"
default:
return fmt.Sprintf("arn:seaweed:iam::user/%s", identityName)
return fmt.Sprintf("arn:aws:iam::user/%s", identityName)
}
}

6
weed/s3api/auth_credentials_test.go

@ -194,7 +194,7 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
expectIdent: &Identity{
Name: "notSpecifyAccountId",
Account: &AccountAdmin,
PrincipalArn: "arn:seaweed:iam::user/notSpecifyAccountId",
PrincipalArn: "arn:aws:iam::user/notSpecifyAccountId",
Actions: []Action{
"Read",
"Write",
@ -220,7 +220,7 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
expectIdent: &Identity{
Name: "specifiedAccountID",
Account: &specifiedAccount,
PrincipalArn: "arn:seaweed:iam::user/specifiedAccountID",
PrincipalArn: "arn:aws:iam::user/specifiedAccountID",
Actions: []Action{
"Read",
"Write",
@ -238,7 +238,7 @@ func TestLoadS3ApiConfiguration(t *testing.T) {
expectIdent: &Identity{
Name: "anonymous",
Account: &AccountAnonymous,
PrincipalArn: "arn:seaweed:iam::user/anonymous",
PrincipalArn: "arn:aws:iam::user/anonymous",
Actions: []Action{
"Read",
"Write",

26
weed/s3api/s3_end_to_end_test.go

@ -54,7 +54,7 @@ func TestS3EndToEndWithJWT(t *testing.T) {
}{
{
name: "S3 Read-Only Role Complete Workflow",
roleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
roleArn: "arn:aws:iam::role/S3ReadOnlyRole",
sessionName: "readonly-test-session",
setupRole: setupS3ReadOnlyRole,
s3Operations: []S3Operation{
@ -69,7 +69,7 @@ func TestS3EndToEndWithJWT(t *testing.T) {
},
{
name: "S3 Admin Role Complete Workflow",
roleArn: "arn:seaweed:iam::role/S3AdminRole",
roleArn: "arn:aws:iam::role/S3AdminRole",
sessionName: "admin-test-session",
setupRole: setupS3AdminRole,
s3Operations: []S3Operation{
@ -83,7 +83,7 @@ func TestS3EndToEndWithJWT(t *testing.T) {
},
{
name: "S3 IP-Restricted Role",
roleArn: "arn:seaweed:iam::role/S3IPRestrictedRole",
roleArn: "arn:aws:iam::role/S3IPRestrictedRole",
sessionName: "ip-restricted-session",
setupRole: setupS3IPRestrictedRole,
s3Operations: []S3Operation{
@ -145,7 +145,7 @@ func TestS3MultipartUploadWithJWT(t *testing.T) {
// Assume role
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/S3WriteRole",
RoleArn: "arn:aws:iam::role/S3WriteRole",
WebIdentityToken: validJWTToken,
RoleSessionName: "multipart-test-session",
})
@ -255,7 +255,7 @@ func TestS3PerformanceWithIAM(t *testing.T) {
// Assume role
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
RoleArn: "arn:aws:iam::role/S3ReadOnlyRole",
WebIdentityToken: validJWTToken,
RoleSessionName: "performance-test-session",
})
@ -452,8 +452,8 @@ func setupS3ReadOnlyRole(ctx context.Context, manager *integration.IAMManager) {
Effect: "Allow",
Action: []string{"s3:GetObject", "s3:ListBucket", "s3:HeadObject"},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
{
@ -496,8 +496,8 @@ func setupS3AdminRole(ctx context.Context, manager *integration.IAMManager) {
Effect: "Allow",
Action: []string{"s3:*"},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
{
@ -540,8 +540,8 @@ func setupS3WriteRole(ctx context.Context, manager *integration.IAMManager) {
Effect: "Allow",
Action: []string{"s3:PutObject", "s3:GetObject", "s3:ListBucket", "s3:DeleteObject"},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
{
@ -584,8 +584,8 @@ func setupS3IPRestrictedRole(ctx context.Context, manager *integration.IAMManage
Effect: "Allow",
Action: []string{"s3:GetObject", "s3:ListBucket"},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
Condition: map[string]map[string]interface{}{
"IpAddress": {

8
weed/s3api/s3_iam_middleware.go

@ -139,7 +139,7 @@ func (s3iam *S3IAMIntegration) AuthenticateJWT(ctx context.Context, r *http.Requ
parts := strings.Split(roleName, "/")
roleNameOnly = parts[len(parts)-1]
}
principalArn = fmt.Sprintf("arn:seaweed:sts::assumed-role/%s/%s", roleNameOnly, sessionName)
principalArn = fmt.Sprintf("arn:aws:sts::assumed-role/%s/%s", roleNameOnly, sessionName)
}
// Validate the JWT token directly using STS service (avoid circular dependency)
@ -238,11 +238,11 @@ type MockAssumedRoleUser struct {
// buildS3ResourceArn builds an S3 resource ARN from bucket and object
func buildS3ResourceArn(bucket string, objectKey string) string {
if bucket == "" {
return "arn:seaweed:s3:::*"
return "arn:aws:s3:::*"
}
if objectKey == "" || objectKey == "/" {
return "arn:seaweed:s3:::" + bucket
return "arn:aws:s3:::" + bucket
}
// Remove leading slash from object key if present
@ -250,7 +250,7 @@ func buildS3ResourceArn(bucket string, objectKey string) string {
objectKey = objectKey[1:]
}
return "arn:seaweed:s3:::" + bucket + "/" + objectKey
return "arn:aws:s3:::" + bucket + "/" + objectKey
}
// determineGranularS3Action determines the specific S3 IAM action based on HTTP request details

16
weed/s3api/s3_iam_simple_test.go

@ -84,31 +84,31 @@ func TestBuildS3ResourceArn(t *testing.T) {
name: "empty bucket and object",
bucket: "",
object: "",
expected: "arn:seaweed:s3:::*",
expected: "arn:aws:s3:::*",
},
{
name: "bucket only",
bucket: "test-bucket",
object: "",
expected: "arn:seaweed:s3:::test-bucket",
expected: "arn:aws:s3:::test-bucket",
},
{
name: "bucket and object",
bucket: "test-bucket",
object: "test-object.txt",
expected: "arn:seaweed:s3:::test-bucket/test-object.txt",
expected: "arn:aws:s3:::test-bucket/test-object.txt",
},
{
name: "bucket and object with leading slash",
bucket: "test-bucket",
object: "/test-object.txt",
expected: "arn:seaweed:s3:::test-bucket/test-object.txt",
expected: "arn:aws:s3:::test-bucket/test-object.txt",
},
{
name: "bucket and nested object",
bucket: "test-bucket",
object: "folder/subfolder/test-object.txt",
expected: "arn:seaweed:s3:::test-bucket/folder/subfolder/test-object.txt",
expected: "arn:aws:s3:::test-bucket/folder/subfolder/test-object.txt",
},
}
@ -447,7 +447,7 @@ func TestExtractRoleNameFromPrincipal(t *testing.T) {
}{
{
name: "valid assumed role ARN",
principal: "arn:seaweed:sts::assumed-role/S3ReadOnlyRole/session-123",
principal: "arn:aws:sts::assumed-role/S3ReadOnlyRole/session-123",
expected: "S3ReadOnlyRole",
},
{
@ -457,7 +457,7 @@ func TestExtractRoleNameFromPrincipal(t *testing.T) {
},
{
name: "missing session name",
principal: "arn:seaweed:sts::assumed-role/TestRole",
principal: "arn:aws:sts::assumed-role/TestRole",
expected: "TestRole", // Extracts role name even without session name
},
{
@ -479,7 +479,7 @@ func TestExtractRoleNameFromPrincipal(t *testing.T) {
func TestIAMIdentityIsAdmin(t *testing.T) {
identity := &IAMIdentity{
Name: "test-identity",
Principal: "arn:seaweed:sts::assumed-role/TestRole/session",
Principal: "arn:aws:sts::assumed-role/TestRole/session",
SessionToken: "test-token",
}

20
weed/s3api/s3_jwt_auth_test.go

@ -56,7 +56,7 @@ func TestJWTAuthenticationFlow(t *testing.T) {
}{
{
name: "Read-Only JWT Authentication",
roleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
roleArn: "arn:aws:iam::role/S3ReadOnlyRole",
setupRole: setupTestReadOnlyRole,
testOperations: []JWTTestOperation{
{Action: s3_constants.ACTION_READ, Bucket: "test-bucket", Object: "test-file.txt", ExpectedAllow: true},
@ -66,7 +66,7 @@ func TestJWTAuthenticationFlow(t *testing.T) {
},
{
name: "Admin JWT Authentication",
roleArn: "arn:seaweed:iam::role/S3AdminRole",
roleArn: "arn:aws:iam::role/S3AdminRole",
setupRole: setupTestAdminRole,
testOperations: []JWTTestOperation{
{Action: s3_constants.ACTION_READ, Bucket: "admin-bucket", Object: "admin-file.txt", ExpectedAllow: true},
@ -221,7 +221,7 @@ func TestIPBasedPolicyEnforcement(t *testing.T) {
// Assume role
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/S3IPRestrictedRole",
RoleArn: "arn:aws:iam::role/S3IPRestrictedRole",
WebIdentityToken: validJWTToken,
RoleSessionName: "ip-test-session",
})
@ -363,8 +363,8 @@ func setupTestReadOnlyRole(ctx context.Context, manager *integration.IAMManager)
Effect: "Allow",
Action: []string{"s3:GetObject", "s3:ListBucket"},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
{
@ -425,8 +425,8 @@ func setupTestAdminRole(ctx context.Context, manager *integration.IAMManager) {
Effect: "Allow",
Action: []string{"s3:*"},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
{
@ -487,8 +487,8 @@ func setupTestIPRestrictedRole(ctx context.Context, manager *integration.IAMMana
Effect: "Allow",
Action: []string{"s3:GetObject", "s3:ListBucket"},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
Condition: map[string]map[string]interface{}{
"IpAddress": {
@ -544,7 +544,7 @@ func testJWTAuthorizationWithRole(t *testing.T, iam *IdentityAccessManagement, i
req.Header.Set("X-SeaweedFS-Session-Token", token)
// Use a proper principal ARN format that matches what STS would generate
principalArn := "arn:seaweed:sts::assumed-role/" + roleName + "/test-session"
principalArn := "arn:aws:sts::assumed-role/" + roleName + "/test-session"
req.Header.Set("X-SeaweedFS-Principal", principalArn)
// Test authorization

14
weed/s3api/s3_multipart_iam_test.go

@ -58,7 +58,7 @@ func TestMultipartIAMValidation(t *testing.T) {
// Get session token
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/S3WriteRole",
RoleArn: "arn:aws:iam::role/S3WriteRole",
WebIdentityToken: validJWTToken,
RoleSessionName: "multipart-test-session",
})
@ -443,8 +443,8 @@ func TestMultipartUploadSession(t *testing.T) {
UploadID: "test-upload-123",
Bucket: "test-bucket",
ObjectKey: "test-file.txt",
Initiator: "arn:seaweed:iam::user/testuser",
Owner: "arn:seaweed:iam::user/testuser",
Initiator: "arn:aws:iam::user/testuser",
Owner: "arn:aws:iam::user/testuser",
CreatedAt: time.Now(),
Parts: []MultipartUploadPart{
{
@ -550,8 +550,8 @@ func setupTestRolesForMultipart(ctx context.Context, manager *integration.IAMMan
"s3:ListParts",
},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
},
@ -603,8 +603,8 @@ func createMultipartRequest(t *testing.T, method, path, sessionToken string) *ht
if sessionToken != "" {
req.Header.Set("Authorization", "Bearer "+sessionToken)
// Set the principal ARN header that matches the assumed role from the test setup
// This corresponds to the role "arn:seaweed:iam::role/S3WriteRole" with session name "multipart-test-session"
req.Header.Set("X-SeaweedFS-Principal", "arn:seaweed:sts::assumed-role/S3WriteRole/multipart-test-session")
// This corresponds to the role "arn:aws:iam::role/S3WriteRole" with session name "multipart-test-session"
req.Header.Set("X-SeaweedFS-Principal", "arn:aws:sts::assumed-role/S3WriteRole/multipart-test-session")
}
// Add common headers

56
weed/s3api/s3_policy_templates.go

@ -32,8 +32,8 @@ func (t *S3PolicyTemplates) GetS3ReadOnlyPolicy() *policy.PolicyDocument {
"s3:ListAllMyBuckets",
},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
},
@ -59,8 +59,8 @@ func (t *S3PolicyTemplates) GetS3WriteOnlyPolicy() *policy.PolicyDocument {
"s3:ListParts",
},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
},
@ -79,8 +79,8 @@ func (t *S3PolicyTemplates) GetS3AdminPolicy() *policy.PolicyDocument {
"s3:*",
},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
},
@ -103,8 +103,8 @@ func (t *S3PolicyTemplates) GetBucketSpecificReadPolicy(bucketName string) *poli
"s3:GetBucketLocation",
},
Resource: []string{
"arn:seaweed:s3:::" + bucketName,
"arn:seaweed:s3:::" + bucketName + "/*",
"arn:aws:s3:::" + bucketName,
"arn:aws:s3:::" + bucketName + "/*",
},
},
},
@ -130,8 +130,8 @@ func (t *S3PolicyTemplates) GetBucketSpecificWritePolicy(bucketName string) *pol
"s3:ListParts",
},
Resource: []string{
"arn:seaweed:s3:::" + bucketName,
"arn:seaweed:s3:::" + bucketName + "/*",
"arn:aws:s3:::" + bucketName,
"arn:aws:s3:::" + bucketName + "/*",
},
},
},
@ -150,7 +150,7 @@ func (t *S3PolicyTemplates) GetPathBasedAccessPolicy(bucketName, pathPrefix stri
"s3:ListBucket",
},
Resource: []string{
"arn:seaweed:s3:::" + bucketName,
"arn:aws:s3:::" + bucketName,
},
Condition: map[string]map[string]interface{}{
"StringLike": map[string]interface{}{
@ -171,7 +171,7 @@ func (t *S3PolicyTemplates) GetPathBasedAccessPolicy(bucketName, pathPrefix stri
"s3:AbortMultipartUpload",
},
Resource: []string{
"arn:seaweed:s3:::" + bucketName + "/" + pathPrefix + "/*",
"arn:aws:s3:::" + bucketName + "/" + pathPrefix + "/*",
},
},
},
@ -190,8 +190,8 @@ func (t *S3PolicyTemplates) GetIPRestrictedPolicy(allowedCIDRs []string) *policy
"s3:*",
},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
Condition: map[string]map[string]interface{}{
"IpAddress": map[string]interface{}{
@ -217,8 +217,8 @@ func (t *S3PolicyTemplates) GetTimeBasedAccessPolicy(startHour, endHour int) *po
"s3:ListBucket",
},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
Condition: map[string]map[string]interface{}{
"DateGreaterThan": map[string]interface{}{
@ -252,7 +252,7 @@ func (t *S3PolicyTemplates) GetMultipartUploadPolicy(bucketName string) *policy.
"s3:ListParts",
},
Resource: []string{
"arn:seaweed:s3:::" + bucketName + "/*",
"arn:aws:s3:::" + bucketName + "/*",
},
},
{
@ -262,7 +262,7 @@ func (t *S3PolicyTemplates) GetMultipartUploadPolicy(bucketName string) *policy.
"s3:ListBucket",
},
Resource: []string{
"arn:seaweed:s3:::" + bucketName,
"arn:aws:s3:::" + bucketName,
},
},
},
@ -282,7 +282,7 @@ func (t *S3PolicyTemplates) GetPresignedURLPolicy(bucketName string) *policy.Pol
"s3:PutObject",
},
Resource: []string{
"arn:seaweed:s3:::" + bucketName + "/*",
"arn:aws:s3:::" + bucketName + "/*",
},
Condition: map[string]map[string]interface{}{
"StringEquals": map[string]interface{}{
@ -310,8 +310,8 @@ func (t *S3PolicyTemplates) GetTemporaryAccessPolicy(bucketName string, expirati
"s3:ListBucket",
},
Resource: []string{
"arn:seaweed:s3:::" + bucketName,
"arn:seaweed:s3:::" + bucketName + "/*",
"arn:aws:s3:::" + bucketName,
"arn:aws:s3:::" + bucketName + "/*",
},
Condition: map[string]map[string]interface{}{
"DateLessThan": map[string]interface{}{
@ -338,7 +338,7 @@ func (t *S3PolicyTemplates) GetContentTypeRestrictedPolicy(bucketName string, al
"s3:CompleteMultipartUpload",
},
Resource: []string{
"arn:seaweed:s3:::" + bucketName + "/*",
"arn:aws:s3:::" + bucketName + "/*",
},
Condition: map[string]map[string]interface{}{
"StringEquals": map[string]interface{}{
@ -354,8 +354,8 @@ func (t *S3PolicyTemplates) GetContentTypeRestrictedPolicy(bucketName string, al
"s3:ListBucket",
},
Resource: []string{
"arn:seaweed:s3:::" + bucketName,
"arn:seaweed:s3:::" + bucketName + "/*",
"arn:aws:s3:::" + bucketName,
"arn:aws:s3:::" + bucketName + "/*",
},
},
},
@ -385,8 +385,8 @@ func (t *S3PolicyTemplates) GetDenyDeletePolicy() *policy.PolicyDocument {
"s3:ListParts",
},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
{
@ -398,8 +398,8 @@ func (t *S3PolicyTemplates) GetDenyDeletePolicy() *policy.PolicyDocument {
"s3:DeleteBucket",
},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
},

32
weed/s3api/s3_policy_templates_test.go

@ -26,8 +26,8 @@ func TestS3PolicyTemplates(t *testing.T) {
assert.NotContains(t, stmt.Action, "s3:PutObject")
assert.NotContains(t, stmt.Action, "s3:DeleteObject")
assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*")
assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*/*")
assert.Contains(t, stmt.Resource, "arn:aws:s3:::*")
assert.Contains(t, stmt.Resource, "arn:aws:s3:::*/*")
})
t.Run("S3WriteOnlyPolicy", func(t *testing.T) {
@ -45,8 +45,8 @@ func TestS3PolicyTemplates(t *testing.T) {
assert.NotContains(t, stmt.Action, "s3:GetObject")
assert.NotContains(t, stmt.Action, "s3:DeleteObject")
assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*")
assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*/*")
assert.Contains(t, stmt.Resource, "arn:aws:s3:::*")
assert.Contains(t, stmt.Resource, "arn:aws:s3:::*/*")
})
t.Run("S3AdminPolicy", func(t *testing.T) {
@ -61,8 +61,8 @@ func TestS3PolicyTemplates(t *testing.T) {
assert.Equal(t, "S3FullAccess", stmt.Sid)
assert.Contains(t, stmt.Action, "s3:*")
assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*")
assert.Contains(t, stmt.Resource, "arn:seaweed:s3:::*/*")
assert.Contains(t, stmt.Resource, "arn:aws:s3:::*")
assert.Contains(t, stmt.Resource, "arn:aws:s3:::*/*")
})
}
@ -84,8 +84,8 @@ func TestBucketSpecificPolicies(t *testing.T) {
assert.Contains(t, stmt.Action, "s3:ListBucket")
assert.NotContains(t, stmt.Action, "s3:PutObject")
expectedBucketArn := "arn:seaweed:s3:::" + bucketName
expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
expectedBucketArn := "arn:aws:s3:::" + bucketName
expectedObjectArn := "arn:aws:s3:::" + bucketName + "/*"
assert.Contains(t, stmt.Resource, expectedBucketArn)
assert.Contains(t, stmt.Resource, expectedObjectArn)
})
@ -104,8 +104,8 @@ func TestBucketSpecificPolicies(t *testing.T) {
assert.Contains(t, stmt.Action, "s3:CreateMultipartUpload")
assert.NotContains(t, stmt.Action, "s3:GetObject")
expectedBucketArn := "arn:seaweed:s3:::" + bucketName
expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
expectedBucketArn := "arn:aws:s3:::" + bucketName
expectedObjectArn := "arn:aws:s3:::" + bucketName + "/*"
assert.Contains(t, stmt.Resource, expectedBucketArn)
assert.Contains(t, stmt.Resource, expectedObjectArn)
})
@ -127,7 +127,7 @@ func TestPathBasedAccessPolicy(t *testing.T) {
assert.Equal(t, "Allow", listStmt.Effect)
assert.Equal(t, "ListBucketPermission", listStmt.Sid)
assert.Contains(t, listStmt.Action, "s3:ListBucket")
assert.Contains(t, listStmt.Resource, "arn:seaweed:s3:::"+bucketName)
assert.Contains(t, listStmt.Resource, "arn:aws:s3:::"+bucketName)
assert.NotNil(t, listStmt.Condition)
// Second statement: Object operations on path
@ -138,7 +138,7 @@ func TestPathBasedAccessPolicy(t *testing.T) {
assert.Contains(t, objectStmt.Action, "s3:PutObject")
assert.Contains(t, objectStmt.Action, "s3:DeleteObject")
expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/" + pathPrefix + "/*"
expectedObjectArn := "arn:aws:s3:::" + bucketName + "/" + pathPrefix + "/*"
assert.Contains(t, objectStmt.Resource, expectedObjectArn)
}
@ -216,7 +216,7 @@ func TestMultipartUploadPolicyTemplate(t *testing.T) {
assert.Contains(t, multipartStmt.Action, "s3:ListMultipartUploads")
assert.Contains(t, multipartStmt.Action, "s3:ListParts")
expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
expectedObjectArn := "arn:aws:s3:::" + bucketName + "/*"
assert.Contains(t, multipartStmt.Resource, expectedObjectArn)
// Second statement: List bucket
@ -225,7 +225,7 @@ func TestMultipartUploadPolicyTemplate(t *testing.T) {
assert.Equal(t, "ListBucketForMultipart", listStmt.Sid)
assert.Contains(t, listStmt.Action, "s3:ListBucket")
expectedBucketArn := "arn:seaweed:s3:::" + bucketName
expectedBucketArn := "arn:aws:s3:::" + bucketName
assert.Contains(t, listStmt.Resource, expectedBucketArn)
}
@ -246,7 +246,7 @@ func TestPresignedURLPolicy(t *testing.T) {
assert.Contains(t, stmt.Action, "s3:PutObject")
assert.NotNil(t, stmt.Condition)
expectedObjectArn := "arn:seaweed:s3:::" + bucketName + "/*"
expectedObjectArn := "arn:aws:s3:::" + bucketName + "/*"
assert.Contains(t, stmt.Resource, expectedObjectArn)
// Check signature version condition
@ -495,7 +495,7 @@ func TestPolicyValidation(t *testing.T) {
// Check resource format
for _, resource := range stmt.Resource {
if resource != "*" {
assert.Contains(t, resource, "arn:seaweed:s3:::", "Resource should be valid SeaweedFS S3 ARN: %s", resource)
assert.Contains(t, resource, "arn:aws:s3:::", "Resource should be valid SeaweedFS S3 ARN: %s", resource)
}
}
}

4
weed/s3api/s3_presigned_url_iam.go

@ -98,7 +98,7 @@ func (iam *IdentityAccessManagement) ValidatePresignedURLWithIAM(r *http.Request
parts := strings.Split(roleName, "/")
roleNameOnly = parts[len(parts)-1]
}
principalArn = fmt.Sprintf("arn:seaweed:sts::assumed-role/%s/%s", roleNameOnly, sessionName)
principalArn = fmt.Sprintf("arn:aws:sts::assumed-role/%s/%s", roleNameOnly, sessionName)
}
// Create IAM identity for authorization using extracted information
@ -130,7 +130,7 @@ func (pm *S3PresignedURLManager) GeneratePresignedURLWithIAM(ctx context.Context
// Validate session token and get identity
// Use a proper ARN format for the principal
principalArn := fmt.Sprintf("arn:seaweed:sts::assumed-role/PresignedUser/presigned-session")
principalArn := fmt.Sprintf("arn:aws:sts::assumed-role/PresignedUser/presigned-session")
iamIdentity := &IAMIdentity{
SessionToken: req.SessionToken,
Principal: principalArn,

12
weed/s3api/s3_presigned_url_iam_test.go

@ -57,7 +57,7 @@ func TestPresignedURLIAMValidation(t *testing.T) {
// Get session token
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/S3ReadOnlyRole",
RoleArn: "arn:aws:iam::role/S3ReadOnlyRole",
WebIdentityToken: validJWTToken,
RoleSessionName: "presigned-test-session",
})
@ -136,7 +136,7 @@ func TestPresignedURLGeneration(t *testing.T) {
// Get session token
response, err := iamManager.AssumeRoleWithWebIdentity(ctx, &sts.AssumeRoleWithWebIdentityRequest{
RoleArn: "arn:seaweed:iam::role/S3AdminRole",
RoleArn: "arn:aws:iam::role/S3AdminRole",
WebIdentityToken: validJWTToken,
RoleSessionName: "presigned-gen-test-session",
})
@ -503,8 +503,8 @@ func setupTestRolesForPresigned(ctx context.Context, manager *integration.IAMMan
Effect: "Allow",
Action: []string{"s3:GetObject", "s3:ListBucket", "s3:HeadObject"},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
},
@ -539,8 +539,8 @@ func setupTestRolesForPresigned(ctx context.Context, manager *integration.IAMMan
Effect: "Allow",
Action: []string{"s3:*"},
Resource: []string{
"arn:seaweed:s3:::*",
"arn:seaweed:s3:::*/*",
"arn:aws:s3:::*",
"arn:aws:s3:::*/*",
},
},
},

20
weed/s3api/s3api_bucket_handlers.go

@ -608,16 +608,16 @@ func (s3a *S3ApiServer) AuthWithPublicRead(handler http.HandlerFunc, action Acti
return
}
// Check bucket policy for anonymous access using the policy engine
principal := "*" // Anonymous principal
allowed, evaluated, err := s3a.policyEngine.EvaluatePolicy(bucket, object, string(action), principal)
if err != nil {
// SECURITY: Fail-close on policy evaluation errors
// If we can't evaluate the policy, deny access rather than falling through to IAM
glog.Errorf("AuthWithPublicRead: error evaluating bucket policy for %s/%s: %v - denying access", bucket, object, err)
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
return
} else if evaluated && allowed {
// Check bucket policy for anonymous access using the policy engine
principal := "*" // Anonymous principal
allowed, evaluated, err := s3a.policyEngine.EvaluatePolicy(bucket, object, string(action), principal)
if err != nil {
// SECURITY: Fail-close on policy evaluation errors
// If we can't evaluate the policy, deny access rather than falling through to IAM
glog.Errorf("AuthWithPublicRead: error evaluating bucket policy for %s/%s: %v - denying access", bucket, object, err)
s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
return
} else if evaluated && allowed {
glog.V(3).Infof("AuthWithPublicRead: allowing anonymous access to bucket %s (bucket policy)", bucket)
handler(w, r)
return

104
weed/s3api/s3api_bucket_policy_arn_test.go

@ -0,0 +1,104 @@
package s3api
import (
"testing"
)
// TestBuildResourceARN verifies that resource ARNs use the AWS-compatible format
func TestBuildResourceARN(t *testing.T) {
tests := []struct {
name string
bucket string
object string
expected string
}{
{
name: "bucket only",
bucket: "my-bucket",
object: "",
expected: "arn:aws:s3:::my-bucket",
},
{
name: "bucket with slash",
bucket: "my-bucket",
object: "/",
expected: "arn:aws:s3:::my-bucket",
},
{
name: "bucket and object",
bucket: "my-bucket",
object: "path/to/object.txt",
expected: "arn:aws:s3:::my-bucket/path/to/object.txt",
},
{
name: "bucket and object with leading slash",
bucket: "my-bucket",
object: "/path/to/object.txt",
expected: "arn:aws:s3:::my-bucket/path/to/object.txt",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := buildResourceARN(tt.bucket, tt.object)
if result != tt.expected {
t.Errorf("buildResourceARN(%q, %q) = %q, want %q", tt.bucket, tt.object, result, tt.expected)
}
})
}
}
// TestBuildPrincipalARN verifies that principal ARNs use the AWS-compatible format
func TestBuildPrincipalARN(t *testing.T) {
tests := []struct {
name string
identity *Identity
expected string
}{
{
name: "nil identity (anonymous)",
identity: nil,
expected: "*",
},
{
name: "identity with account and name",
identity: &Identity{
Name: "test-user",
Account: &Account{
Id: "123456789012",
},
},
expected: "arn:aws:iam::123456789012:user/test-user",
},
{
name: "identity without account ID",
identity: &Identity{
Name: "test-user",
Account: &Account{
Id: "",
},
},
expected: "arn:aws:iam::000000000000:user/test-user",
},
{
name: "identity without name",
identity: &Identity{
Name: "",
Account: &Account{
Id: "123456789012",
},
},
expected: "arn:aws:iam::123456789012:user/unknown",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := buildPrincipalARN(tt.identity)
if result != tt.expected {
t.Errorf("buildPrincipalARN() = %q, want %q", result, tt.expected)
}
})
}
}

4
weed/s3api/s3api_bucket_policy_handlers.go

@ -275,11 +275,11 @@ func (s3a *S3ApiServer) validateBucketPolicy(policyDoc *policy.PolicyDocument, b
// validateResourceForBucket checks if a resource ARN is valid for the given bucket
func (s3a *S3ApiServer) validateResourceForBucket(resource, bucket string) bool {
// Accepted formats for S3 bucket policies:
// AWS-style ARNs:
// AWS-style ARNs (recommended):
// arn:aws:s3:::bucket-name
// arn:aws:s3:::bucket-name/*
// arn:aws:s3:::bucket-name/path/to/object
// SeaweedFS ARNs:
// Legacy SeaweedFS ARNs (supported for backward compatibility):
// arn:seaweed:s3:::bucket-name
// arn:seaweed:s3:::bucket-name/*
// arn:seaweed:s3:::bucket-name/path/to/object

Loading…
Cancel
Save