diff --git a/weed/s3api/auth_credentials.go b/weed/s3api/auth_credentials.go index 502283a944..01ec4eea64 100644 --- a/weed/s3api/auth_credentials.go +++ b/weed/s3api/auth_credentials.go @@ -1698,10 +1698,9 @@ func (iam *IdentityAccessManagement) Auth(f http.HandlerFunc, action Action) htt // AuthPostPolicy is a specialized authentication wrapper for PostPolicy requests. // It allows requests with multipart/form-data to proceed even if classified as Anonymous, -// because the actual authentication (signature verification) for ALL PostPolicy requests is -// performed unconditionally in PostPolicyBucketHandler.doesPolicySignatureMatch(). -// This delegation only defers the initial authentication classification; it does NOT bypass -// signature verification, which is mandatory for all PostPolicy uploads. +// because PostPolicy signature verification happens in PostPolicyBucketHandler when auth +// is enabled. This delegation only defers the initial authentication classification; it +// does NOT bypass signature verification for authenticated uploads. func (iam *IdentityAccessManagement) AuthPostPolicy(f http.HandlerFunc, action Action) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { scrubInternalHeaders(r) diff --git a/weed/s3api/s3api_object_handlers_postpolicy.go b/weed/s3api/s3api_object_handlers_postpolicy.go index 78fdedd8d0..51d21c4bf8 100644 --- a/weed/s3api/s3api_object_handlers_postpolicy.go +++ b/weed/s3api/s3api_object_handlers_postpolicy.go @@ -80,16 +80,20 @@ func (s3a *S3ApiServer) PostPolicyBucketHandler(w http.ResponseWriter, r *http.R } } - // Verify policy signature. - identity, errCode := s3a.iam.doesPolicySignatureMatch(r.Context(), formValues) - if errCode != s3err.ErrNone { - s3err.WriteErrorResponse(w, r, refineDenialForBucketName(r, identity, errCode)) - return - } - if identity != nil { - r = r.WithContext(recordIdentityInContext(r, identity)) - if identity.Account != nil { - r.Header.Set(s3_constants.AmzAccountId, identity.Account.Id) + // Verify policy signature. With auth off there are no keys to check it against, as on the other handlers. + var identity *Identity + errCode := s3err.ErrNone + if s3a.iam.isEnabled() { + identity, errCode = s3a.iam.doesPolicySignatureMatch(r.Context(), formValues) + if errCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, refineDenialForBucketName(r, identity, errCode)) + return + } + if identity != nil { + r = r.WithContext(recordIdentityInContext(r, identity)) + if identity.Account != nil { + r.Header.Set(s3_constants.AmzAccountId, identity.Account.Id) + } } } diff --git a/weed/s3api/s3api_object_handlers_postpolicy_test.go b/weed/s3api/s3api_object_handlers_postpolicy_test.go index 2dd1874c74..b8e65d6c8d 100644 --- a/weed/s3api/s3api_object_handlers_postpolicy_test.go +++ b/weed/s3api/s3api_object_handlers_postpolicy_test.go @@ -683,3 +683,53 @@ func TestPostPolicyBucketHandler_PolicyViolationReturns403(t *testing.T) { assert.NotContains(t, rec.Body.String(), "InvalidAccessKeyId", "access key must be known so the handler reaches the policy-check branch") } + +// With auth off there are no keys, so a POST Object form must not be rejected +// for its signature; the policy conditions still apply. +func TestPostPolicyBucketHandler_AuthDisabledSkipsSignature(t *testing.T) { + const testBucket = "test-bucket" + + iam := &IdentityAccessManagement{ + hashes: make(map[string]*sync.Pool), + hashCounters: make(map[string]*int32), + } + assert.NoError(t, iam.loadS3ApiConfiguration(&iam_pb.S3ApiConfiguration{})) + assert.False(t, iam.isEnabled()) + + s3a := &S3ApiServer{ + option: &S3ApiServerOption{BucketsPath: "/buckets"}, + iam: iam, + } + s3a.bucketRegistry = NewBucketRegistry(s3a) + s3a.bucketRegistry.setMetadataCache(&BucketMetaData{Name: testBucket, IsTableBucket: false}) + + now := time.Now().UTC() + credential := fmt.Sprintf("ANYKEY/%s/us-east-1/s3/aws4_request", now.Format(yyyymmdd)) + policyJSON := fmt.Sprintf(`{"expiration":"%s","conditions":[["eq","$key","required.txt"]]}`, + now.Add(time.Hour).Format("2006-01-02T15:04:05.000Z")) + + var buf bytes.Buffer + writer := multipart.NewWriter(&buf) + assert.NoError(t, writer.WriteField("key", "wrong.txt")) + assert.NoError(t, writer.WriteField("x-amz-credential", credential)) + assert.NoError(t, writer.WriteField("x-amz-algorithm", "AWS4-HMAC-SHA256")) + assert.NoError(t, writer.WriteField("x-amz-date", now.Format(iso8601Format))) + assert.NoError(t, writer.WriteField("policy", base64.StdEncoding.EncodeToString([]byte(policyJSON)))) + assert.NoError(t, writer.WriteField("x-amz-signature", "00")) + filePart, err := writer.CreateFormFile("file", "payload.txt") + assert.NoError(t, err) + _, err = filePart.Write([]byte("contents")) + assert.NoError(t, err) + assert.NoError(t, writer.Close()) + + req := httptest.NewRequest(http.MethodPost, "/"+testBucket, &buf) + req.Header.Set("Content-Type", writer.FormDataContentType()) + req = mux.SetURLVars(req, map[string]string{"bucket": testBucket}) + rec := httptest.NewRecorder() + + s3a.PostPolicyBucketHandler(rec, req) + + assert.NotContains(t, rec.Body.String(), "InvalidAccessKeyId") + assert.Contains(t, rec.Body.String(), "Policy Condition failed", + "the policy check must still run, body: %s", rec.Body.String()) +}