fix: add new auth method to update ownership within acl

Add helper util auth.UpdateBucketACLOwner() that sets new
default ACL based on new owner and removes old bucket policy.

The ChangeBucketOwner() remains in the backend.Backend
interface in case there is ever a backend that needs to manage
ownership in some other way than with bucket ACLs. The arguments
are changing to clarify the updated owner. This will break any
plugins implementing the old interface. They should use the new
auth.UpdateBucketACLOwner() or implement the corresponding
change specific for the backend.
This commit is contained in:
Ben McClelland
2025-07-09 12:19:09 -07:00
parent c196b5f999
commit f295df2217
8 changed files with 47 additions and 43 deletions

View File

@@ -466,3 +466,30 @@ func VerifyPublicBucketACL(ctx context.Context, be backend.Backend, bucket strin
return nil return nil
} }
// UpdateBucketACLOwner sets default ACL with new owner and removes
// any previous bucket policy that was in place
func UpdateBucketACLOwner(ctx context.Context, be backend.Backend, bucket, newOwner string) error {
acl := ACL{
Owner: newOwner,
Grantees: []Grantee{
{
Permission: PermissionFullControl,
Access: newOwner,
Type: types.TypeCanonicalUser,
},
},
}
result, err := json.Marshal(acl)
if err != nil {
return fmt.Errorf("marshal ACL: %w", err)
}
err = be.PutBucketAcl(ctx, bucket, result)
if err != nil {
return err
}
return be.DeleteBucketPolicy(ctx, bucket)
}

View File

@@ -1683,8 +1683,8 @@ func (az *Azure) GetObjectLegalHold(ctx context.Context, bucket, object, version
return &status, nil return &status, nil
} }
func (az *Azure) ChangeBucketOwner(ctx context.Context, bucket string, acl []byte) error { func (az *Azure) ChangeBucketOwner(ctx context.Context, bucket, owner string) error {
return az.PutBucketAcl(ctx, bucket, acl) return auth.UpdateBucketACLOwner(ctx, az, bucket, owner)
} }
// The action actually returns the containers owned by the user, who initialized the gateway // The action actually returns the containers owned by the user, who initialized the gateway

View File

