Browse Source
feat: implement stateless JWT-only STS architecture
feat: implement stateless JWT-only STS architecture
This major refactoring eliminates all session storage complexity and enables true distributed operation without shared state. All session information is now embedded directly into JWT tokens. Key Changes: Enhanced JWT Claims Structure: - New STSSessionClaims struct with comprehensive session information - Embedded role info, identity provider details, policies, and context - Backward-compatible SessionInfo conversion methods - Built-in validation and utility methods Stateless Token Generator: - Enhanced TokenGenerator with rich JWT claims support - New GenerateJWTWithClaims method for comprehensive tokens - Updated ValidateJWTWithClaims for full session extraction - Maintains backward compatibility with existing methods Completely Stateless STS Service: - Removed SessionStore dependency entirely - Updated all methods to be stateless JWT-only operations - AssumeRoleWithWebIdentity embeds all session info in JWT - AssumeRoleWithCredentials embeds all session info in JWT - ValidateSessionToken extracts everything from JWT token - RevokeSession now validates tokens but cannot truly revoke them Updated Method Signatures: - Removed filerAddress parameters from all STS methods - Simplified AssumeRoleWithWebIdentity, AssumeRoleWithCredentials - Simplified ValidateSessionToken, RevokeSession - Simplified ExpireSessionForTesting Benefits: - True distributed compatibility without shared state - Simplified architecture, no session storage layer - Better performance, no database lookups - Improved security with cryptographically signed tokens - Perfect horizontal scaling Notes: - Stateless tokens cannot be revoked without blacklist - Recommend short-lived tokens for security - All tests updated and passing - Backward compatibility maintained where possiblepull/7160/head
8 changed files with 332 additions and 196 deletions
-
14weed/iam/integration/iam_integration_test.go
-
16weed/iam/integration/iam_manager.go
-
21weed/iam/sts/cross_instance_token_test.go
-
146weed/iam/sts/session_claims.go
-
257weed/iam/sts/sts_service.go
-
9weed/iam/sts/sts_service_test.go
-
61weed/iam/sts/token_utils.go
-
4weed/s3api/s3_iam_middleware.go
@ -0,0 +1,146 @@ |
|||
package sts |
|||
|
|||
import ( |
|||
"time" |
|||
|
|||
"github.com/golang-jwt/jwt/v5" |
|||
) |
|||
|
|||
// STSSessionClaims represents comprehensive session information embedded in JWT tokens
|
|||
// This eliminates the need for separate session storage by embedding all session
|
|||
// metadata directly in the token itself - enabling true stateless operation
|
|||
type STSSessionClaims struct { |
|||
jwt.RegisteredClaims |
|||
|
|||
// Session identification
|
|||
SessionId string `json:"sid"` // session_id (abbreviated for smaller tokens)
|
|||
TokenType string `json:"typ"` // token_type
|
|||
|
|||
// Role information
|
|||
RoleArn string `json:"role"` // role_arn
|
|||
AssumedRole string `json:"assumed"` // assumed_role_user
|
|||
Principal string `json:"principal"` // principal_arn
|
|||
|
|||
// Authorization data
|
|||
Policies []string `json:"pol,omitempty"` // policies (abbreviated)
|
|||
|
|||
// Identity provider information
|
|||
IdentityProvider string `json:"idp"` // identity_provider
|
|||
ExternalUserId string `json:"ext_uid"` // external_user_id
|
|||
ProviderIssuer string `json:"prov_iss"` // provider_issuer
|
|||
|
|||
// Request context (optional, for policy evaluation)
|
|||
RequestContext map[string]interface{} `json:"req_ctx,omitempty"` |
|||
|
|||
// Session metadata
|
|||
AssumedAt time.Time `json:"assumed_at"` // when role was assumed
|
|||
MaxDuration int64 `json:"max_dur,omitempty"` // maximum session duration in seconds
|
|||
} |
|||
|
|||
// NewSTSSessionClaims creates new STS session claims with all required information
|
|||
func NewSTSSessionClaims(sessionId, issuer string, expiresAt time.Time) *STSSessionClaims { |
|||
now := time.Now() |
|||
return &STSSessionClaims{ |
|||
RegisteredClaims: jwt.RegisteredClaims{ |
|||
Issuer: issuer, |
|||
Subject: sessionId, |
|||
IssuedAt: jwt.NewNumericDate(now), |
|||
ExpiresAt: jwt.NewNumericDate(expiresAt), |
|||
NotBefore: jwt.NewNumericDate(now), |
|||
}, |
|||
SessionId: sessionId, |
|||
TokenType: TokenTypeSession, |
|||
AssumedAt: now, |
|||
} |
|||
} |
|||
|
|||
// ToSessionInfo converts JWT claims back to SessionInfo structure
|
|||
// This enables seamless integration with existing code expecting SessionInfo
|
|||
func (c *STSSessionClaims) ToSessionInfo() *SessionInfo { |
|||
var expiresAt time.Time |
|||
if c.ExpiresAt != nil { |
|||
expiresAt = c.ExpiresAt.Time |
|||
} |
|||
|
|||
return &SessionInfo{ |
|||
SessionId: c.SessionId, |
|||
RoleArn: c.RoleArn, |
|||
AssumedRoleUser: c.AssumedRole, |
|||
Principal: c.Principal, |
|||
Policies: c.Policies, |
|||
ExpiresAt: expiresAt, |
|||
IdentityProvider: c.IdentityProvider, |
|||
ExternalUserId: c.ExternalUserId, |
|||
ProviderIssuer: c.ProviderIssuer, |
|||
RequestContext: c.RequestContext, |
|||
} |
|||
} |
|||
|
|||
// IsValid checks if the session claims are valid (not expired, etc.)
|
|||
func (c *STSSessionClaims) IsValid() bool { |
|||
now := time.Now() |
|||
|
|||
// Check expiration
|
|||
if c.ExpiresAt != nil && c.ExpiresAt.Before(now) { |
|||
return false |
|||
} |
|||
|
|||
// Check not-before
|
|||
if c.NotBefore != nil && c.NotBefore.After(now) { |
|||
return false |
|||
} |
|||
|
|||
// Ensure required fields are present
|
|||
if c.SessionId == "" || c.RoleArn == "" || c.Principal == "" { |
|||
return false |
|||
} |
|||
|
|||
return true |
|||
} |
|||
|
|||
// GetSessionId returns the session identifier
|
|||
func (c *STSSessionClaims) GetSessionId() string { |
|||
return c.SessionId |
|||
} |
|||
|
|||
// GetExpiresAt returns the expiration time
|
|||
func (c *STSSessionClaims) GetExpiresAt() time.Time { |
|||
if c.ExpiresAt != nil { |
|||
return c.ExpiresAt.Time |
|||
} |
|||
return time.Time{} |
|||
} |
|||
|
|||
// WithRoleInfo sets role-related information in the claims
|
|||
func (c *STSSessionClaims) WithRoleInfo(roleArn, assumedRole, principal string) *STSSessionClaims { |
|||
c.RoleArn = roleArn |
|||
c.AssumedRole = assumedRole |
|||
c.Principal = principal |
|||
return c |
|||
} |
|||
|
|||
// WithPolicies sets the policies associated with this session
|
|||
func (c *STSSessionClaims) WithPolicies(policies []string) *STSSessionClaims { |
|||
c.Policies = policies |
|||
return c |
|||
} |
|||
|
|||
// WithIdentityProvider sets identity provider information
|
|||
func (c *STSSessionClaims) WithIdentityProvider(providerName, externalUserId, providerIssuer string) *STSSessionClaims { |
|||
c.IdentityProvider = providerName |
|||
c.ExternalUserId = externalUserId |
|||
c.ProviderIssuer = providerIssuer |
|||
return c |
|||
} |
|||
|
|||
// WithRequestContext sets request context for policy evaluation
|
|||
func (c *STSSessionClaims) WithRequestContext(ctx map[string]interface{}) *STSSessionClaims { |
|||
c.RequestContext = ctx |
|||
return c |
|||
} |
|||
|
|||
// WithMaxDuration sets the maximum session duration
|
|||
func (c *STSSessionClaims) WithMaxDuration(duration time.Duration) *STSSessionClaims { |
|||
c.MaxDuration = int64(duration.Seconds()) |
|||
return c |
|||
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue