Browse Source

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
pull/7727/head
Chris Lu 4 days ago
committed by GitHub
parent
commit
b0e0c5aaab
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 5
      weed/s3api/auth_credentials.go
  2. 153
      weed/s3api/s3_end_to_end_test.go

5
weed/s3api/auth_credentials.go

@ -771,6 +771,11 @@ func (iam *IdentityAccessManagement) SetIAMIntegration(integration *S3IAMIntegra
iam.m.Lock()
defer iam.m.Unlock()
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

153
weed/s3api/s3_end_to_end_test.go

@ -6,6 +6,7 @@ import (
"fmt"
"net/http"
"net/http/httptest"
"sync"
"testing"
"time"
@ -654,3 +655,155 @@ func executeS3OperationWithJWT(t *testing.T, s3Server http.Handler, operation S3
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")
}
Loading…
Cancel
Save