sts_service.go 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826
  1. package sts
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "strconv"
  7. "time"
  8. "github.com/golang-jwt/jwt/v5"
  9. "github.com/seaweedfs/seaweedfs/weed/glog"
  10. "github.com/seaweedfs/seaweedfs/weed/iam/providers"
  11. "github.com/seaweedfs/seaweedfs/weed/iam/utils"
  12. )
  13. // TrustPolicyValidator interface for validating trust policies during role assumption
  14. type TrustPolicyValidator interface {
  15. // ValidateTrustPolicyForWebIdentity validates if a web identity token can assume a role
  16. ValidateTrustPolicyForWebIdentity(ctx context.Context, roleArn string, webIdentityToken string) error
  17. // ValidateTrustPolicyForCredentials validates if credentials can assume a role
  18. ValidateTrustPolicyForCredentials(ctx context.Context, roleArn string, identity *providers.ExternalIdentity) error
  19. }
  20. // FlexibleDuration wraps time.Duration to support both integer nanoseconds and duration strings in JSON
  21. type FlexibleDuration struct {
  22. time.Duration
  23. }
  24. // UnmarshalJSON implements JSON unmarshaling for FlexibleDuration
  25. // Supports both: 3600000000000 (nanoseconds) and "1h" (duration string)
  26. func (fd *FlexibleDuration) UnmarshalJSON(data []byte) error {
  27. // Try to unmarshal as a duration string first (e.g., "1h", "30m")
  28. var durationStr string
  29. if err := json.Unmarshal(data, &durationStr); err == nil {
  30. duration, parseErr := time.ParseDuration(durationStr)
  31. if parseErr != nil {
  32. return fmt.Errorf("invalid duration string %q: %w", durationStr, parseErr)
  33. }
  34. fd.Duration = duration
  35. return nil
  36. }
  37. // If that fails, try to unmarshal as an integer (nanoseconds for backward compatibility)
  38. var nanoseconds int64
  39. if err := json.Unmarshal(data, &nanoseconds); err == nil {
  40. fd.Duration = time.Duration(nanoseconds)
  41. return nil
  42. }
  43. // If both fail, try unmarshaling as a quoted number string (edge case)
  44. var numberStr string
  45. if err := json.Unmarshal(data, &numberStr); err == nil {
  46. if nanoseconds, parseErr := strconv.ParseInt(numberStr, 10, 64); parseErr == nil {
  47. fd.Duration = time.Duration(nanoseconds)
  48. return nil
  49. }
  50. }
  51. return fmt.Errorf("unable to parse duration from %s (expected duration string like \"1h\" or integer nanoseconds)", data)
  52. }
  53. // MarshalJSON implements JSON marshaling for FlexibleDuration
  54. // Always marshals as a human-readable duration string
  55. func (fd FlexibleDuration) MarshalJSON() ([]byte, error) {
  56. return json.Marshal(fd.Duration.String())
  57. }
  58. // STSService provides Security Token Service functionality
  59. // This service is now completely stateless - all session information is embedded
  60. // in JWT tokens, eliminating the need for session storage and enabling true
  61. // distributed operation without shared state
  62. type STSService struct {
  63. Config *STSConfig // Public for access by other components
  64. initialized bool
  65. providers map[string]providers.IdentityProvider
  66. issuerToProvider map[string]providers.IdentityProvider // Efficient issuer-based provider lookup
  67. tokenGenerator *TokenGenerator
  68. trustPolicyValidator TrustPolicyValidator // Interface for trust policy validation
  69. }
  70. // STSConfig holds STS service configuration
  71. type STSConfig struct {
  72. // TokenDuration is the default duration for issued tokens
  73. TokenDuration FlexibleDuration `json:"tokenDuration"`
  74. // MaxSessionLength is the maximum duration for any session
  75. MaxSessionLength FlexibleDuration `json:"maxSessionLength"`
  76. // Issuer is the STS issuer identifier
  77. Issuer string `json:"issuer"`
  78. // SigningKey is used to sign session tokens
  79. SigningKey []byte `json:"signingKey"`
  80. // Providers configuration - enables automatic provider loading
  81. Providers []*ProviderConfig `json:"providers,omitempty"`
  82. }
  83. // ProviderConfig holds identity provider configuration
  84. type ProviderConfig struct {
  85. // Name is the unique identifier for the provider
  86. Name string `json:"name"`
  87. // Type specifies the provider type (oidc, ldap, etc.)
  88. Type string `json:"type"`
  89. // Config contains provider-specific configuration
  90. Config map[string]interface{} `json:"config"`
  91. // Enabled indicates if this provider should be active
  92. Enabled bool `json:"enabled"`
  93. }
  94. // AssumeRoleWithWebIdentityRequest represents a request to assume role with web identity
  95. type AssumeRoleWithWebIdentityRequest struct {
  96. // RoleArn is the ARN of the role to assume
  97. RoleArn string `json:"RoleArn"`
  98. // WebIdentityToken is the OIDC token from the identity provider
  99. WebIdentityToken string `json:"WebIdentityToken"`
  100. // RoleSessionName is a name for the assumed role session
  101. RoleSessionName string `json:"RoleSessionName"`
  102. // DurationSeconds is the duration of the role session (optional)
  103. DurationSeconds *int64 `json:"DurationSeconds,omitempty"`
  104. // Policy is an optional session policy (optional)
  105. Policy *string `json:"Policy,omitempty"`
  106. }
  107. // AssumeRoleWithCredentialsRequest represents a request to assume role with username/password
  108. type AssumeRoleWithCredentialsRequest struct {
  109. // RoleArn is the ARN of the role to assume
  110. RoleArn string `json:"RoleArn"`
  111. // Username is the username for authentication
  112. Username string `json:"Username"`
  113. // Password is the password for authentication
  114. Password string `json:"Password"`
  115. // RoleSessionName is a name for the assumed role session
  116. RoleSessionName string `json:"RoleSessionName"`
  117. // ProviderName is the name of the identity provider to use
  118. ProviderName string `json:"ProviderName"`
  119. // DurationSeconds is the duration of the role session (optional)
  120. DurationSeconds *int64 `json:"DurationSeconds,omitempty"`
  121. }
  122. // AssumeRoleResponse represents the response from assume role operations
  123. type AssumeRoleResponse struct {
  124. // Credentials contains the temporary security credentials
  125. Credentials *Credentials `json:"Credentials"`
  126. // AssumedRoleUser contains information about the assumed role user
  127. AssumedRoleUser *AssumedRoleUser `json:"AssumedRoleUser"`
  128. // PackedPolicySize is the percentage of max policy size used (AWS compatibility)
  129. PackedPolicySize *int64 `json:"PackedPolicySize,omitempty"`
  130. }
  131. // Credentials represents temporary security credentials
  132. type Credentials struct {
  133. // AccessKeyId is the access key ID
  134. AccessKeyId string `json:"AccessKeyId"`
  135. // SecretAccessKey is the secret access key
  136. SecretAccessKey string `json:"SecretAccessKey"`
  137. // SessionToken is the session token
  138. SessionToken string `json:"SessionToken"`
  139. // Expiration is when the credentials expire
  140. Expiration time.Time `json:"Expiration"`
  141. }
  142. // AssumedRoleUser contains information about the assumed role user
  143. type AssumedRoleUser struct {
  144. // AssumedRoleId is the unique identifier of the assumed role
  145. AssumedRoleId string `json:"AssumedRoleId"`
  146. // Arn is the ARN of the assumed role user
  147. Arn string `json:"Arn"`
  148. // Subject is the subject identifier from the identity provider
  149. Subject string `json:"Subject,omitempty"`
  150. }
  151. // SessionInfo represents information about an active session
  152. type SessionInfo struct {
  153. // SessionId is the unique identifier for the session
  154. SessionId string `json:"sessionId"`
  155. // SessionName is the name of the role session
  156. SessionName string `json:"sessionName"`
  157. // RoleArn is the ARN of the assumed role
  158. RoleArn string `json:"roleArn"`
  159. // AssumedRoleUser contains information about the assumed role user
  160. AssumedRoleUser string `json:"assumedRoleUser"`
  161. // Principal is the principal ARN
  162. Principal string `json:"principal"`
  163. // Subject is the subject identifier from the identity provider
  164. Subject string `json:"subject"`
  165. // Provider is the identity provider used (legacy field)
  166. Provider string `json:"provider"`
  167. // IdentityProvider is the identity provider used
  168. IdentityProvider string `json:"identityProvider"`
  169. // ExternalUserId is the external user identifier from the provider
  170. ExternalUserId string `json:"externalUserId"`
  171. // ProviderIssuer is the issuer from the identity provider
  172. ProviderIssuer string `json:"providerIssuer"`
  173. // Policies are the policies associated with this session
  174. Policies []string `json:"policies"`
  175. // RequestContext contains additional request context for policy evaluation
  176. RequestContext map[string]interface{} `json:"requestContext,omitempty"`
  177. // CreatedAt is when the session was created
  178. CreatedAt time.Time `json:"createdAt"`
  179. // ExpiresAt is when the session expires
  180. ExpiresAt time.Time `json:"expiresAt"`
  181. // Credentials are the temporary credentials for this session
  182. Credentials *Credentials `json:"credentials"`
  183. }
  184. // NewSTSService creates a new STS service
  185. func NewSTSService() *STSService {
  186. return &STSService{
  187. providers: make(map[string]providers.IdentityProvider),
  188. issuerToProvider: make(map[string]providers.IdentityProvider),
  189. }
  190. }
  191. // Initialize initializes the STS service with configuration
  192. func (s *STSService) Initialize(config *STSConfig) error {
  193. if config == nil {
  194. return fmt.Errorf(ErrConfigCannotBeNil)
  195. }
  196. if err := s.validateConfig(config); err != nil {
  197. return fmt.Errorf("invalid STS configuration: %w", err)
  198. }
  199. s.Config = config
  200. // Initialize token generator for stateless JWT operations
  201. s.tokenGenerator = NewTokenGenerator(config.SigningKey, config.Issuer)
  202. // Load identity providers from configuration
  203. if err := s.loadProvidersFromConfig(config); err != nil {
  204. return fmt.Errorf("failed to load identity providers: %w", err)
  205. }
  206. s.initialized = true
  207. return nil
  208. }
  209. // validateConfig validates the STS configuration
  210. func (s *STSService) validateConfig(config *STSConfig) error {
  211. if config.TokenDuration.Duration <= 0 {
  212. return fmt.Errorf(ErrInvalidTokenDuration)
  213. }
  214. if config.MaxSessionLength.Duration <= 0 {
  215. return fmt.Errorf(ErrInvalidMaxSessionLength)
  216. }
  217. if config.Issuer == "" {
  218. return fmt.Errorf(ErrIssuerRequired)
  219. }
  220. if len(config.SigningKey) < MinSigningKeyLength {
  221. return fmt.Errorf(ErrSigningKeyTooShort, MinSigningKeyLength)
  222. }
  223. return nil
  224. }
  225. // loadProvidersFromConfig loads identity providers from configuration
  226. func (s *STSService) loadProvidersFromConfig(config *STSConfig) error {
  227. if len(config.Providers) == 0 {
  228. glog.V(2).Infof("No providers configured in STS config")
  229. return nil
  230. }
  231. factory := NewProviderFactory()
  232. // Load all providers from configuration
  233. providersMap, err := factory.LoadProvidersFromConfig(config.Providers)
  234. if err != nil {
  235. return fmt.Errorf("failed to load providers from config: %w", err)
  236. }
  237. // Replace current providers with new ones
  238. s.providers = providersMap
  239. // Also populate the issuerToProvider map for efficient and secure JWT validation
  240. s.issuerToProvider = make(map[string]providers.IdentityProvider)
  241. for name, provider := range s.providers {
  242. issuer := s.extractIssuerFromProvider(provider)
  243. if issuer != "" {
  244. if _, exists := s.issuerToProvider[issuer]; exists {
  245. glog.Warningf("Duplicate issuer %s found for provider %s. Overwriting.", issuer, name)
  246. }
  247. s.issuerToProvider[issuer] = provider
  248. glog.V(2).Infof("Registered provider %s with issuer %s for efficient lookup", name, issuer)
  249. }
  250. }
  251. glog.V(1).Infof("Successfully loaded %d identity providers: %v",
  252. len(s.providers), s.getProviderNames())
  253. return nil
  254. }
  255. // getProviderNames returns list of loaded provider names
  256. func (s *STSService) getProviderNames() []string {
  257. names := make([]string, 0, len(s.providers))
  258. for name := range s.providers {
  259. names = append(names, name)
  260. }
  261. return names
  262. }
  263. // IsInitialized returns whether the service is initialized
  264. func (s *STSService) IsInitialized() bool {
  265. return s.initialized
  266. }
  267. // RegisterProvider registers an identity provider
  268. func (s *STSService) RegisterProvider(provider providers.IdentityProvider) error {
  269. if provider == nil {
  270. return fmt.Errorf(ErrProviderCannotBeNil)
  271. }
  272. name := provider.Name()
  273. if name == "" {
  274. return fmt.Errorf(ErrProviderNameEmpty)
  275. }
  276. s.providers[name] = provider
  277. // Try to extract issuer information for efficient lookup
  278. // This is a best-effort approach for different provider types
  279. issuer := s.extractIssuerFromProvider(provider)
  280. if issuer != "" {
  281. s.issuerToProvider[issuer] = provider
  282. glog.V(2).Infof("Registered provider %s with issuer %s for efficient lookup", name, issuer)
  283. }
  284. return nil
  285. }
  286. // extractIssuerFromProvider attempts to extract issuer information from different provider types
  287. func (s *STSService) extractIssuerFromProvider(provider providers.IdentityProvider) string {
  288. // Handle different provider types
  289. switch p := provider.(type) {
  290. case interface{ GetIssuer() string }:
  291. // For providers that implement GetIssuer() method
  292. return p.GetIssuer()
  293. default:
  294. // For other provider types, we'll rely on JWT parsing during validation
  295. // This is still more efficient than the current brute-force approach
  296. return ""
  297. }
  298. }
  299. // GetProviders returns all registered identity providers
  300. func (s *STSService) GetProviders() map[string]providers.IdentityProvider {
  301. return s.providers
  302. }
  303. // SetTrustPolicyValidator sets the trust policy validator for role assumption validation
  304. func (s *STSService) SetTrustPolicyValidator(validator TrustPolicyValidator) {
  305. s.trustPolicyValidator = validator
  306. }
  307. // AssumeRoleWithWebIdentity assumes a role using a web identity token (OIDC)
  308. // This method is now completely stateless - all session information is embedded in the JWT token
  309. func (s *STSService) AssumeRoleWithWebIdentity(ctx context.Context, request *AssumeRoleWithWebIdentityRequest) (*AssumeRoleResponse, error) {
  310. if !s.initialized {
  311. return nil, fmt.Errorf(ErrSTSServiceNotInitialized)
  312. }
  313. if request == nil {
  314. return nil, fmt.Errorf("request cannot be nil")
  315. }
  316. // Validate request parameters
  317. if err := s.validateAssumeRoleWithWebIdentityRequest(request); err != nil {
  318. return nil, fmt.Errorf("invalid request: %w", err)
  319. }
  320. // Check for unsupported session policy
  321. if request.Policy != nil {
  322. return nil, fmt.Errorf("session policies are not currently supported - Policy parameter must be omitted")
  323. }
  324. // 1. Validate the web identity token with appropriate provider
  325. externalIdentity, provider, err := s.validateWebIdentityToken(ctx, request.WebIdentityToken)
  326. if err != nil {
  327. return nil, fmt.Errorf("failed to validate web identity token: %w", err)
  328. }
  329. // 2. Check if the role exists and can be assumed (includes trust policy validation)
  330. if err := s.validateRoleAssumptionForWebIdentity(ctx, request.RoleArn, request.WebIdentityToken); err != nil {
  331. return nil, fmt.Errorf("role assumption denied: %w", err)
  332. }
  333. // 3. Calculate session duration
  334. sessionDuration := s.calculateSessionDuration(request.DurationSeconds)
  335. expiresAt := time.Now().Add(sessionDuration)
  336. // 4. Generate session ID and credentials
  337. sessionId, err := GenerateSessionId()
  338. if err != nil {
  339. return nil, fmt.Errorf("failed to generate session ID: %w", err)
  340. }
  341. credGenerator := NewCredentialGenerator()
  342. credentials, err := credGenerator.GenerateTemporaryCredentials(sessionId, expiresAt)
  343. if err != nil {
  344. return nil, fmt.Errorf("failed to generate credentials: %w", err)
  345. }
  346. // 5. Create comprehensive JWT session token with all session information embedded
  347. assumedRoleUser := &AssumedRoleUser{
  348. AssumedRoleId: request.RoleArn,
  349. Arn: GenerateAssumedRoleArn(request.RoleArn, request.RoleSessionName),
  350. Subject: externalIdentity.UserID,
  351. }
  352. // Create rich JWT claims with all session information
  353. sessionClaims := NewSTSSessionClaims(sessionId, s.Config.Issuer, expiresAt).
  354. WithSessionName(request.RoleSessionName).
  355. WithRoleInfo(request.RoleArn, assumedRoleUser.Arn, assumedRoleUser.Arn).
  356. WithIdentityProvider(provider.Name(), externalIdentity.UserID, "").
  357. WithMaxDuration(sessionDuration)
  358. // Generate self-contained JWT token with all session information
  359. jwtToken, err := s.tokenGenerator.GenerateJWTWithClaims(sessionClaims)
  360. if err != nil {
  361. return nil, fmt.Errorf("failed to generate JWT session token: %w", err)
  362. }
  363. credentials.SessionToken = jwtToken
  364. // 6. Build and return response (no session storage needed!)
  365. return &AssumeRoleResponse{
  366. Credentials: credentials,
  367. AssumedRoleUser: assumedRoleUser,
  368. }, nil
  369. }
  370. // AssumeRoleWithCredentials assumes a role using username/password credentials
  371. // This method is now completely stateless - all session information is embedded in the JWT token
  372. func (s *STSService) AssumeRoleWithCredentials(ctx context.Context, request *AssumeRoleWithCredentialsRequest) (*AssumeRoleResponse, error) {
  373. if !s.initialized {
  374. return nil, fmt.Errorf("STS service not initialized")
  375. }
  376. if request == nil {
  377. return nil, fmt.Errorf("request cannot be nil")
  378. }
  379. // Validate request parameters
  380. if err := s.validateAssumeRoleWithCredentialsRequest(request); err != nil {
  381. return nil, fmt.Errorf("invalid request: %w", err)
  382. }
  383. // 1. Get the specified provider
  384. provider, exists := s.providers[request.ProviderName]
  385. if !exists {
  386. return nil, fmt.Errorf("identity provider not found: %s", request.ProviderName)
  387. }
  388. // 2. Validate credentials with the specified provider
  389. credentials := request.Username + ":" + request.Password
  390. externalIdentity, err := provider.Authenticate(ctx, credentials)
  391. if err != nil {
  392. return nil, fmt.Errorf("failed to authenticate credentials: %w", err)
  393. }
  394. // 3. Check if the role exists and can be assumed (includes trust policy validation)
  395. if err := s.validateRoleAssumptionForCredentials(ctx, request.RoleArn, externalIdentity); err != nil {
  396. return nil, fmt.Errorf("role assumption denied: %w", err)
  397. }
  398. // 4. Calculate session duration
  399. sessionDuration := s.calculateSessionDuration(request.DurationSeconds)
  400. expiresAt := time.Now().Add(sessionDuration)
  401. // 5. Generate session ID and temporary credentials
  402. sessionId, err := GenerateSessionId()
  403. if err != nil {
  404. return nil, fmt.Errorf("failed to generate session ID: %w", err)
  405. }
  406. credGenerator := NewCredentialGenerator()
  407. tempCredentials, err := credGenerator.GenerateTemporaryCredentials(sessionId, expiresAt)
  408. if err != nil {
  409. return nil, fmt.Errorf("failed to generate credentials: %w", err)
  410. }
  411. // 6. Create comprehensive JWT session token with all session information embedded
  412. assumedRoleUser := &AssumedRoleUser{
  413. AssumedRoleId: request.RoleArn,
  414. Arn: GenerateAssumedRoleArn(request.RoleArn, request.RoleSessionName),
  415. Subject: externalIdentity.UserID,
  416. }
  417. // Create rich JWT claims with all session information
  418. sessionClaims := NewSTSSessionClaims(sessionId, s.Config.Issuer, expiresAt).
  419. WithSessionName(request.RoleSessionName).
  420. WithRoleInfo(request.RoleArn, assumedRoleUser.Arn, assumedRoleUser.Arn).
  421. WithIdentityProvider(provider.Name(), externalIdentity.UserID, "").
  422. WithMaxDuration(sessionDuration)
  423. // Generate self-contained JWT token with all session information
  424. jwtToken, err := s.tokenGenerator.GenerateJWTWithClaims(sessionClaims)
  425. if err != nil {
  426. return nil, fmt.Errorf("failed to generate JWT session token: %w", err)
  427. }
  428. tempCredentials.SessionToken = jwtToken
  429. // 7. Build and return response (no session storage needed!)
  430. return &AssumeRoleResponse{
  431. Credentials: tempCredentials,
  432. AssumedRoleUser: assumedRoleUser,
  433. }, nil
  434. }
  435. // ValidateSessionToken validates a session token and returns session information
  436. // This method is now completely stateless - all session information is extracted from the JWT token
  437. func (s *STSService) ValidateSessionToken(ctx context.Context, sessionToken string) (*SessionInfo, error) {
  438. if !s.initialized {
  439. return nil, fmt.Errorf(ErrSTSServiceNotInitialized)
  440. }
  441. if sessionToken == "" {
  442. return nil, fmt.Errorf(ErrSessionTokenCannotBeEmpty)
  443. }
  444. // Validate JWT and extract comprehensive session claims
  445. claims, err := s.tokenGenerator.ValidateJWTWithClaims(sessionToken)
  446. if err != nil {
  447. return nil, fmt.Errorf(ErrSessionValidationFailed, err)
  448. }
  449. // Convert JWT claims back to SessionInfo
  450. // All session information is embedded in the JWT token itself
  451. return claims.ToSessionInfo(), nil
  452. }
  453. // NOTE: Session revocation is not supported in the stateless JWT design.
  454. //
  455. // In a stateless JWT system, tokens cannot be revoked without implementing a token blacklist,
  456. // which would break the stateless architecture. Tokens remain valid until their natural
  457. // expiration time.
  458. //
  459. // For applications requiring token revocation, consider:
  460. // 1. Using shorter token lifespans (e.g., 15-30 minutes)
  461. // 2. Implementing a distributed token blacklist (breaks stateless design)
  462. // 3. Including a "jti" (JWT ID) claim for tracking specific tokens
  463. //
  464. // Use ValidateSessionToken() to verify if a token is valid and not expired.
  465. // Helper methods for AssumeRoleWithWebIdentity
  466. // validateAssumeRoleWithWebIdentityRequest validates the request parameters
  467. func (s *STSService) validateAssumeRoleWithWebIdentityRequest(request *AssumeRoleWithWebIdentityRequest) error {
  468. if request.RoleArn == "" {
  469. return fmt.Errorf("RoleArn is required")
  470. }
  471. if request.WebIdentityToken == "" {
  472. return fmt.Errorf("WebIdentityToken is required")
  473. }
  474. if request.RoleSessionName == "" {
  475. return fmt.Errorf("RoleSessionName is required")
  476. }
  477. // Validate session duration if provided
  478. if request.DurationSeconds != nil {
  479. if *request.DurationSeconds < 900 || *request.DurationSeconds > 43200 { // 15min to 12 hours
  480. return fmt.Errorf("DurationSeconds must be between 900 and 43200 seconds")
  481. }
  482. }
  483. return nil
  484. }
  485. // validateWebIdentityToken validates the web identity token with strict issuer-to-provider mapping
  486. // SECURITY: JWT tokens with a specific issuer claim MUST only be validated by the provider for that issuer
  487. // SECURITY: This method only accepts JWT tokens. Non-JWT authentication must use AssumeRoleWithCredentials with explicit ProviderName.
  488. func (s *STSService) validateWebIdentityToken(ctx context.Context, token string) (*providers.ExternalIdentity, providers.IdentityProvider, error) {
  489. // Try to extract issuer from JWT token for strict validation
  490. issuer, err := s.extractIssuerFromJWT(token)
  491. if err != nil {
  492. // Token is not a valid JWT or cannot be parsed
  493. // SECURITY: Web identity tokens MUST be JWT tokens. Non-JWT authentication flows
  494. // should use AssumeRoleWithCredentials with explicit ProviderName to prevent
  495. // security vulnerabilities from non-deterministic provider selection.
  496. return nil, nil, fmt.Errorf("web identity token must be a valid JWT token: %w", err)
  497. }
  498. // Look up the specific provider for this issuer
  499. provider, exists := s.issuerToProvider[issuer]
  500. if !exists {
  501. // SECURITY: If no provider is registered for this issuer, fail immediately
  502. // This prevents JWT tokens from being validated by unintended providers
  503. return nil, nil, fmt.Errorf("no identity provider registered for issuer: %s", issuer)
  504. }
  505. // Authenticate with the correct provider for this issuer
  506. identity, err := provider.Authenticate(ctx, token)
  507. if err != nil {
  508. return nil, nil, fmt.Errorf("token validation failed with provider for issuer %s: %w", issuer, err)
  509. }
  510. if identity == nil {
  511. return nil, nil, fmt.Errorf("authentication succeeded but no identity returned for issuer %s", issuer)
  512. }
  513. return identity, provider, nil
  514. }
  515. // ValidateWebIdentityToken is a public method that exposes secure token validation for external use
  516. // This method uses issuer-based lookup to select the correct provider, ensuring security and efficiency
  517. func (s *STSService) ValidateWebIdentityToken(ctx context.Context, token string) (*providers.ExternalIdentity, providers.IdentityProvider, error) {
  518. return s.validateWebIdentityToken(ctx, token)
  519. }
  520. // extractIssuerFromJWT extracts the issuer (iss) claim from a JWT token without verification
  521. func (s *STSService) extractIssuerFromJWT(token string) (string, error) {
  522. // Parse token without verification to get claims
  523. parsedToken, _, err := new(jwt.Parser).ParseUnverified(token, jwt.MapClaims{})
  524. if err != nil {
  525. return "", fmt.Errorf("failed to parse JWT token: %v", err)
  526. }
  527. // Extract claims
  528. claims, ok := parsedToken.Claims.(jwt.MapClaims)
  529. if !ok {
  530. return "", fmt.Errorf("invalid token claims")
  531. }
  532. // Get issuer claim
  533. issuer, ok := claims["iss"].(string)
  534. if !ok || issuer == "" {
  535. return "", fmt.Errorf("missing or invalid issuer claim")
  536. }
  537. return issuer, nil
  538. }
  539. // validateRoleAssumptionForWebIdentity validates role assumption for web identity tokens
  540. // This method performs complete trust policy validation to prevent unauthorized role assumptions
  541. func (s *STSService) validateRoleAssumptionForWebIdentity(ctx context.Context, roleArn string, webIdentityToken string) error {
  542. if roleArn == "" {
  543. return fmt.Errorf("role ARN cannot be empty")
  544. }
  545. if webIdentityToken == "" {
  546. return fmt.Errorf("web identity token cannot be empty")
  547. }
  548. // Basic role ARN format validation
  549. expectedPrefix := "arn:seaweed:iam::role/"
  550. if len(roleArn) < len(expectedPrefix) || roleArn[:len(expectedPrefix)] != expectedPrefix {
  551. return fmt.Errorf("invalid role ARN format: got %s, expected format: %s*", roleArn, expectedPrefix)
  552. }
  553. // Extract role name and validate ARN format
  554. roleName := utils.ExtractRoleNameFromArn(roleArn)
  555. if roleName == "" {
  556. return fmt.Errorf("invalid role ARN format: %s", roleArn)
  557. }
  558. // CRITICAL SECURITY: Perform trust policy validation
  559. if s.trustPolicyValidator != nil {
  560. if err := s.trustPolicyValidator.ValidateTrustPolicyForWebIdentity(ctx, roleArn, webIdentityToken); err != nil {
  561. return fmt.Errorf("trust policy validation failed: %w", err)
  562. }
  563. } else {
  564. // If no trust policy validator is configured, fail closed for security
  565. glog.Errorf("SECURITY WARNING: No trust policy validator configured - denying role assumption for security")
  566. return fmt.Errorf("trust policy validation not available - role assumption denied for security")
  567. }
  568. return nil
  569. }
  570. // validateRoleAssumptionForCredentials validates role assumption for credential-based authentication
  571. // This method performs complete trust policy validation to prevent unauthorized role assumptions
  572. func (s *STSService) validateRoleAssumptionForCredentials(ctx context.Context, roleArn string, identity *providers.ExternalIdentity) error {
  573. if roleArn == "" {
  574. return fmt.Errorf("role ARN cannot be empty")
  575. }
  576. if identity == nil {
  577. return fmt.Errorf("identity cannot be nil")
  578. }
  579. // Basic role ARN format validation
  580. expectedPrefix := "arn:seaweed:iam::role/"
  581. if len(roleArn) < len(expectedPrefix) || roleArn[:len(expectedPrefix)] != expectedPrefix {
  582. return fmt.Errorf("invalid role ARN format: got %s, expected format: %s*", roleArn, expectedPrefix)
  583. }
  584. // Extract role name and validate ARN format
  585. roleName := utils.ExtractRoleNameFromArn(roleArn)
  586. if roleName == "" {
  587. return fmt.Errorf("invalid role ARN format: %s", roleArn)
  588. }
  589. // CRITICAL SECURITY: Perform trust policy validation
  590. if s.trustPolicyValidator != nil {
  591. if err := s.trustPolicyValidator.ValidateTrustPolicyForCredentials(ctx, roleArn, identity); err != nil {
  592. return fmt.Errorf("trust policy validation failed: %w", err)
  593. }
  594. } else {
  595. // If no trust policy validator is configured, fail closed for security
  596. glog.Errorf("SECURITY WARNING: No trust policy validator configured - denying role assumption for security")
  597. return fmt.Errorf("trust policy validation not available - role assumption denied for security")
  598. }
  599. return nil
  600. }
  601. // calculateSessionDuration calculates the session duration
  602. func (s *STSService) calculateSessionDuration(durationSeconds *int64) time.Duration {
  603. if durationSeconds != nil {
  604. return time.Duration(*durationSeconds) * time.Second
  605. }
  606. // Use default from config
  607. return s.Config.TokenDuration.Duration
  608. }
  609. // extractSessionIdFromToken extracts session ID from JWT session token
  610. func (s *STSService) extractSessionIdFromToken(sessionToken string) string {
  611. // Parse JWT and extract session ID from claims
  612. claims, err := s.tokenGenerator.ValidateJWTWithClaims(sessionToken)
  613. if err != nil {
  614. // For test compatibility, also handle direct session IDs
  615. if len(sessionToken) == 32 { // Typical session ID length
  616. return sessionToken
  617. }
  618. return ""
  619. }
  620. return claims.SessionId
  621. }
  622. // validateAssumeRoleWithCredentialsRequest validates the credentials request parameters
  623. func (s *STSService) validateAssumeRoleWithCredentialsRequest(request *AssumeRoleWithCredentialsRequest) error {
  624. if request.RoleArn == "" {
  625. return fmt.Errorf("RoleArn is required")
  626. }
  627. if request.Username == "" {
  628. return fmt.Errorf("Username is required")
  629. }
  630. if request.Password == "" {
  631. return fmt.Errorf("Password is required")
  632. }
  633. if request.RoleSessionName == "" {
  634. return fmt.Errorf("RoleSessionName is required")
  635. }
  636. if request.ProviderName == "" {
  637. return fmt.Errorf("ProviderName is required")
  638. }
  639. // Validate session duration if provided
  640. if request.DurationSeconds != nil {
  641. if *request.DurationSeconds < 900 || *request.DurationSeconds > 43200 { // 15min to 12 hours
  642. return fmt.Errorf("DurationSeconds must be between 900 and 43200 seconds")
  643. }
  644. }
  645. return nil
  646. }
  647. // ExpireSessionForTesting manually expires a session for testing purposes
  648. func (s *STSService) ExpireSessionForTesting(ctx context.Context, sessionToken string) error {
  649. if !s.initialized {
  650. return fmt.Errorf("STS service not initialized")
  651. }
  652. if sessionToken == "" {
  653. return fmt.Errorf("session token cannot be empty")
  654. }
  655. // Validate JWT token format
  656. _, err := s.tokenGenerator.ValidateJWTWithClaims(sessionToken)
  657. if err != nil {
  658. return fmt.Errorf("invalid session token format: %w", err)
  659. }
  660. // In a stateless system, we cannot manually expire JWT tokens
  661. // The token expiration is embedded in the token itself and handled by JWT validation
  662. glog.V(1).Infof("Manual session expiration requested for stateless token - cannot expire JWT tokens manually")
  663. return fmt.Errorf("manual session expiration not supported in stateless JWT system")
  664. }