@@ -96,7 +96,7 @@ type Backend interface {
GetObjectLegalHold(_ context.Context, bucket, object, versionId string) (*bool, error) GetObjectLegalHold(_ context.Context, bucket, object, versionId string) (*bool, error)
// non AWS actions // non AWS actions
ChangeBucketOwner(_ context.Context, bucket string, acl []byte) error ChangeBucketOwner(_ context.Context, bucket, owner string) error
ListBucketsAndOwners(context.Context) ([]s3response.Bucket, error) ListBucketsAndOwners(context.Context) ([]s3response.Bucket, error)
} }
@@ -280,7 +280,7 @@ func (BackendUnsupported) GetObjectLegalHold(_ context.Context, bucket, object,
return nil, s3err.GetAPIError(s3err.ErrNotImplemented) return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
} }
func (BackendUnsupported) ChangeBucketOwner(_ context.Context, bucket string, acl []byte) error { func (BackendUnsupported) ChangeBucketOwner(_ context.Context, bucket, owner string) error {
return s3err.GetAPIError(s3err.ErrNotImplemented) return s3err.GetAPIError(s3err.ErrNotImplemented)
} }
func (BackendUnsupported) ListBucketsAndOwners(context.Context) ([]s3response.Bucket, error) { func (BackendUnsupported) ListBucketsAndOwners(context.Context) ([]s3response.Bucket, error) {

View File

@@ -4888,8 +4888,8 @@ func (p *Posix) GetObjectRetention(_ context.Context, bucket, object, versionId
return data, nil return data, nil
} }
func (p *Posix) ChangeBucketOwner(ctx context.Context, bucket string, acl []byte) error { func (p *Posix) ChangeBucketOwner(ctx context.Context, bucket, owner string) error {
return p.PutBucketAcl(ctx, bucket, acl) return auth.UpdateBucketACLOwner(ctx, p, bucket, owner)
} }
func listBucketFileInfos(bucketlinks bool) ([]fs.FileInfo, error) { func listBucketFileInfos(bucketlinks bool) ([]fs.FileInfo, error) {

View File

@@ -1547,8 +1547,8 @@ func (s *S3Proxy) GetObjectLegalHold(ctx context.Context, bucket, object, versio
return nil, s3err.GetAPIError(s3err.ErrNotImplemented) return nil, s3err.GetAPIError(s3err.ErrNotImplemented)
} }
func (s *S3Proxy) ChangeBucketOwner(ctx context.Context, bucket string, acl []byte) error { func (s *S3Proxy) ChangeBucketOwner(ctx context.Context, bucket, owner string) error {
return s.PutBucketAcl(ctx, bucket, acl) return auth.UpdateBucketACLOwner(ctx, s, bucket, owner)
} }
func (s *S3Proxy) ListBucketsAndOwners(ctx context.Context) ([]s3response.Bucket, error) { func (s *S3Proxy) ListBucketsAndOwners(ctx context.Context) ([]s3response.Bucket, error) {

View File

@@ -15,13 +15,10 @@
package controllers package controllers
import ( import (
"encoding/json"
"encoding/xml" "encoding/xml"
"fmt"
"net/http" "net/http"
"strings" "strings"
"github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/versity/versitygw/auth" "github.com/versity/versitygw/auth"
"github.com/versity/versitygw/backend" "github.com/versity/versitygw/backend"
@@ -172,27 +169,7 @@ func (c AdminController) ChangeBucketOwner(ctx *fiber.Ctx) error {
}) })
} }
acl := auth.ACL{ err = c.be.ChangeBucketOwner(ctx.Context(), bucket, owner)
Owner: owner,
Grantees: []auth.Grantee{
{
Permission: auth.PermissionFullControl,
Access: owner,
Type: types.TypeCanonicalUser,
},
},
}
aclParsed, err := json.Marshal(acl)
if err != nil {
return SendResponse(ctx, fmt.Errorf("failed to marshal the bucket acl: %w", err),
&MetaOpts{
Logger: c.l,
Action: metrics.ActionAdminChangeBucketOwner,
})
}
err = c.be.ChangeBucketOwner(ctx.Context(), bucket, aclParsed)
return SendResponse(ctx, err, return SendResponse(ctx, err,
&MetaOpts{ &MetaOpts{
Logger: c.l, Logger: c.l,

View File

@@ -324,7 +324,7 @@ func TestAdminController_ChangeBucketOwner(t *testing.T) {
} }
adminController := AdminController{ adminController := AdminController{
be: &BackendMock{ be: &BackendMock{
ChangeBucketOwnerFunc: func(contextMoqParam context.Context, bucket string, acl []byte) error { ChangeBucketOwnerFunc: func(contextMoqParam context.Context, bucket, owner string) error {
return nil return nil
}, },
}, },

View File

@@ -26,7 +26,7 @@ var _ backend.Backend = &BackendMock{}
// AbortMultipartUploadFunc: func(contextMoqParam context.Context, abortMultipartUploadInput *s3.AbortMultipartUploadInput) error { // AbortMultipartUploadFunc: func(contextMoqParam context.Context, abortMultipartUploadInput *s3.AbortMultipartUploadInput) error {
// panic("mock out the AbortMultipartUpload method") // panic("mock out the AbortMultipartUpload method")
// }, // },
// ChangeBucketOwnerFunc: func(contextMoqParam context.Context, bucket string, acl []byte) error { // ChangeBucketOwnerFunc: func(contextMoqParam context.Context, bucket string, owner string) error {
// panic("mock out the ChangeBucketOwner method") // panic("mock out the ChangeBucketOwner method")
// }, // },
// CompleteMultipartUploadFunc: func(contextMoqParam context.Context, completeMultipartUploadInput *s3.CompleteMultipartUploadInput) (s3response.CompleteMultipartUploadResult, string, error) { // CompleteMultipartUploadFunc: func(contextMoqParam context.Context, completeMultipartUploadInput *s3.CompleteMultipartUploadInput) (s3response.CompleteMultipartUploadResult, string, error) {
@@ -196,7 +196,7 @@ type BackendMock struct {
AbortMultipartUploadFunc func(contextMoqParam context.Context, abortMultipartUploadInput *s3.AbortMultipartUploadInput) error AbortMultipartUploadFunc func(contextMoqParam context.Context, abortMultipartUploadInput *s3.AbortMultipartUploadInput) error
// ChangeBucketOwnerFunc mocks the ChangeBucketOwner method. // ChangeBucketOwnerFunc mocks the ChangeBucketOwner method.
ChangeBucketOwnerFunc func(contextMoqParam context.Context, bucket string, acl []byte) error ChangeBucketOwnerFunc func(contextMoqParam context.Context, bucket string, owner string) error
// CompleteMultipartUploadFunc mocks the CompleteMultipartUpload method. // CompleteMultipartUploadFunc mocks the CompleteMultipartUpload method.
CompleteMultipartUploadFunc func(contextMoqParam context.Context, completeMultipartUploadInput *s3.CompleteMultipartUploadInput) (s3response.CompleteMultipartUploadResult, string, error) CompleteMultipartUploadFunc func(contextMoqParam context.Context, completeMultipartUploadInput *s3.CompleteMultipartUploadInput) (s3response.CompleteMultipartUploadResult, string, error)
@@ -369,8 +369,8 @@ type BackendMock struct {
ContextMoqParam context.Context ContextMoqParam context.Context
// Bucket is the bucket argument value. // Bucket is the bucket argument value.
Bucket string Bucket string
// ACL is the acl argument value. // Owner is the owner argument value.
ACL []byte Owner string
} }
// CompleteMultipartUpload holds details about calls to the CompleteMultipartUpload method. // CompleteMultipartUpload holds details about calls to the CompleteMultipartUpload method.
CompleteMultipartUpload []struct { CompleteMultipartUpload []struct {
@@ -864,23 +864,23 @@ func (mock *BackendMock) AbortMultipartUploadCalls() []struct {
} }
// ChangeBucketOwner calls ChangeBucketOwnerFunc. // ChangeBucketOwner calls ChangeBucketOwnerFunc.
func (mock *BackendMock) ChangeBucketOwner(contextMoqParam context.Context, bucket string, acl []byte) error { func (mock *BackendMock) ChangeBucketOwner(contextMoqParam context.Context, bucket string, owner string) error {
if mock.ChangeBucketOwnerFunc == nil { if mock.ChangeBucketOwnerFunc == nil {
panic("BackendMock.ChangeBucketOwnerFunc: method is nil but Backend.ChangeBucketOwner was just called") panic("BackendMock.ChangeBucketOwnerFunc: method is nil but Backend.ChangeBucketOwner was just called")
} }
callInfo := struct { callInfo := struct {
ContextMoqParam context.Context ContextMoqParam context.Context
Bucket string Bucket string
ACL []byte Owner string
}{ }{
ContextMoqParam: contextMoqParam, ContextMoqParam: contextMoqParam,
Bucket: bucket, Bucket: bucket,
ACL: acl, Owner: owner,
} }
mock.lockChangeBucketOwner.Lock() mock.lockChangeBucketOwner.Lock()
mock.calls.ChangeBucketOwner = append(mock.calls.ChangeBucketOwner, callInfo) mock.calls.ChangeBucketOwner = append(mock.calls.ChangeBucketOwner, callInfo)
mock.lockChangeBucketOwner.Unlock() mock.lockChangeBucketOwner.Unlock()
return mock.ChangeBucketOwnerFunc(contextMoqParam, bucket, acl) return mock.ChangeBucketOwnerFunc(contextMoqParam, bucket, owner)
} }
// ChangeBucketOwnerCalls gets all the calls that were made to ChangeBucketOwner. // ChangeBucketOwnerCalls gets all the calls that were made to ChangeBucketOwner.
@@ -890,12 +890,12 @@ func (mock *BackendMock) ChangeBucketOwner(contextMoqParam context.Context, buck
func (mock *BackendMock) ChangeBucketOwnerCalls() []struct { func (mock *BackendMock) ChangeBucketOwnerCalls() []struct {
ContextMoqParam context.Context ContextMoqParam context.Context
Bucket string Bucket string
ACL []byte Owner string
} { } {
var calls []struct { var calls []struct {
ContextMoqParam context.Context ContextMoqParam context.Context
Bucket string Bucket string
ACL []byte Owner string
} }
mock.lockChangeBucketOwner.RLock() mock.lockChangeBucketOwner.RLock()
calls = mock.calls.ChangeBucketOwner calls = mock.calls.ChangeBucketOwner