mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-01 09:50:39 +01:00
s3: allow POST Object uploads when auth is off (#12273)
* s3: allow POST Object uploads when auth is off With no identities configured every other handler skips authentication, but PostPolicyBucketHandler still verified the form signature, so a browser form upload failed with InvalidAccessKeyId (or a malformed credential error without signature fields). Skip the signature check when auth is off; the policy conditions are still enforced. * s3: update AuthPostPolicy comment for the auth-off path --------- Co-authored-by: Chris Lu <chris.lu@gmail.com>
This commit is contained in:
1 parent
05584c541c
commit
ecd716e0e2
3 files changed
+67
-14
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
Reference in new issue
Block a user