You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							241 lines
						
					
					
						
							6.0 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							241 lines
						
					
					
						
							6.0 KiB
						
					
					
				
								package s3api
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"context"
							 | 
						|
									"net/http"
							 | 
						|
									"net/http/httptest"
							 | 
						|
									"testing"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/iam/integration"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/iam/policy"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/iam/sts"
							 | 
						|
									"github.com/stretchr/testify/assert"
							 | 
						|
									"github.com/stretchr/testify/require"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// TestS3IAMMiddleware tests the basic S3 IAM middleware functionality
							 | 
						|
								func TestS3IAMMiddleware(t *testing.T) {
							 | 
						|
									// Create IAM manager
							 | 
						|
									iamManager := integration.NewIAMManager()
							 | 
						|
								
							 | 
						|
									// Initialize with test configuration
							 | 
						|
									config := &integration.IAMConfig{
							 | 
						|
										STS: &sts.STSConfig{
							 | 
						|
											TokenDuration:    time.Hour,
							 | 
						|
											MaxSessionLength: time.Hour * 12,
							 | 
						|
											Issuer:           "test-sts",
							 | 
						|
											SigningKey:       []byte("test-signing-key-32-characters-long"),
							 | 
						|
										},
							 | 
						|
										Policy: &policy.PolicyEngineConfig{
							 | 
						|
											DefaultEffect: "Deny",
							 | 
						|
											StoreType:     "memory",
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									err := iamManager.Initialize(config)
							 | 
						|
									require.NoError(t, err)
							 | 
						|
								
							 | 
						|
									// Create S3 IAM integration
							 | 
						|
									s3IAMIntegration := NewS3IAMIntegration(iamManager)
							 | 
						|
								
							 | 
						|
									// Test that integration is created successfully
							 | 
						|
									assert.NotNil(t, s3IAMIntegration)
							 | 
						|
									assert.True(t, s3IAMIntegration.enabled)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestS3IAMMiddlewareJWTAuth tests JWT authentication
							 | 
						|
								func TestS3IAMMiddlewareJWTAuth(t *testing.T) {
							 | 
						|
									// Skip for now since it requires full setup
							 | 
						|
									t.Skip("JWT authentication test requires full IAM setup")
							 | 
						|
								
							 | 
						|
									// Create IAM integration
							 | 
						|
									s3iam := NewS3IAMIntegration(nil) // Disabled integration
							 | 
						|
								
							 | 
						|
									// Create test request with JWT token
							 | 
						|
									req := httptest.NewRequest("GET", "/test-bucket/test-object", http.NoBody)
							 | 
						|
									req.Header.Set("Authorization", "Bearer test-token")
							 | 
						|
								
							 | 
						|
									// Test authentication (should return not implemented when disabled)
							 | 
						|
									ctx := context.Background()
							 | 
						|
									identity, errCode := s3iam.AuthenticateJWT(ctx, req)
							 | 
						|
								
							 | 
						|
									assert.Nil(t, identity)
							 | 
						|
									assert.NotEqual(t, errCode, 0) // Should return an error
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestBuildS3ResourceArn tests resource ARN building
							 | 
						|
								func TestBuildS3ResourceArn(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name     string
							 | 
						|
										bucket   string
							 | 
						|
										object   string
							 | 
						|
										expected string
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											name:     "empty bucket and object",
							 | 
						|
											bucket:   "",
							 | 
						|
											object:   "",
							 | 
						|
											expected: "arn:seaweed:s3:::*",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "bucket only",
							 | 
						|
											bucket:   "test-bucket",
							 | 
						|
											object:   "",
							 | 
						|
											expected: "arn:seaweed:s3:::test-bucket",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "bucket and object",
							 | 
						|
											bucket:   "test-bucket",
							 | 
						|
											object:   "test-object.txt",
							 | 
						|
											expected: "arn:seaweed: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",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											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",
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											result := buildS3ResourceArn(tt.bucket, tt.object)
							 | 
						|
											assert.Equal(t, tt.expected, result)
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestMapS3ActionToIAMAction tests S3 to IAM action mapping
							 | 
						|
								func TestMapS3ActionToIAMAction(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name     string
							 | 
						|
										s3Action Action
							 | 
						|
										expected string
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											name:     "read action",
							 | 
						|
											s3Action: "READ", // Assuming this is defined in s3_constants
							 | 
						|
											expected: "READ", // Will fallback to string representation
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "write action",
							 | 
						|
											s3Action: "WRITE",
							 | 
						|
											expected: "WRITE",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:     "list action",
							 | 
						|
											s3Action: "LIST",
							 | 
						|
											expected: "LIST",
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											result := mapS3ActionToIAMAction(tt.s3Action)
							 | 
						|
											assert.Equal(t, tt.expected, result)
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestExtractSourceIP tests source IP extraction from requests
							 | 
						|
								func TestExtractSourceIP(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name       string
							 | 
						|
										setupReq   func() *http.Request
							 | 
						|
										expectedIP string
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											name: "X-Forwarded-For header",
							 | 
						|
											setupReq: func() *http.Request {
							 | 
						|
												req := httptest.NewRequest("GET", "/test", http.NoBody)
							 | 
						|
												req.Header.Set("X-Forwarded-For", "192.168.1.100, 10.0.0.1")
							 | 
						|
												return req
							 | 
						|
											},
							 | 
						|
											expectedIP: "192.168.1.100",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "X-Real-IP header",
							 | 
						|
											setupReq: func() *http.Request {
							 | 
						|
												req := httptest.NewRequest("GET", "/test", http.NoBody)
							 | 
						|
												req.Header.Set("X-Real-IP", "192.168.1.200")
							 | 
						|
												return req
							 | 
						|
											},
							 | 
						|
											expectedIP: "192.168.1.200",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name: "RemoteAddr fallback",
							 | 
						|
											setupReq: func() *http.Request {
							 | 
						|
												req := httptest.NewRequest("GET", "/test", http.NoBody)
							 | 
						|
												req.RemoteAddr = "192.168.1.300:12345"
							 | 
						|
												return req
							 | 
						|
											},
							 | 
						|
											expectedIP: "192.168.1.300",
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											req := tt.setupReq()
							 | 
						|
											result := extractSourceIP(req)
							 | 
						|
											assert.Equal(t, tt.expectedIP, result)
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestExtractRoleNameFromPrincipal tests role name extraction
							 | 
						|
								func TestExtractRoleNameFromPrincipal(t *testing.T) {
							 | 
						|
									tests := []struct {
							 | 
						|
										name      string
							 | 
						|
										principal string
							 | 
						|
										expected  string
							 | 
						|
									}{
							 | 
						|
										{
							 | 
						|
											name:      "valid assumed role ARN",
							 | 
						|
											principal: "arn:seaweed:sts::assumed-role/S3ReadOnlyRole/session-123",
							 | 
						|
											expected:  "S3ReadOnlyRole",
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:      "invalid format",
							 | 
						|
											principal: "invalid-principal",
							 | 
						|
											expected:  "invalid-principal", // Returns original on failure
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:      "missing session name",
							 | 
						|
											principal: "arn:seaweed:sts::assumed-role/TestRole",
							 | 
						|
											expected:  "arn:seaweed:sts::assumed-role/TestRole", // Returns original on failure
							 | 
						|
										},
							 | 
						|
										{
							 | 
						|
											name:      "empty principal",
							 | 
						|
											principal: "",
							 | 
						|
											expected:  "",
							 | 
						|
										},
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									for _, tt := range tests {
							 | 
						|
										t.Run(tt.name, func(t *testing.T) {
							 | 
						|
											result := extractRoleNameFromPrincipal(tt.principal)
							 | 
						|
											assert.Equal(t, tt.expected, result)
							 | 
						|
										})
							 | 
						|
									}
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// TestIAMIdentityIsAdmin tests the IsAdmin method
							 | 
						|
								func TestIAMIdentityIsAdmin(t *testing.T) {
							 | 
						|
									identity := &IAMIdentity{
							 | 
						|
										Name:         "test-identity",
							 | 
						|
										Principal:    "arn:seaweed:sts::assumed-role/TestRole/session",
							 | 
						|
										SessionToken: "test-token",
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// In our implementation, IsAdmin always returns false since admin status
							 | 
						|
									// is determined by policies, not identity
							 | 
						|
									result := identity.IsAdmin()
							 | 
						|
									assert.False(t, result)
							 | 
						|
								}
							 |