aboutsummaryrefslogtreecommitdiff
path: root/weed/iam/sts/session_policy_test.go
blob: 6f94169ec1bd47574bcecc52610b7156b23407f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
package sts

import (
	"context"
	"testing"
	"time"

	"github.com/golang-jwt/jwt/v5"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// createSessionPolicyTestJWT creates a test JWT token for session policy tests
func createSessionPolicyTestJWT(t *testing.T, issuer, subject string) string {
	token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
		"iss": issuer,
		"sub": subject,
		"aud": "test-client",
		"exp": time.Now().Add(time.Hour).Unix(),
		"iat": time.Now().Unix(),
	})

	tokenString, err := token.SignedString([]byte("test-signing-key"))
	require.NoError(t, err)
	return tokenString
}

// TestAssumeRoleWithWebIdentity_SessionPolicy tests the handling of the Policy field
// in AssumeRoleWithWebIdentityRequest to ensure users are properly informed that
// session policies are not currently supported
func TestAssumeRoleWithWebIdentity_SessionPolicy(t *testing.T) {
	service := setupTestSTSService(t)

	t.Run("should_reject_request_with_session_policy", func(t *testing.T) {
		ctx := context.Background()

		// Create a request with a session policy
		sessionPolicy := `{
			"Version": "2012-10-17",
			"Statement": [{
				"Effect": "Allow",
				"Action": "s3:GetObject",
				"Resource": "arn:aws:s3:::example-bucket/*"
			}]
		}`

		testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")

		request := &AssumeRoleWithWebIdentityRequest{
			RoleArn:          "arn:seaweed:iam::role/TestRole",
			WebIdentityToken: testToken,
			RoleSessionName:  "test-session",
			DurationSeconds:  nil,            // Use default
			Policy:           &sessionPolicy, // ← Session policy provided
		}

		// Should return an error indicating session policies are not supported
		response, err := service.AssumeRoleWithWebIdentity(ctx, request)

		// Verify the error
		assert.Error(t, err)
		assert.Nil(t, response)
		assert.Contains(t, err.Error(), "session policies are not currently supported")
		assert.Contains(t, err.Error(), "Policy parameter must be omitted")
	})

	t.Run("should_succeed_without_session_policy", func(t *testing.T) {
		ctx := context.Background()
		testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")

		request := &AssumeRoleWithWebIdentityRequest{
			RoleArn:          "arn:seaweed:iam::role/TestRole",
			WebIdentityToken: testToken,
			RoleSessionName:  "test-session",
			DurationSeconds:  nil, // Use default
			Policy:           nil, // ← No session policy
		}

		// Should succeed without session policy
		response, err := service.AssumeRoleWithWebIdentity(ctx, request)

		// Verify success
		require.NoError(t, err)
		require.NotNil(t, response)
		assert.NotNil(t, response.Credentials)
		assert.NotEmpty(t, response.Credentials.AccessKeyId)
		assert.NotEmpty(t, response.Credentials.SecretAccessKey)
		assert.NotEmpty(t, response.Credentials.SessionToken)
	})

	t.Run("should_succeed_with_empty_policy_pointer", func(t *testing.T) {
		ctx := context.Background()
		testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")

		request := &AssumeRoleWithWebIdentityRequest{
			RoleArn:          "arn:seaweed:iam::role/TestRole",
			WebIdentityToken: testToken,
			RoleSessionName:  "test-session",
			Policy:           nil, // ← Explicitly nil
		}

		// Should succeed with nil policy pointer
		response, err := service.AssumeRoleWithWebIdentity(ctx, request)

		require.NoError(t, err)
		require.NotNil(t, response)
		assert.NotNil(t, response.Credentials)
	})

	t.Run("should_reject_empty_string_policy", func(t *testing.T) {
		ctx := context.Background()

		emptyPolicy := "" // Empty string, but still a non-nil pointer

		request := &AssumeRoleWithWebIdentityRequest{
			RoleArn:          "arn:seaweed:iam::role/TestRole",
			WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
			RoleSessionName:  "test-session",
			Policy:           &emptyPolicy, // ← Non-nil pointer to empty string
		}

		// Should still reject because pointer is not nil
		response, err := service.AssumeRoleWithWebIdentity(ctx, request)

		assert.Error(t, err)
		assert.Nil(t, response)
		assert.Contains(t, err.Error(), "session policies are not currently supported")
	})
}

