Files
seaweedfs/weed/s3api/chunked_reader_v4_bucket_policy_test.go
T
Chris LuandDevin 9a9bd67efe s3api: do not re-run permission checks in the aws-chunked reader (#11655)
calculateSeedSignature called verifyV4Signature with
shouldCheckPermissions=true, which runs VerifyActionPermission — a
check that does not consult bucket policies. Every caller of
newChunkedReader is already behind the Auth middleware, which does
evaluate them, so a principal allowed only by the bucket policy was
authorized upstream and then denied when the handler built the body
reader: aws-chunked PutObject/UploadPart (botocore's default shape
over TLS, PyArrow's over HTTP as well) failed with AccessDenied.

The reader now verifies only the signature; a wrong secret is still
SignatureDoesNotMatch. The non-streaming paths already work this way.

Generated with [Devin](https://devin.ai)

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-09 10:01:28 +08:00

90 lines
3.4 KiB
Go

package s3api
import (
"bytes"
"fmt"
"io"
"net/http"
"strings"
"sync"
"testing"
"github.com/gorilla/mux"
"github.com/seaweedfs/seaweedfs/weed/pb/iam_pb"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
"github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
"github.com/stretchr/testify/require"
)
const (
bpcBucket = "policy-bucket"
bpcObject = "incoming/part.bin"
bpcAccessKey = "LOCALPOLICYKEY000001"
bpcSecretKey = "local-policy-secret-for-loopback-only"
bpcIdentityName = "policy-writer"
bpcAccountID = "000000000000"
)
// An identity with credentials and nothing else, plus a bucket policy that lets it put objects.
func newBucketPolicyOnlyIAM(t *testing.T) *IdentityAccessManagement {
t.Helper()
iam := &IdentityAccessManagement{
hashes: make(map[string]*sync.Pool),
hashCounters: make(map[string]*int32),
}
err := iam.loadS3ApiConfiguration(&iam_pb.S3ApiConfiguration{
Accounts: []*iam_pb.Account{{Id: bpcAccountID, DisplayName: bpcIdentityName}},
Identities: []*iam_pb.Identity{{
Name: bpcIdentityName,
Account: &iam_pb.Account{Id: bpcAccountID, DisplayName: bpcIdentityName},
Credentials: []*iam_pb.Credential{{AccessKey: bpcAccessKey, SecretKey: bpcSecretKey}},
}},
})
require.NoError(t, err)
policy := fmt.Sprintf(`{"Version":"2012-10-17","Statement":[
{"Effect":"Allow","Principal":{"AWS":"arn:aws:iam::%s:user/%s"},
"Action":"s3:PutObject","Resource":"arn:aws:s3:::%s/*"}]}`, bpcAccountID, bpcIdentityName, bpcBucket)
engine := NewBucketPolicyEngine()
require.NoError(t, engine.engine.SetBucketPolicy(bpcBucket, policy))
iam.policyEngine = engine
return iam
}
// A SigV4 PutObject whose body is aws-chunked with an unsigned payload and a CRC32 trailer.
func bpcStreamingPut(t *testing.T, secretKey string) *http.Request {
t.Helper()
payload := generateStreamingUnsignedPayloadTrailerPayload(true)
urlStr := fmt.Sprintf("http://127.0.0.1:9000/%s/%s", bpcBucket, bpcObject)
req := mustNewRequest(http.MethodPut, urlStr, int64(len(payload)), bytes.NewReader([]byte(payload)), t)
req.Header.Set("Content-Encoding", "aws-chunked")
req.Header.Set("x-amz-decoded-content-length", "17408")
req.Header.Set("x-amz-content-sha256", streamingUnsignedPayload)
req.Header.Set("x-amz-trailer", "x-amz-checksum-crc32")
require.NoError(t, signRequestV4(req, bpcAccessKey, secretKey))
return mux.SetURLVars(req, map[string]string{"bucket": bpcBucket, "object": bpcObject})
}
func TestChunkedReaderDoesNotReauthorizeBucketPolicyPrincipal(t *testing.T) {
iam := newBucketPolicyOnlyIAM(t)
// The middleware authorizes the write through the bucket policy.
req := bpcStreamingPut(t, bpcSecretKey)
identity, errCode := iam.authRequest(req, s3_constants.ACTION_WRITE)
require.Equal(t, s3err.ErrNone, errCode, "the bucket policy must allow the PutObject")
require.NotNil(t, identity)
require.Empty(t, identity.Actions)
require.Empty(t, identity.PolicyNames)
// The handler then builds the body reader for the same request.
reader, errCode := iam.newChunkedReader(req)
require.Equal(t, s3err.ErrNone, errCode, "the chunked reader must not deny what the middleware allowed")
data, err := io.ReadAll(reader)
require.NoError(t, err)
require.Equal(t, strings.Repeat("a", 17408), string(data))
// The seed signature is still verified.
_, errCode = iam.newChunkedReader(bpcStreamingPut(t, "not-the-secret"))
require.Equal(t, s3err.ErrSignatureDoesNotMatch, errCode)
}