session_policy_test.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. package sts
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. "github.com/golang-jwt/jwt/v5"
  7. "github.com/stretchr/testify/assert"
  8. "github.com/stretchr/testify/require"
  9. )
  10. // createSessionPolicyTestJWT creates a test JWT token for session policy tests
  11. func createSessionPolicyTestJWT(t *testing.T, issuer, subject string) string {
  12. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
  13. "iss": issuer,
  14. "sub": subject,
  15. "aud": "test-client",
  16. "exp": time.Now().Add(time.Hour).Unix(),
  17. "iat": time.Now().Unix(),
  18. })
  19. tokenString, err := token.SignedString([]byte("test-signing-key"))
  20. require.NoError(t, err)
  21. return tokenString
  22. }
  23. // TestAssumeRoleWithWebIdentity_SessionPolicy tests the handling of the Policy field
  24. // in AssumeRoleWithWebIdentityRequest to ensure users are properly informed that
  25. // session policies are not currently supported
  26. func TestAssumeRoleWithWebIdentity_SessionPolicy(t *testing.T) {
  27. service := setupTestSTSService(t)
  28. t.Run("should_reject_request_with_session_policy", func(t *testing.T) {
  29. ctx := context.Background()
  30. // Create a request with a session policy
  31. sessionPolicy := `{
  32. "Version": "2012-10-17",
  33. "Statement": [{
  34. "Effect": "Allow",
  35. "Action": "s3:GetObject",
  36. "Resource": "arn:aws:s3:::example-bucket/*"
  37. }]
  38. }`
  39. testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")
  40. request := &AssumeRoleWithWebIdentityRequest{
  41. RoleArn: "arn:seaweed:iam::role/TestRole",
  42. WebIdentityToken: testToken,
  43. RoleSessionName: "test-session",
  44. DurationSeconds: nil, // Use default
  45. Policy: &sessionPolicy, // ← Session policy provided
  46. }
  47. // Should return an error indicating session policies are not supported
  48. response, err := service.AssumeRoleWithWebIdentity(ctx, request)
  49. // Verify the error
  50. assert.Error(t, err)
  51. assert.Nil(t, response)
  52. assert.Contains(t, err.Error(), "session policies are not currently supported")
  53. assert.Contains(t, err.Error(), "Policy parameter must be omitted")
  54. })
  55. t.Run("should_succeed_without_session_policy", func(t *testing.T) {
  56. ctx := context.Background()
  57. testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")
  58. request := &AssumeRoleWithWebIdentityRequest{
  59. RoleArn: "arn:seaweed:iam::role/TestRole",
  60. WebIdentityToken: testToken,
  61. RoleSessionName: "test-session",
  62. DurationSeconds: nil, // Use default
  63. Policy: nil, // ← No session policy
  64. }
  65. // Should succeed without session policy
  66. response, err := service.AssumeRoleWithWebIdentity(ctx, request)
  67. // Verify success
  68. require.NoError(t, err)
  69. require.NotNil(t, response)
  70. assert.NotNil(t, response.Credentials)
  71. assert.NotEmpty(t, response.Credentials.AccessKeyId)
  72. assert.NotEmpty(t, response.Credentials.SecretAccessKey)
  73. assert.NotEmpty(t, response.Credentials.SessionToken)
  74. })
  75. t.Run("should_succeed_with_empty_policy_pointer", func(t *testing.T) {
  76. ctx := context.Background()
  77. testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")
  78. request := &AssumeRoleWithWebIdentityRequest{
  79. RoleArn: "arn:seaweed:iam::role/TestRole",
  80. WebIdentityToken: testToken,
  81. RoleSessionName: "test-session",
  82. Policy: nil, // ← Explicitly nil
  83. }
  84. // Should succeed with nil policy pointer
  85. response, err := service.AssumeRoleWithWebIdentity(ctx, request)
  86. require.NoError(t, err)
  87. require.NotNil(t, response)
  88. assert.NotNil(t, response.Credentials)
  89. })
  90. t.Run("should_reject_empty_string_policy", func(t *testing.T) {
  91. ctx := context.Background()
  92. emptyPolicy := "" // Empty string, but still a non-nil pointer
  93. request := &AssumeRoleWithWebIdentityRequest{
  94. RoleArn: "arn:seaweed:iam::role/TestRole",
  95. WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
  96. RoleSessionName: "test-session",
  97. Policy: &emptyPolicy, // ← Non-nil pointer to empty string
  98. }
  99. // Should still reject because pointer is not nil
  100. response, err := service.AssumeRoleWithWebIdentity(ctx, request)
  101. assert.Error(t, err)
  102. assert.Nil(t, response)
  103. assert.Contains(t, err.Error(), "session policies are not currently supported")
  104. })
  105. }
  106. // TestAssumeRoleWithWebIdentity_SessionPolicy_ErrorMessage tests that the error message
  107. // is clear and helps users understand what they need to do
  108. func TestAssumeRoleWithWebIdentity_SessionPolicy_ErrorMessage(t *testing.T) {
  109. service := setupTestSTSService(t)
  110. ctx := context.Background()
  111. complexPolicy := `{
  112. "Version": "2012-10-17",
  113. "Statement": [
  114. {
  115. "Sid": "AllowS3Access",
  116. "Effect": "Allow",
  117. "Action": [
  118. "s3:GetObject",
  119. "s3:PutObject"
  120. ],
  121. "Resource": [
  122. "arn:aws:s3:::my-bucket/*",
  123. "arn:aws:s3:::my-bucket"
  124. ],
  125. "Condition": {
  126. "StringEquals": {
  127. "s3:prefix": ["documents/", "images/"]
  128. }
  129. }
  130. }
  131. ]
  132. }`
  133. testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")
  134. request := &AssumeRoleWithWebIdentityRequest{
  135. RoleArn: "arn:seaweed:iam::role/TestRole",
  136. WebIdentityToken: testToken,
  137. RoleSessionName: "test-session-with-complex-policy",
  138. Policy: &complexPolicy,
  139. }
  140. response, err := service.AssumeRoleWithWebIdentity(ctx, request)
  141. // Verify error details
  142. require.Error(t, err)
  143. assert.Nil(t, response)
  144. errorMsg := err.Error()
  145. // The error should be clear and actionable
  146. assert.Contains(t, errorMsg, "session policies are not currently supported",
  147. "Error should explain that session policies aren't supported")
  148. assert.Contains(t, errorMsg, "Policy parameter must be omitted",
  149. "Error should specify what action the user needs to take")
  150. // Should NOT contain internal implementation details
  151. assert.NotContains(t, errorMsg, "nil pointer",
  152. "Error should not expose internal implementation details")
  153. assert.NotContains(t, errorMsg, "struct field",
  154. "Error should not expose internal struct details")
  155. }
  156. // Test edge case scenarios for the Policy field handling
  157. func TestAssumeRoleWithWebIdentity_SessionPolicy_EdgeCases(t *testing.T) {
  158. service := setupTestSTSService(t)
  159. t.Run("malformed_json_policy_still_rejected", func(t *testing.T) {
  160. ctx := context.Background()
  161. malformedPolicy := `{"Version": "2012-10-17", "Statement": [` // Incomplete JSON
  162. request := &AssumeRoleWithWebIdentityRequest{
  163. RoleArn: "arn:seaweed:iam::role/TestRole",
  164. WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
  165. RoleSessionName: "test-session",
  166. Policy: &malformedPolicy,
  167. }
  168. // Should reject before even parsing the policy JSON
  169. response, err := service.AssumeRoleWithWebIdentity(ctx, request)
  170. assert.Error(t, err)
  171. assert.Nil(t, response)
  172. assert.Contains(t, err.Error(), "session policies are not currently supported")
  173. })
  174. t.Run("policy_with_whitespace_still_rejected", func(t *testing.T) {
  175. ctx := context.Background()
  176. whitespacePolicy := " \t\n " // Only whitespace
  177. request := &AssumeRoleWithWebIdentityRequest{
  178. RoleArn: "arn:seaweed:iam::role/TestRole",
  179. WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
  180. RoleSessionName: "test-session",
  181. Policy: &whitespacePolicy,
  182. }
  183. // Should reject any non-nil policy, even whitespace
  184. response, err := service.AssumeRoleWithWebIdentity(ctx, request)
  185. assert.Error(t, err)
  186. assert.Nil(t, response)
  187. assert.Contains(t, err.Error(), "session policies are not currently supported")
  188. })
  189. }
  190. // TestAssumeRoleWithWebIdentity_PolicyFieldDocumentation verifies that the struct
  191. // field is properly documented to help developers understand the limitation
  192. func TestAssumeRoleWithWebIdentity_PolicyFieldDocumentation(t *testing.T) {
  193. // This test documents the current behavior and ensures the struct field
  194. // exists with proper typing
  195. request := &AssumeRoleWithWebIdentityRequest{}
  196. // Verify the Policy field exists and has the correct type
  197. assert.IsType(t, (*string)(nil), request.Policy,
  198. "Policy field should be *string type for optional JSON policy")
  199. // Verify initial value is nil (no policy by default)
  200. assert.Nil(t, request.Policy,
  201. "Policy field should default to nil (no session policy)")
  202. // Test that we can set it to a string pointer (even though it will be rejected)
  203. policyValue := `{"Version": "2012-10-17"}`
  204. request.Policy = &policyValue
  205. assert.NotNil(t, request.Policy, "Should be able to assign policy value")
  206. assert.Equal(t, policyValue, *request.Policy, "Policy value should be preserved")
  207. }
  208. // TestAssumeRoleWithCredentials_NoSessionPolicySupport verifies that
  209. // AssumeRoleWithCredentialsRequest doesn't have a Policy field, which is correct
  210. // since credential-based role assumption typically doesn't support session policies
  211. func TestAssumeRoleWithCredentials_NoSessionPolicySupport(t *testing.T) {
  212. // Verify that AssumeRoleWithCredentialsRequest doesn't have a Policy field
  213. // This is the expected behavior since session policies are typically only
  214. // supported with web identity (OIDC/SAML) flows in AWS STS
  215. request := &AssumeRoleWithCredentialsRequest{
  216. RoleArn: "arn:seaweed:iam::role/TestRole",
  217. Username: "testuser",
  218. Password: "testpass",
  219. RoleSessionName: "test-session",
  220. ProviderName: "ldap",
  221. }
  222. // The struct should compile and work without a Policy field
  223. assert.NotNil(t, request)
  224. assert.Equal(t, "arn:seaweed:iam::role/TestRole", request.RoleArn)
  225. assert.Equal(t, "testuser", request.Username)
  226. // This documents that credential-based assume role does NOT support session policies
  227. // which matches AWS STS behavior where session policies are primarily for
  228. // web identity (OIDC/SAML) and federation scenarios
  229. }