s3: enable auth when IAM integration is configured (#7726)
When only IAM integration is configured (via -s3.iam.config) without traditional S3 identities, the isAuthEnabled flag was not being set, causing the Auth middleware to bypass all authentication checks. This fix ensures that when SetIAMIntegration is called with a non-nil integration, isAuthEnabled is set to true, properly enforcing authentication for all requests. Added negative authentication tests: - TestS3AuthenticationDenied: tests rejection of unauthenticated, invalid, and expired JWT requests - TestS3IAMOnlyModeRejectsAnonymous: tests that IAM-only mode properly rejects anonymous requests Fixes #7724
This commit is contained in:
@@ -771,6 +771,11 @@ func (iam *IdentityAccessManagement) SetIAMIntegration(integration *S3IAMIntegra
|
|||||||
iam.m.Lock()
|
iam.m.Lock()
|
||||||
defer iam.m.Unlock()
|
defer iam.m.Unlock()
|
||||||
iam.iamIntegration = integration
|
iam.iamIntegration = integration
|
||||||
|
// When IAM integration is configured, authentication must be enabled
|
||||||
|
// to ensure requests go through proper auth checks
|
||||||
|
if integration != nil {
|
||||||
|
iam.isAuthEnabled = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// authenticateJWTWithIAM authenticates JWT tokens using the IAM integration
|
// authenticateJWTWithIAM authenticates JWT tokens using the IAM integration
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -654,3 +655,155 @@ func executeS3OperationWithJWT(t *testing.T, s3Server http.Handler, operation S3
|
|||||||
|
|
||||||
return allowed
|
return allowed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestS3AuthenticationDenied tests that unauthenticated and invalid requests are properly rejected
|
||||||
|
func TestS3AuthenticationDenied(t *testing.T) {
|
||||||
|
s3Server, _ := setupCompleteS3IAMSystem(t)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
setupRequest func() *http.Request
|
||||||
|
expectedStatus int
|
||||||
|
description string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no_authorization_header",
|
||||||
|
setupRequest: func() *http.Request {
|
||||||
|
req := httptest.NewRequest("GET", "/test-auth", nil)
|
||||||
|
// No Authorization header
|
||||||
|
return req
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
description: "Request without Authorization header should be rejected",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty_bearer_token",
|
||||||
|
setupRequest: func() *http.Request {
|
||||||
|
req := httptest.NewRequest("GET", "/test-auth", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer ")
|
||||||
|
return req
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
description: "Request with empty Bearer token should be rejected",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid_jwt_token",
|
||||||
|
setupRequest: func() *http.Request {
|
||||||
|
req := httptest.NewRequest("GET", "/test-auth", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer invalid.jwt.token")
|
||||||
|
return req
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
description: "Request with invalid JWT token should be rejected",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "malformed_authorization_header",
|
||||||
|
setupRequest: func() *http.Request {
|
||||||
|
req := httptest.NewRequest("GET", "/test-auth", nil)
|
||||||
|
req.Header.Set("Authorization", "NotBearer sometoken")
|
||||||
|
return req
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
description: "Request with malformed Authorization header should be rejected",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "expired_jwt_token",
|
||||||
|
setupRequest: func() *http.Request {
|
||||||
|
// Create an expired JWT token
|
||||||
|
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||||
|
"iss": "https://test-issuer.com",
|
||||||
|
"sub": "test-user",
|
||||||
|
"exp": time.Now().Add(-time.Hour).Unix(), // Expired 1 hour ago
|
||||||
|
"iat": time.Now().Add(-2 * time.Hour).Unix(),
|
||||||
|
})
|
||||||
|
tokenString, _ := token.SignedString([]byte("test-signing-key"))
|
||||||
|
|
||||||
|
req := httptest.NewRequest("GET", "/test-auth", nil)
|
||||||
|
req.Header.Set("Authorization", "Bearer "+tokenString)
|
||||||
|
return req
|
||||||
|
},
|
||||||
|
expectedStatus: http.StatusUnauthorized,
|
||||||
|
description: "Request with expired JWT token should be rejected",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
req := tt.setupRequest()
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
s3Server.ServeHTTP(recorder, req)
|
||||||
|
|
||||||
|
// Verify the request was rejected with the expected status
|
||||||
|
assert.Equal(t, tt.expectedStatus, recorder.Code,
|
||||||
|
"%s: expected status %d but got %d. Response: %s",
|
||||||
|
tt.description, tt.expectedStatus, recorder.Code, recorder.Body.String())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestS3IAMOnlyModeRejectsAnonymous tests that when only IAM is configured
|
||||||
|
// (no traditional identities), anonymous requests are properly denied
|
||||||
|
func TestS3IAMOnlyModeRejectsAnonymous(t *testing.T) {
|
||||||
|
// Create IAM with NO traditional identities (simulating IAM-only setup)
|
||||||
|
iam := &IdentityAccessManagement{
|
||||||
|
identities: []*Identity{},
|
||||||
|
accessKeyIdent: make(map[string]*Identity),
|
||||||
|
nameToIdentity: make(map[string]*Identity),
|
||||||
|
accounts: make(map[string]*Account),
|
||||||
|
emailAccount: make(map[string]*Account),
|
||||||
|
hashes: make(map[string]*sync.Pool),
|
||||||
|
hashCounters: make(map[string]*int32),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up IAM integration
|
||||||
|
iamManager := integration.NewIAMManager()
|
||||||
|
config := &integration.IAMConfig{
|
||||||
|
STS: &sts.STSConfig{
|
||||||
|
TokenDuration: sts.FlexibleDuration{Duration: time.Hour},
|
||||||
|
MaxSessionLength: sts.FlexibleDuration{Duration: time.Hour * 12},
|
||||||
|
Issuer: "test-sts",
|
||||||
|
SigningKey: []byte("test-signing-key-32-characters-long"),
|
||||||
|
},
|
||||||
|
Policy: &policy.PolicyEngineConfig{
|
||||||
|
DefaultEffect: "Deny",
|
||||||
|
StoreType: "memory",
|
||||||
|
},
|
||||||
|
Roles: &integration.RoleStoreConfig{
|
||||||
|
StoreType: "memory",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
err := iamManager.Initialize(config, func() string {
|
||||||
|
return "localhost:8888"
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
s3IAMIntegration := NewS3IAMIntegration(iamManager, "localhost:8888")
|
||||||
|
require.NotNil(t, s3IAMIntegration)
|
||||||
|
|
||||||
|
// Set IAM integration - this should enable auth
|
||||||
|
iam.SetIAMIntegration(s3IAMIntegration)
|
||||||
|
|
||||||
|
// Verify auth is enabled
|
||||||
|
require.True(t, iam.isEnabled(), "Auth must be enabled when IAM integration is configured")
|
||||||
|
|
||||||
|
// Test that the Auth middleware blocks unauthenticated requests
|
||||||
|
handlerCalled := false
|
||||||
|
testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
handlerCalled = true
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wrap with auth middleware
|
||||||
|
wrappedHandler := iam.Auth(testHandler, "Write")
|
||||||
|
|
||||||
|
// Create an unauthenticated request
|
||||||
|
req := httptest.NewRequest("PUT", "/mybucket/test.txt", nil)
|
||||||
|
rr := httptest.NewRecorder()
|
||||||
|
|
||||||
|
wrappedHandler.ServeHTTP(rr, req)
|
||||||
|
|
||||||
|
// Handler should NOT have been called
|
||||||
|
assert.False(t, handlerCalled, "Handler should not be called for unauthenticated request")
|
||||||
|
assert.NotEqual(t, http.StatusOK, rr.Code, "Unauthenticated request should not return 200 OK")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user