// TestAssumeRoleWithWebIdentity_SessionPolicy_ErrorMessage tests that the error message
// is clear and helps users understand what they need to do
func TestAssumeRoleWithWebIdentity_SessionPolicy_ErrorMessage(t *testing.T) {
	service := setupTestSTSService(t)

	ctx := context.Background()
	complexPolicy := `{
		"Version": "2012-10-17",
		"Statement": [
			{
				"Sid": "AllowS3Access",
				"Effect": "Allow",
				"Action": [
					"s3:GetObject",
					"s3:PutObject"
				],
				"Resource": [
					"arn:aws:s3:::my-bucket/*",
					"arn:aws:s3:::my-bucket"
				],
				"Condition": {
					"StringEquals": {
						"s3:prefix": ["documents/", "images/"]
					}
				}
			}
		]
	}`

	testToken := createSessionPolicyTestJWT(t, "test-issuer", "test-user")

	request := &AssumeRoleWithWebIdentityRequest{
		RoleArn:          "arn:seaweed:iam::role/TestRole",
		WebIdentityToken: testToken,
		RoleSessionName:  "test-session-with-complex-policy",
		Policy:           &complexPolicy,
	}

	response, err := service.AssumeRoleWithWebIdentity(ctx, request)

	// Verify error details
	require.Error(t, err)
	assert.Nil(t, response)

	errorMsg := err.Error()

	// The error should be clear and actionable
	assert.Contains(t, errorMsg, "session policies are not currently supported",
		"Error should explain that session policies aren't supported")
	assert.Contains(t, errorMsg, "Policy parameter must be omitted",
		"Error should specify what action the user needs to take")

	// Should NOT contain internal implementation details
	assert.NotContains(t, errorMsg, "nil pointer",
		"Error should not expose internal implementation details")
	assert.NotContains(t, errorMsg, "struct field",
		"Error should not expose internal struct details")
}

// Test edge case scenarios for the Policy field handling
func TestAssumeRoleWithWebIdentity_SessionPolicy_EdgeCases(t *testing.T) {
	service := setupTestSTSService(t)

	t.Run("malformed_json_policy_still_rejected", func(t *testing.T) {
		ctx := context.Background()
		malformedPolicy := `{"Version": "2012-10-17", "Statement": [` // Incomplete JSON

		request := &AssumeRoleWithWebIdentityRequest{
			RoleArn:          "arn:seaweed:iam::role/TestRole",
			WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
			RoleSessionName:  "test-session",
			Policy:           &malformedPolicy,
		}

		// Should reject before even parsing the policy JSON
		response, err := service.AssumeRoleWithWebIdentity(ctx, request)

		assert.Error(t, err)
		assert.Nil(t, response)
		assert.Contains(t, err.Error(), "session policies are not currently supported")
	})

	t.Run("policy_with_whitespace_still_rejected", func(t *testing.T) {
		ctx := context.Background()
		whitespacePolicy := "   \t\n   " // Only whitespace

		request := &AssumeRoleWithWebIdentityRequest{
			RoleArn:          "arn:seaweed:iam::role/TestRole",
			WebIdentityToken: createSessionPolicyTestJWT(t, "test-issuer", "test-user"),
			RoleSessionName:  "test-session",
			Policy:           &whitespacePolicy,
		}

		// Should reject any non-nil policy, even whitespace
		response, err := service.AssumeRoleWithWebIdentity(ctx, request)

		assert.Error(t, err)
		assert.Nil(t, response)
		assert.Contains(t, err.Error(), "session policies are not currently supported")
	})
}

// TestAssumeRoleWithWebIdentity_PolicyFieldDocumentation verifies that the struct
// field is properly documented to help developers understand the limitation
func TestAssumeRoleWithWebIdentity_PolicyFieldDocumentation(t *testing.T) {
	// This test documents the current behavior and ensures the struct field
	// exists with proper typing
	request := &AssumeRoleWithWebIdentityRequest{}

	// Verify the Policy field exists and has the correct type
	assert.IsType(t, (*string)(nil), request.Policy,
		"Policy field should be *string type for optional JSON policy")

	// Verify initial value is nil (no policy by default)
	assert.Nil(t, request.Policy,
		"Policy field should default to nil (no session policy)")

	// Test that we can set it to a string pointer (even though it will be rejected)
	policyValue := `{"Version": "2012-10-17"}`
	request.Policy = &policyValue
	assert.NotNil(t, request.Policy, "Should be able to assign policy value")
	assert.Equal(t, policyValue, *request.Policy, "Policy value should be preserved")
}

// TestAssumeRoleWithCredentials_NoSessionPolicySupport verifies that
// AssumeRoleWithCredentialsRequest doesn't have a Policy field, which is correct
// since credential-based role assumption typically doesn't support session policies
func TestAssumeRoleWithCredentials_NoSessionPolicySupport(t *testing.T) {
	// Verify that AssumeRoleWithCredentialsRequest doesn't have a Policy field
	// This is the expected behavior since session policies are typically only
	// supported with web identity (OIDC/SAML) flows in AWS STS
	request := &AssumeRoleWithCredentialsRequest{
		RoleArn:         "arn:seaweed:iam::role/TestRole",
		Username:        "testuser",
		Password:        "testpass",
		RoleSessionName: "test-session",
		ProviderName:    "ldap",
	}

	// The struct should compile and work without a Policy field
	assert.NotNil(t, request)
	assert.Equal(t, "arn:seaweed:iam::role/TestRole", request.RoleArn)
	assert.Equal(t, "testuser", request.Username)

	// This documents that credential-based assume role does NOT support session policies
	// which matches AWS STS behavior where session policies are primarily for
	// web identity (OIDC/SAML) and federation scenarios
}