mirror of
https://github.com/seaweedfs/seaweedfs.git
synced 2026-11-10 22:08:48 +01:00
s3: require dedicated object-lock permissions for x-amz-object-lock-* headers (#11492)
* s3: require dedicated object-lock permissions for x-amz-object-lock-* headers PutObject, CreateMultipartUpload, and PostPolicy honor the retention and legal-hold headers after only the route's s3:PutObject check, so a write-only principal could pin a version under COMPLIANCE retention that nobody can remove before its retain-until date. On AWS these headers require s3:PutObjectRetention / s3:PutObjectLegalHold. validateObjectLockHeaders is the shared funnel for all four call sites; it now authorizes the corresponding dedicated action when each header is present. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> * s3: record the verified POST-policy signer as the request identity The handler authenticated the form policy signature but stored only the signer's name, so downstream authorization (the object-lock header check) re-authenticated the form-signed request as anonymous and evaluated the wrong principal. Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
1 parent
e57f8c4d87
commit
a976b21010
5 files changed
+104
-21
No files matched your search
@@ -2,9 +2,11 @@ package s3api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
iamlib "github.com/seaweedfs/seaweedfs/weed/iam"
|
||||
@@ -88,6 +90,53 @@ func TestGovernanceBypassDoesNotInheritDeleteObjectVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// The object-lock request headers on PutObject/CreateMultipartUpload must be
|
||||
// authorized as s3:PutObjectRetention / s3:PutObjectLegalHold; s3:PutObject
|
||||
// alone used to set retention and legal hold on new versions.
|
||||
func TestObjectLockHeadersRequireDedicatedActions(t *testing.T) {
|
||||
iam := &IdentityAccessManagement{isAuthEnabled: true}
|
||||
require.NoError(t, iam.PutPolicy("Writer",
|
||||
`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:PutObject"],"Resource":"arn:aws:s3:::worm/*"}]}`))
|
||||
require.NoError(t, iam.PutPolicy("Locked",
|
||||
`{"Version":"2012-10-17","Statement":[{"Effect":"Allow","Action":["s3:PutObject","s3:PutObjectRetention","s3:PutObjectLegalHold"],"Resource":"arn:aws:s3:::worm/*"}]}`))
|
||||
s3a := &S3ApiServer{iam: iam}
|
||||
|
||||
retainUntil := time.Now().Add(24 * time.Hour).Format(time.RFC3339)
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
policy string
|
||||
headers map[string]string
|
||||
wantErr error
|
||||
}{
|
||||
{"writer sets retention", "Writer",
|
||||
map[string]string{s3_constants.AmzObjectLockMode: "COMPLIANCE", s3_constants.AmzObjectLockRetainUntilDate: retainUntil},
|
||||
ErrObjectLockNotAuthorized},
|
||||
{"writer sets legal hold", "Writer",
|
||||
map[string]string{s3_constants.AmzObjectLockLegalHold: s3_constants.LegalHoldOn},
|
||||
ErrObjectLockNotAuthorized},
|
||||
{"writer plain put", "Writer", nil, nil},
|
||||
{"locked principal sets both", "Locked",
|
||||
map[string]string{s3_constants.AmzObjectLockMode: "GOVERNANCE", s3_constants.AmzObjectLockRetainUntilDate: retainUntil, s3_constants.AmzObjectLockLegalHold: s3_constants.LegalHoldOn},
|
||||
nil},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
req := httptest.NewRequest(http.MethodPut, "http://localhost:8333/worm/doc.txt", nil)
|
||||
req = mux.SetURLVars(req, map[string]string{"bucket": "worm", "object": "doc.txt"})
|
||||
for k, v := range tc.headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
req = req.WithContext(s3_constants.SetIdentityInContext(req.Context(), &Identity{
|
||||
Name: "caller",
|
||||
PolicyNames: []string{tc.policy},
|
||||
Account: &Account{Id: "test-account"},
|
||||
}))
|
||||
if err := s3a.validateObjectLockHeaders(req, "worm", "doc.txt", true); !errors.Is(err, tc.wantErr) {
|
||||
t.Errorf("validateObjectLockHeaders() = %v, want %v", err, tc.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGovernanceBypassUsesBodyObjectKey(t *testing.T) {
|
||||
iam := newTestIAM()
|
||||
iam.identities[0].Actions = []Action{
|
||||
|
||||
Reference in new issue
Block a user