Files
seaweedfs/weed/s3api/s3api_sts_duration_test.go
T
Chris Lu 0de9c1f231 fix(s3api/sts): respect MaxSessionLength config in DurationSeconds validation (#11267)
* Refactor parseDurationSeconds into a STSHandlers method

Convert the parseDurationSeconds wrapper from a package-level function
into a method on STSHandlers so it can reach the configured STS service.
No behavior change; the three AssumeRole* handlers now invoke it via
their receiver.

* Respect MaxSessionLength config in STS DurationSeconds validation

parseDurationSeconds validated DurationSeconds against a hardcoded
43200s (12h) ceiling, so raising maxSessionLength in iam.json above
12h had no effect on AssumeRole, AssumeRoleWithWebIdentity, or
AssumeRoleWithLDAPIdentity — requests were rejected at the handler
before reaching the service layer.

Derive the upper bound from the configured STS MaxSessionLength,
falling back to maxDurationSeconds (43200s) when unset. The service
layer (calculateSessionDuration) already caps the issued duration
at MaxSessionLength, so this only relaxes the input-validation gate.

* Add tests for STS DurationSeconds MaxSessionLength bound

Cover the configured MaxSessionLength upper bound, rejection above
it, fallback to the 43200s default when STS config is unset, the
900s minimum, and the empty-parameter nil path.

* Refactor validateSessionDurationSeconds into a STSService method

Convert validateSessionDurationSeconds from a package-level function
into a method on STSService so it can reach the configured STS config.
No behavior change; the three assume-role entry points in the service
(AssumeRoleForPrincipal, validateAssumeRoleWithWebIdentityRequest,
validateAssumeRoleWithCredentialsRequest) now invoke it via their
receiver.

* Respect MaxSessionLength config in STS service DurationSeconds validation

The STS service validateSessionDurationSeconds rejected DurationSeconds
above a hardcoded 43200s (12h) ceiling, so even after the handler
accepted a longer duration it was rejected again in the service layer
for AssumeRoleForPrincipal, AssumeRoleWithWebIdentity, and
AssumeRoleWithCredentials.

Derive the upper bound from the configured MaxSessionLength, falling
back to DefaultMaxSessionLength (43200s) when unset. The issued
duration is still capped at MaxSessionLength by calculateSessionDuration.

* Add tests for STS service DurationSeconds MaxSessionLength bound

Cover the configured MaxSessionLength upper bound, rejection above
it, fallback to the 43200s default when STS config is unset, the
900s minimum, and the nil DurationSeconds path.

* Preserve capping when MaxSessionLength is below the API minimum

Deriving the DurationSeconds upper bound directly from MaxSessionLength
created an empty valid range when MaxSessionLength is configured below
the 900s API minimum, rejecting every explicit DurationSeconds that the
old code silently capped via calculateSessionDuration.

Only apply the configured MaxSessionLength as the upper bound when it is
at least minDurationSeconds; otherwise keep the default bound and let
calculateSessionDuration enforce the shorter configured limit.

* Add tests for sub-minimum MaxSessionLength capping behavior

Verify that a MaxSessionLength below the 900s API minimum keeps the
default upper bound so explicit DurationSeconds within the default
range are still accepted (and later capped by calculateSessionDuration).
2026-09-10 23:03:32 -07:00

106 lines
3.2 KiB
Go

package s3api
import (
"net/http"
"net/url"
"strings"
"testing"
"time"
"github.com/seaweedfs/seaweedfs/weed/iam/sts"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newDurationSecondsRequest(t *testing.T, seconds string) *http.Request {
t.Helper()
form := url.Values{}
if seconds != "" {
form.Set("DurationSeconds", seconds)
}
req, err := http.NewRequest("POST", "/", strings.NewReader(form.Encode()))
require.NoError(t, err)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
require.NoError(t, req.ParseForm())
return req
}
func newSTSHandlersWithMaxSession(t *testing.T, maxSession time.Duration) *STSHandlers {
t.Helper()
s := sts.NewSTSService()
require.NoError(t, s.Initialize(&sts.STSConfig{
Issuer: "test-issuer",
SigningKey: []byte("test-signing-key-at-least-32-bytes-long-for-security"),
TokenDuration: sts.FlexibleDuration{Duration: time.Hour},
MaxSessionLength: sts.FlexibleDuration{Duration: maxSession},
}))
return NewSTSHandlers(s, nil)
}
func TestParseDurationSeconds_RespectsConfiguredMaxSessionLength(t *testing.T) {
h := newSTSHandlersWithMaxSession(t, 168*time.Hour)
ds, errCode, err := h.parseDurationSeconds(newDurationSecondsRequest(t, "604800"))
require.NoError(t, err)
assert.Equal(t, STSErrorCode(""), errCode)
if assert.NotNil(t, ds) {
assert.Equal(t, int64(604800), *ds)
}
}
func TestParseDurationSeconds_RejectsAboveConfiguredMaxSessionLength(t *testing.T) {
h := newSTSHandlersWithMaxSession(t, 24*time.Hour)
ds, errCode, err := h.parseDurationSeconds(newDurationSecondsRequest(t, "90000"))
assert.Error(t, err)
assert.Equal(t, STSErrInvalidParameterValue, errCode)
assert.Nil(t, ds)
}
func TestParseDurationSeconds_FallsBackToDefaultWhenUnset(t *testing.T) {
h := &STSHandlers{stsService: nil}
ds, errCode, err := h.parseDurationSeconds(newDurationSecondsRequest(t, "43200"))
require.NoError(t, err)
assert.Equal(t, STSErrorCode(""), errCode)
if assert.NotNil(t, ds) {
assert.Equal(t, int64(43200), *ds)
}
_, errCode, err = h.parseDurationSeconds(newDurationSecondsRequest(t, "43201"))
assert.Error(t, err)
assert.Equal(t, STSErrInvalidParameterValue, errCode)
}
func TestParseDurationSeconds_EnforcesMinimum(t *testing.T) {
h := newSTSHandlersWithMaxSession(t, 168*time.Hour)
_, errCode, err := h.parseDurationSeconds(newDurationSecondsRequest(t, "899"))
assert.Error(t, err)
assert.Equal(t, STSErrInvalidParameterValue, errCode)
}
func TestParseDurationSeconds_EmptyReturnsNil(t *testing.T) {
h := newSTSHandlersWithMaxSession(t, 168*time.Hour)
ds, errCode, err := h.parseDurationSeconds(newDurationSecondsRequest(t, ""))
require.NoError(t, err)
assert.Equal(t, STSErrorCode(""), errCode)
assert.Nil(t, ds)
}
func TestParseDurationSeconds_SubMinimumMaxSessionLengthKeepsCapping(t *testing.T) {
h := newSTSHandlersWithMaxSession(t, 5*time.Minute)
ds, errCode, err := h.parseDurationSeconds(newDurationSecondsRequest(t, "900"))
require.NoError(t, err)
assert.Equal(t, STSErrorCode(""), errCode)
if assert.NotNil(t, ds) {
assert.Equal(t, int64(900), *ds)
}
_, errCode, err = h.parseDurationSeconds(newDurationSecondsRequest(t, "43201"))
assert.Error(t, err)
assert.Equal(t, STSErrInvalidParameterValue, errCode)
}