forked from TrueCloudLab/frostfs-s3-gw
[#213] Add object acl versioning
Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
5502fb97c3
commit
a9be642eaf
4 changed files with 441 additions and 139 deletions
|
@ -12,6 +12,7 @@ import (
|
||||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
||||||
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||||
|
"github.com/nspcc-dev/neofs-api-go/v2/acl"
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/api"
|
"github.com/nspcc-dev/neofs-s3-gw/api"
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/api/cache"
|
"github.com/nspcc-dev/neofs-s3-gw/api/cache"
|
||||||
"github.com/nspcc-dev/neofs-s3-gw/api/errors"
|
"github.com/nspcc-dev/neofs-s3-gw/api/errors"
|
||||||
|
@ -69,6 +70,7 @@ type bucketPolicy struct {
|
||||||
Version string `json:"Version"`
|
Version string `json:"Version"`
|
||||||
ID string `json:"Id"`
|
ID string `json:"Id"`
|
||||||
Statement []statement `json:"Statement"`
|
Statement []statement `json:"Statement"`
|
||||||
|
Bucket string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type statement struct {
|
type statement struct {
|
||||||
|
@ -89,10 +91,30 @@ type ast struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type astResource struct {
|
type astResource struct {
|
||||||
Name string
|
resourceInfo
|
||||||
Operations []*astOperation
|
Operations []*astOperation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type resourceInfo struct {
|
||||||
|
Bucket string
|
||||||
|
Object string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resourceInfo) Name() string {
|
||||||
|
if len(r.Object) == 0 {
|
||||||
|
return r.Bucket
|
||||||
|
}
|
||||||
|
if len(r.Version) == 0 {
|
||||||
|
return r.Bucket + "/" + r.Object
|
||||||
|
}
|
||||||
|
return r.Bucket + "/" + r.Object + ":" + r.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *resourceInfo) IsBucket() bool {
|
||||||
|
return len(r.Object) == 0
|
||||||
|
}
|
||||||
|
|
||||||
type astOperation struct {
|
type astOperation struct {
|
||||||
Users []string
|
Users []string
|
||||||
Role eacl.Role
|
Role eacl.Role
|
||||||
|
@ -137,27 +159,20 @@ func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Resource = reqInfo.BucketName
|
resInfo := &resourceInfo{Bucket: reqInfo.BucketName}
|
||||||
list.IsBucket = true
|
astBucket, err := aclToAst(list, resInfo)
|
||||||
|
|
||||||
bktPolicy, err := aclToPolicy(list)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not translate acl to policy", reqInfo, err)
|
h.logAndSendError(w, "could not translate acl to policy", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.updateBucketACL(r, bktPolicy, reqInfo.BucketName); err != nil {
|
if err = h.updateBucketACL(r, astBucket, reqInfo.BucketName); err != nil {
|
||||||
h.logAndSendError(w, "could not update bucket acl", reqInfo, err)
|
h.logAndSendError(w, "could not update bucket acl", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *handler) updateBucketACL(r *http.Request, bktPolicy *bucketPolicy, bkt string) error {
|
func (h *handler) updateBucketACL(r *http.Request, astChild *ast, bkt string) error {
|
||||||
astChild, err := policyToAst(bktPolicy)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("could not translate policy to ast: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
bucketACL, err := h.obj.GetBucketACL(r.Context(), bkt)
|
bucketACL, err := h.obj.GetBucketACL(r.Context(), bkt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not get bucket eacl: %w", err)
|
return fmt.Errorf("could not get bucket eacl: %w", err)
|
||||||
|
@ -169,8 +184,8 @@ func (h *handler) updateBucketACL(r *http.Request, bktPolicy *bucketPolicy, bkt
|
||||||
|
|
||||||
parentAst := tableToAst(bucketACL.EACL, bkt)
|
parentAst := tableToAst(bucketACL.EACL, bkt)
|
||||||
for _, resource := range parentAst.Resources {
|
for _, resource := range parentAst.Resources {
|
||||||
if resource.Name == bucketACL.Info.CID.String() {
|
if resource.Bucket == bucketACL.Info.CID.String() {
|
||||||
resource.Name = bkt
|
resource.Bucket = bkt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +194,7 @@ func (h *handler) updateBucketACL(r *http.Request, bktPolicy *bucketPolicy, bkt
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
table, err := astToTable(resAst, bkt)
|
table, err := astToTable(resAst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not translate ast to table: %w", err)
|
return fmt.Errorf("could not translate ast to table: %w", err)
|
||||||
}
|
}
|
||||||
|
@ -218,6 +233,7 @@ func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
var (
|
var (
|
||||||
err error
|
err error
|
||||||
reqInfo = api.GetReqInfo(r.Context())
|
reqInfo = api.GetReqInfo(r.Context())
|
||||||
|
versionID = reqInfo.URL.Query().Get(api.QueryVersionID)
|
||||||
)
|
)
|
||||||
|
|
||||||
list := &AccessControlPolicy{}
|
list := &AccessControlPolicy{}
|
||||||
|
@ -232,18 +248,22 @@ func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Resource = reqInfo.BucketName + "/" + reqInfo.ObjectName
|
resInfo := &resourceInfo{
|
||||||
|
Bucket: reqInfo.BucketName,
|
||||||
|
Object: reqInfo.ObjectName,
|
||||||
|
Version: versionID,
|
||||||
|
}
|
||||||
|
|
||||||
bktPolicy, err := aclToPolicy(list)
|
astObject, err := aclToAst(list, resInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not translate acl to policy", reqInfo, err)
|
h.logAndSendError(w, "could not translate acl to ast", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p := &layer.HeadObjectParams{
|
p := &layer.HeadObjectParams{
|
||||||
Bucket: reqInfo.BucketName,
|
Bucket: reqInfo.BucketName,
|
||||||
Object: reqInfo.ObjectName,
|
Object: reqInfo.ObjectName,
|
||||||
VersionID: reqInfo.URL.Query().Get(api.QueryVersionID),
|
VersionID: versionID,
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = h.obj.GetObjectInfo(r.Context(), p); err != nil {
|
if _, err = h.obj.GetObjectInfo(r.Context(), p); err != nil {
|
||||||
|
@ -251,7 +271,7 @@ func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = h.updateBucketACL(r, bktPolicy, reqInfo.BucketName); err != nil {
|
if err = h.updateBucketACL(r, astObject, reqInfo.BucketName); err != nil {
|
||||||
h.logAndSendError(w, "could not update bucket acl", reqInfo, err)
|
h.logAndSendError(w, "could not update bucket acl", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -295,13 +315,19 @@ func checkOwner(info *cache.BucketInfo, owner string) error {
|
||||||
|
|
||||||
func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
reqInfo := api.GetReqInfo(r.Context())
|
reqInfo := api.GetReqInfo(r.Context())
|
||||||
bktPolicy := &bucketPolicy{}
|
bktPolicy := &bucketPolicy{Bucket: reqInfo.BucketName}
|
||||||
if err := json.NewDecoder(r.Body).Decode(bktPolicy); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(bktPolicy); err != nil {
|
||||||
h.logAndSendError(w, "could not parse bucket policy", reqInfo, err)
|
h.logAndSendError(w, "could not parse bucket policy", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.updateBucketACL(r, bktPolicy, reqInfo.BucketName); err != nil {
|
astPolicy, err := policyToAst(bktPolicy)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not translate policy to ast", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = h.updateBucketACL(r, astPolicy, reqInfo.BucketName); err != nil {
|
||||||
h.logAndSendError(w, "could not update bucket acl", reqInfo, err)
|
h.logAndSendError(w, "could not update bucket acl", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -465,22 +491,32 @@ func tableToAst(table *eacl.Table, bktName string) *ast {
|
||||||
rr := make(map[string]*astResource)
|
rr := make(map[string]*astResource)
|
||||||
|
|
||||||
for _, record := range table.Records() {
|
for _, record := range table.Records() {
|
||||||
resname := bktName
|
resName := bktName
|
||||||
|
var objectName string
|
||||||
|
var version string
|
||||||
for _, filter := range record.Filters() {
|
for _, filter := range record.Filters() {
|
||||||
if filter.Matcher() == eacl.MatchStringEqual && filter.Key() == object.AttributeFileName {
|
if filter.Matcher() == eacl.MatchStringEqual {
|
||||||
resname = filter.Value()
|
if filter.Key() == object.AttributeFileName {
|
||||||
|
objectName = filter.Value()
|
||||||
|
resName += "/" + objectName
|
||||||
|
} else if filter.Key() == acl.FilterObjectID {
|
||||||
|
version = filter.Value()
|
||||||
|
resName += "/" + version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
r, ok := rr[resname]
|
}
|
||||||
|
r, ok := rr[resName]
|
||||||
if !ok {
|
if !ok {
|
||||||
r = &astResource{
|
r = &astResource{resourceInfo: resourceInfo{
|
||||||
Name: resname,
|
Bucket: bktName,
|
||||||
}
|
Object: objectName,
|
||||||
|
Version: version,
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
for _, target := range record.Targets() {
|
for _, target := range record.Targets() {
|
||||||
r.Operations = addToList(r.Operations, record, target)
|
r.Operations = addToList(r.Operations, record, target)
|
||||||
}
|
}
|
||||||
rr[resname] = r
|
rr[resName] = r
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, val := range rr {
|
for _, val := range rr {
|
||||||
|
@ -493,7 +529,7 @@ func tableToAst(table *eacl.Table, bktName string) *ast {
|
||||||
func mergeAst(parent, child *ast) (*ast, bool) {
|
func mergeAst(parent, child *ast) (*ast, bool) {
|
||||||
updated := false
|
updated := false
|
||||||
for _, resource := range child.Resources {
|
for _, resource := range child.Resources {
|
||||||
parentResource := getParentResource(parent, resource.Name)
|
parentResource := getParentResource(parent, resource)
|
||||||
if parentResource == nil {
|
if parentResource == nil {
|
||||||
parent.Resources = append(parent.Resources, resource)
|
parent.Resources = append(parent.Resources, resource)
|
||||||
updated = true
|
updated = true
|
||||||
|
@ -652,20 +688,21 @@ func removeUsers(resource *astResource, astOperation *astOperation, users []stri
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getParentResource(parent *ast, resource string) *astResource {
|
func getParentResource(parent *ast, resource *astResource) *astResource {
|
||||||
for _, parentResource := range parent.Resources {
|
for _, parentResource := range parent.Resources {
|
||||||
if resource == parentResource.Name {
|
if resource.Bucket == parentResource.Bucket && resource.Object == parentResource.Object &&
|
||||||
|
resource.Version == parentResource.Version {
|
||||||
return parentResource
|
return parentResource
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func astToTable(ast *ast, bkt string) (*eacl.Table, error) {
|
func astToTable(ast *ast) (*eacl.Table, error) {
|
||||||
table := eacl.NewTable()
|
table := eacl.NewTable()
|
||||||
|
|
||||||
for _, resource := range ast.Resources {
|
for _, resource := range ast.Resources {
|
||||||
records, err := formRecords(resource.Operations, resource.Name, bkt)
|
records, err := formRecords(resource.Operations, resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -677,7 +714,7 @@ func astToTable(ast *ast, bkt string) (*eacl.Table, error) {
|
||||||
return table, nil
|
return table, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func formRecords(operations []*astOperation, resource, bkt string) ([]*eacl.Record, error) {
|
func formRecords(operations []*astOperation, resource *astResource) ([]*eacl.Record, error) {
|
||||||
var res []*eacl.Record
|
var res []*eacl.Record
|
||||||
|
|
||||||
for _, astOp := range operations {
|
for _, astOp := range operations {
|
||||||
|
@ -695,9 +732,15 @@ func formRecords(operations []*astOperation, resource, bkt string) ([]*eacl.Reco
|
||||||
eacl.AddFormedTarget(record, eacl.RoleUser, (ecdsa.PublicKey)(*pk))
|
eacl.AddFormedTarget(record, eacl.RoleUser, (ecdsa.PublicKey)(*pk))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if resource != bkt {
|
if len(resource.Object) != 0 {
|
||||||
trimmedName := strings.TrimPrefix(resource, bkt+"/")
|
if len(resource.Version) != 0 {
|
||||||
record.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFileName, trimmedName)
|
oid := object.NewID()
|
||||||
|
if err := oid.Parse(resource.Version); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
record.AddObjectIDFilter(eacl.MatchStringEqual, oid)
|
||||||
|
}
|
||||||
|
record.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFileName, resource.Object)
|
||||||
}
|
}
|
||||||
res = append(res, record)
|
res = append(res, record)
|
||||||
}
|
}
|
||||||
|
@ -756,14 +799,15 @@ func policyToAst(bktPolicy *bucketPolicy) (*ast, error) {
|
||||||
trimmedResource := strings.TrimPrefix(resource, arnAwsPrefix)
|
trimmedResource := strings.TrimPrefix(resource, arnAwsPrefix)
|
||||||
r, ok := rr[trimmedResource]
|
r, ok := rr[trimmedResource]
|
||||||
if !ok {
|
if !ok {
|
||||||
r = &astResource{
|
r = &astResource{resourceInfo: resourceInfo{Bucket: bktPolicy.Bucket}}
|
||||||
Name: trimmedResource,
|
if trimmedResource != bktPolicy.Bucket {
|
||||||
|
r.Object = strings.TrimPrefix(trimmedResource, bktPolicy.Bucket+"/")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, action := range state.Action {
|
for _, action := range state.Action {
|
||||||
for _, op := range actionToOpMap[action] {
|
for _, op := range actionToOpMap[action] {
|
||||||
toAction := effectToAction(state.Effect)
|
toAction := effectToAction(state.Effect)
|
||||||
r.Operations = addTo(r.Operations, state, op, role, toAction)
|
r.Operations = addTo(r.Operations, state.Principal.CanonicalUser, op, role, toAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -782,9 +826,12 @@ func astToPolicy(ast *ast) *bucketPolicy {
|
||||||
bktPolicy := &bucketPolicy{}
|
bktPolicy := &bucketPolicy{}
|
||||||
|
|
||||||
for _, resource := range ast.Resources {
|
for _, resource := range ast.Resources {
|
||||||
|
if len(resource.Version) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
allowed, denied := triageOperations(resource.Operations)
|
allowed, denied := triageOperations(resource.Operations)
|
||||||
handleResourceOperations(bktPolicy, allowed, eacl.ActionAllow, resource.Name)
|
handleResourceOperations(bktPolicy, allowed, eacl.ActionAllow, resource.Name())
|
||||||
handleResourceOperations(bktPolicy, denied, eacl.ActionDeny, resource.Name)
|
handleResourceOperations(bktPolicy, denied, eacl.ActionDeny, resource.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
return bktPolicy
|
return bktPolicy
|
||||||
|
@ -845,7 +892,7 @@ func triageOperations(operations []*astOperation) ([]*astOperation, []*astOperat
|
||||||
return allowed, denied
|
return allowed, denied
|
||||||
}
|
}
|
||||||
|
|
||||||
func addTo(list []*astOperation, state statement, op eacl.Operation, role eacl.Role, action eacl.Action) []*astOperation {
|
func addTo(list []*astOperation, userID string, op eacl.Operation, role eacl.Role, action eacl.Action) []*astOperation {
|
||||||
var found *astOperation
|
var found *astOperation
|
||||||
for _, astop := range list {
|
for _, astop := range list {
|
||||||
if astop.Op == op && astop.Role == role {
|
if astop.Op == op && astop.Role == role {
|
||||||
|
@ -855,7 +902,7 @@ func addTo(list []*astOperation, state statement, op eacl.Operation, role eacl.R
|
||||||
|
|
||||||
if found != nil {
|
if found != nil {
|
||||||
if role == eacl.RoleUser {
|
if role == eacl.RoleUser {
|
||||||
found.Users = append(found.Users, state.Principal.CanonicalUser)
|
found.Users = append(found.Users, userID)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
astoperation := &astOperation{
|
astoperation := &astOperation{
|
||||||
|
@ -864,7 +911,7 @@ func addTo(list []*astOperation, state statement, op eacl.Operation, role eacl.R
|
||||||
Action: action,
|
Action: action,
|
||||||
}
|
}
|
||||||
if role == eacl.RoleUser {
|
if role == eacl.RoleUser {
|
||||||
astoperation.Users = append(astoperation.Users, state.Principal.CanonicalUser)
|
astoperation.Users = append(astoperation.Users, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
list = append(list, astoperation)
|
list = append(list, astoperation)
|
||||||
|
@ -873,13 +920,56 @@ func addTo(list []*astOperation, state statement, op eacl.Operation, role eacl.R
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
func aclToPolicy(acl *AccessControlPolicy) (*bucketPolicy, error) {
|
func aclToAst(acl *AccessControlPolicy, resInfo *resourceInfo) (*ast, error) {
|
||||||
if acl.Resource == "" {
|
res := &ast{}
|
||||||
return nil, fmt.Errorf("resource must not be empty")
|
|
||||||
|
resource := &astResource{resourceInfo: *resInfo}
|
||||||
|
|
||||||
|
ops := readOps
|
||||||
|
if resInfo.IsBucket() {
|
||||||
|
ops = append(ops, writeOps...)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, op := range ops {
|
||||||
|
operation := &astOperation{
|
||||||
|
Users: []string{acl.Owner.ID},
|
||||||
|
Role: eacl.RoleUser,
|
||||||
|
Op: op,
|
||||||
|
Action: eacl.ActionAllow,
|
||||||
|
}
|
||||||
|
resource.Operations = append(resource.Operations, operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, grant := range acl.AccessControlList {
|
||||||
|
if grant.Grantee.Type == acpAmazonCustomerByEmail || (grant.Grantee.Type == acpGroup && grant.Grantee.URI != allUsersGroup) {
|
||||||
|
return nil, fmt.Errorf("unsupported grantee: %v", grant.Grantee)
|
||||||
|
}
|
||||||
|
|
||||||
|
role := eacl.RoleUser
|
||||||
|
if grant.Grantee.Type == acpGroup {
|
||||||
|
role = eacl.RoleOthers
|
||||||
|
} else if grant.Grantee.ID == acl.Owner.ID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, action := range getActions(grant.Permission, resInfo.IsBucket()) {
|
||||||
|
for _, op := range actionToOpMap[action] {
|
||||||
|
resource.Operations = addTo(resource.Operations, grant.Grantee.ID, op, role, eacl.ActionAllow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Resources = []*astResource{resource}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func aclToPolicy(acl *AccessControlPolicy, resInfo *resourceInfo) (*bucketPolicy, error) {
|
||||||
|
if resInfo.Bucket == "" {
|
||||||
|
return nil, fmt.Errorf("resource bucket must not be empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
results := []statement{
|
results := []statement{
|
||||||
getAllowStatement(acl, acl.Owner.ID, aclFullControl),
|
getAllowStatement(resInfo, acl.Owner.ID, aclFullControl),
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, grant := range acl.AccessControlList {
|
for _, grant := range acl.AccessControlList {
|
||||||
|
@ -893,7 +983,7 @@ func aclToPolicy(acl *AccessControlPolicy) (*bucketPolicy, error) {
|
||||||
} else if user == acl.Owner.ID {
|
} else if user == acl.Owner.ID {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
results = append(results, getAllowStatement(acl, user, grant.Permission))
|
results = append(results, getAllowStatement(resInfo, user, grant.Permission))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &bucketPolicy{
|
return &bucketPolicy{
|
||||||
|
@ -901,14 +991,14 @@ func aclToPolicy(acl *AccessControlPolicy) (*bucketPolicy, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getAllowStatement(acl *AccessControlPolicy, id string, permission AWSACL) statement {
|
func getAllowStatement(resInfo *resourceInfo, id string, permission AWSACL) statement {
|
||||||
state := statement{
|
state := statement{
|
||||||
Effect: "Allow",
|
Effect: "Allow",
|
||||||
Principal: principal{
|
Principal: principal{
|
||||||
CanonicalUser: id,
|
CanonicalUser: id,
|
||||||
},
|
},
|
||||||
Action: getActions(permission, acl.IsBucket),
|
Action: getActions(permission, resInfo.IsBucket()),
|
||||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
Resource: []string{arnAwsPrefix + resInfo.Name()},
|
||||||
}
|
}
|
||||||
|
|
||||||
if id == allUsersWildcard {
|
if id == allUsersWildcard {
|
||||||
|
@ -1081,8 +1171,8 @@ func contains(list []eacl.Operation, op eacl.Operation) bool {
|
||||||
|
|
||||||
type getRecordFunc func(op eacl.Operation) *eacl.Record
|
type getRecordFunc func(op eacl.Operation) *eacl.Record
|
||||||
|
|
||||||
func bucketACLToTable(acp *AccessControlPolicy) (*eacl.Table, error) {
|
func bucketACLToTable(acp *AccessControlPolicy, resInfo *resourceInfo) (*eacl.Table, error) {
|
||||||
if !acp.IsBucket {
|
if !resInfo.IsBucket() {
|
||||||
return nil, fmt.Errorf("allowed only bucket acl")
|
return nil, fmt.Errorf("allowed only bucket acl")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,10 @@ package handler
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
@ -16,6 +19,12 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTableToAst(t *testing.T) {
|
func TestTableToAst(t *testing.T) {
|
||||||
|
b := make([]byte, 32)
|
||||||
|
_, err := io.ReadFull(rand.Reader, b)
|
||||||
|
require.NoError(t, err)
|
||||||
|
oid := object.NewID()
|
||||||
|
oid.SetSHA256(sha256.Sum256(b))
|
||||||
|
|
||||||
key, err := keys.NewPrivateKey()
|
key, err := keys.NewPrivateKey()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
key2, err := keys.NewPrivateKey()
|
key2, err := keys.NewPrivateKey()
|
||||||
|
@ -33,19 +42,24 @@ func TestTableToAst(t *testing.T) {
|
||||||
eacl.AddFormedTarget(record2, eacl.RoleUser, *(*ecdsa.PublicKey)(key.PublicKey()))
|
eacl.AddFormedTarget(record2, eacl.RoleUser, *(*ecdsa.PublicKey)(key.PublicKey()))
|
||||||
eacl.AddFormedTarget(record2, eacl.RoleUser, *(*ecdsa.PublicKey)(key2.PublicKey()))
|
eacl.AddFormedTarget(record2, eacl.RoleUser, *(*ecdsa.PublicKey)(key2.PublicKey()))
|
||||||
record2.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFileName, "objectName")
|
record2.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFileName, "objectName")
|
||||||
|
record2.AddObjectIDFilter(eacl.MatchStringEqual, oid)
|
||||||
table.AddRecord(record2)
|
table.AddRecord(record2)
|
||||||
|
|
||||||
expectedAst := &ast{
|
expectedAst := &ast{
|
||||||
Resources: []*astResource{
|
Resources: []*astResource{
|
||||||
{
|
{
|
||||||
Name: "bucketName",
|
resourceInfo: resourceInfo{Bucket: "bucketName"},
|
||||||
Operations: []*astOperation{{
|
Operations: []*astOperation{{
|
||||||
Role: eacl.RoleOthers,
|
Role: eacl.RoleOthers,
|
||||||
Op: eacl.OperationGet,
|
Op: eacl.OperationGet,
|
||||||
Action: eacl.ActionAllow,
|
Action: eacl.ActionAllow,
|
||||||
}}},
|
}}},
|
||||||
{
|
{
|
||||||
Name: "objectName",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucketName",
|
||||||
|
Object: "objectName",
|
||||||
|
Version: oid.String(),
|
||||||
|
},
|
||||||
Operations: []*astOperation{{
|
Operations: []*astOperation{{
|
||||||
Users: []string{
|
Users: []string{
|
||||||
hex.EncodeToString(key.PublicKey().Bytes()),
|
hex.EncodeToString(key.PublicKey().Bytes()),
|
||||||
|
@ -58,9 +72,9 @@ func TestTableToAst(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
actualAst := tableToAst(table, expectedAst.Resources[0].Name)
|
actualAst := tableToAst(table, expectedAst.Resources[0].Bucket)
|
||||||
|
|
||||||
if actualAst.Resources[0].Name == expectedAst.Resources[0].Name {
|
if actualAst.Resources[0].Name() == expectedAst.Resources[0].Name() {
|
||||||
require.Equal(t, expectedAst, actualAst)
|
require.Equal(t, expectedAst, actualAst)
|
||||||
} else {
|
} else {
|
||||||
require.Equal(t, len(expectedAst.Resources), len(actualAst.Resources))
|
require.Equal(t, len(expectedAst.Resources), len(actualAst.Resources))
|
||||||
|
@ -90,11 +104,14 @@ func TestPolicyToAst(t *testing.T) {
|
||||||
Resource: []string{"arn:aws:s3:::bucketName/object"},
|
Resource: []string{"arn:aws:s3:::bucketName/object"},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
policy.Bucket = "bucketName"
|
||||||
|
|
||||||
expectedAst := &ast{
|
expectedAst := &ast{
|
||||||
Resources: []*astResource{
|
Resources: []*astResource{
|
||||||
{
|
{
|
||||||
Name: "bucketName",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucketName",
|
||||||
|
},
|
||||||
Operations: []*astOperation{{
|
Operations: []*astOperation{{
|
||||||
Role: eacl.RoleOthers,
|
Role: eacl.RoleOthers,
|
||||||
Op: eacl.OperationPut,
|
Op: eacl.OperationPut,
|
||||||
|
@ -102,7 +119,10 @@ func TestPolicyToAst(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "bucketName/object",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucketName",
|
||||||
|
Object: "object",
|
||||||
|
},
|
||||||
Operations: getReadOps(key, eacl.RoleUser, eacl.ActionDeny),
|
Operations: getReadOps(key, eacl.RoleUser, eacl.ActionDeny),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -111,7 +131,7 @@ func TestPolicyToAst(t *testing.T) {
|
||||||
actualAst, err := policyToAst(policy)
|
actualAst, err := policyToAst(policy)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
if actualAst.Resources[0].Name == expectedAst.Resources[0].Name {
|
if actualAst.Resources[0].Name() == expectedAst.Resources[0].Name() {
|
||||||
require.Equal(t, expectedAst, actualAst)
|
require.Equal(t, expectedAst, actualAst)
|
||||||
} else {
|
} else {
|
||||||
require.Equal(t, len(expectedAst.Resources), len(actualAst.Resources))
|
require.Equal(t, len(expectedAst.Resources), len(actualAst.Resources))
|
||||||
|
@ -142,7 +162,10 @@ func TestMergeAstUnModified(t *testing.T) {
|
||||||
child := &ast{
|
child := &ast{
|
||||||
Resources: []*astResource{
|
Resources: []*astResource{
|
||||||
{
|
{
|
||||||
Name: "objectName",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucket",
|
||||||
|
Object: "objectName",
|
||||||
|
},
|
||||||
Operations: []*astOperation{{
|
Operations: []*astOperation{{
|
||||||
Users: []string{hex.EncodeToString(key.PublicKey().Bytes())},
|
Users: []string{hex.EncodeToString(key.PublicKey().Bytes())},
|
||||||
Role: eacl.RoleUser,
|
Role: eacl.RoleUser,
|
||||||
|
@ -156,7 +179,9 @@ func TestMergeAstUnModified(t *testing.T) {
|
||||||
parent := &ast{
|
parent := &ast{
|
||||||
Resources: []*astResource{
|
Resources: []*astResource{
|
||||||
{
|
{
|
||||||
Name: "bucket",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucket",
|
||||||
|
},
|
||||||
Operations: []*astOperation{{
|
Operations: []*astOperation{{
|
||||||
Role: eacl.RoleOthers,
|
Role: eacl.RoleOthers,
|
||||||
Op: eacl.OperationGet,
|
Op: eacl.OperationGet,
|
||||||
|
@ -176,7 +201,10 @@ func TestMergeAstModified(t *testing.T) {
|
||||||
child := &ast{
|
child := &ast{
|
||||||
Resources: []*astResource{
|
Resources: []*astResource{
|
||||||
{
|
{
|
||||||
Name: "objectName",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucket",
|
||||||
|
Object: "objectName",
|
||||||
|
},
|
||||||
Operations: []*astOperation{{
|
Operations: []*astOperation{{
|
||||||
Role: eacl.RoleOthers,
|
Role: eacl.RoleOthers,
|
||||||
Op: eacl.OperationPut,
|
Op: eacl.OperationPut,
|
||||||
|
@ -194,7 +222,10 @@ func TestMergeAstModified(t *testing.T) {
|
||||||
parent := &ast{
|
parent := &ast{
|
||||||
Resources: []*astResource{
|
Resources: []*astResource{
|
||||||
{
|
{
|
||||||
Name: "objectName",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucket",
|
||||||
|
Object: "objectName",
|
||||||
|
},
|
||||||
Operations: []*astOperation{{
|
Operations: []*astOperation{{
|
||||||
Users: []string{"user1"},
|
Users: []string{"user1"},
|
||||||
Role: eacl.RoleUser,
|
Role: eacl.RoleUser,
|
||||||
|
@ -208,7 +239,10 @@ func TestMergeAstModified(t *testing.T) {
|
||||||
expected := &ast{
|
expected := &ast{
|
||||||
Resources: []*astResource{
|
Resources: []*astResource{
|
||||||
{
|
{
|
||||||
Name: "objectName",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucket",
|
||||||
|
Object: "objectName",
|
||||||
|
},
|
||||||
Operations: []*astOperation{
|
Operations: []*astOperation{
|
||||||
child.Resources[0].Operations[0],
|
child.Resources[0].Operations[0],
|
||||||
{
|
{
|
||||||
|
@ -231,7 +265,10 @@ func TestMergeAstModifiedConflict(t *testing.T) {
|
||||||
child := &ast{
|
child := &ast{
|
||||||
Resources: []*astResource{
|
Resources: []*astResource{
|
||||||
{
|
{
|
||||||
Name: "objectName",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucket",
|
||||||
|
Object: "objectName",
|
||||||
|
},
|
||||||
Operations: []*astOperation{{
|
Operations: []*astOperation{{
|
||||||
Users: []string{"user1"},
|
Users: []string{"user1"},
|
||||||
Role: eacl.RoleUser,
|
Role: eacl.RoleUser,
|
||||||
|
@ -250,7 +287,10 @@ func TestMergeAstModifiedConflict(t *testing.T) {
|
||||||
parent := &ast{
|
parent := &ast{
|
||||||
Resources: []*astResource{
|
Resources: []*astResource{
|
||||||
{
|
{
|
||||||
Name: "objectName",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucket",
|
||||||
|
Object: "objectName",
|
||||||
|
},
|
||||||
Operations: []*astOperation{{
|
Operations: []*astOperation{{
|
||||||
Users: []string{"user1"},
|
Users: []string{"user1"},
|
||||||
Role: eacl.RoleUser,
|
Role: eacl.RoleUser,
|
||||||
|
@ -274,7 +314,10 @@ func TestMergeAstModifiedConflict(t *testing.T) {
|
||||||
expected := &ast{
|
expected := &ast{
|
||||||
Resources: []*astResource{
|
Resources: []*astResource{
|
||||||
{
|
{
|
||||||
Name: "objectName",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucket",
|
||||||
|
Object: "objectName",
|
||||||
|
},
|
||||||
Operations: []*astOperation{
|
Operations: []*astOperation{
|
||||||
{
|
{
|
||||||
Users: []string{"user2", "user1"},
|
Users: []string{"user2", "user1"},
|
||||||
|
@ -304,7 +347,9 @@ func TestAstToTable(t *testing.T) {
|
||||||
ast := &ast{
|
ast := &ast{
|
||||||
Resources: []*astResource{
|
Resources: []*astResource{
|
||||||
{
|
{
|
||||||
Name: "bucketName",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucketName",
|
||||||
|
},
|
||||||
Operations: []*astOperation{{
|
Operations: []*astOperation{{
|
||||||
Users: []string{hex.EncodeToString(key.PublicKey().Bytes())},
|
Users: []string{hex.EncodeToString(key.PublicKey().Bytes())},
|
||||||
Role: eacl.RoleUser,
|
Role: eacl.RoleUser,
|
||||||
|
@ -313,7 +358,10 @@ func TestAstToTable(t *testing.T) {
|
||||||
}},
|
}},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "bucketName/objectName",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucketName",
|
||||||
|
Object: "objectName",
|
||||||
|
},
|
||||||
Operations: []*astOperation{{
|
Operations: []*astOperation{{
|
||||||
Role: eacl.RoleOthers,
|
Role: eacl.RoleOthers,
|
||||||
Op: eacl.OperationGet,
|
Op: eacl.OperationGet,
|
||||||
|
@ -336,14 +384,16 @@ func TestAstToTable(t *testing.T) {
|
||||||
record2.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFileName, "objectName")
|
record2.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFileName, "objectName")
|
||||||
expectedTable.AddRecord(record2)
|
expectedTable.AddRecord(record2)
|
||||||
|
|
||||||
actualTable, err := astToTable(ast, "bucketName")
|
actualTable, err := astToTable(ast)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedTable, actualTable)
|
require.Equal(t, expectedTable, actualTable)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRemoveUsers(t *testing.T) {
|
func TestRemoveUsers(t *testing.T) {
|
||||||
resource := &astResource{
|
resource := &astResource{
|
||||||
Name: "name",
|
resourceInfo: resourceInfo{
|
||||||
|
Bucket: "bucket",
|
||||||
|
},
|
||||||
Operations: []*astOperation{{
|
Operations: []*astOperation{{
|
||||||
Users: []string{"user1", "user3"},
|
Users: []string{"user1", "user3"},
|
||||||
Role: eacl.RoleUser,
|
Role: eacl.RoleUser,
|
||||||
|
@ -361,7 +411,7 @@ func TestRemoveUsers(t *testing.T) {
|
||||||
removeUsers(resource, op, []string{"user1", "user2"})
|
removeUsers(resource, op, []string{"user1", "user2"})
|
||||||
|
|
||||||
require.Equal(t, len(resource.Operations), 1)
|
require.Equal(t, len(resource.Operations), 1)
|
||||||
require.Equal(t, resource.Name, resource.Name)
|
require.Equal(t, resource.Name(), resource.Name())
|
||||||
require.Equal(t, resource.Operations[0].Users, []string{"user3"})
|
require.Equal(t, resource.Operations[0].Users, []string{"user3"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -392,8 +442,10 @@ func TestBucketAclToPolicy(t *testing.T) {
|
||||||
},
|
},
|
||||||
Permission: aclWrite,
|
Permission: aclWrite,
|
||||||
}},
|
}},
|
||||||
Resource: "bucketName",
|
}
|
||||||
IsBucket: true,
|
|
||||||
|
resInfo := &resourceInfo{
|
||||||
|
Bucket: "bucketName",
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedPolicy := &bucketPolicy{
|
expectedPolicy := &bucketPolicy{
|
||||||
|
@ -404,24 +456,24 @@ func TestBucketAclToPolicy(t *testing.T) {
|
||||||
CanonicalUser: id,
|
CanonicalUser: id,
|
||||||
},
|
},
|
||||||
Action: []string{"s3:ListBucket", "s3:ListBucketVersions", "s3:ListBucketMultipartUploads", "s3:PutObject", "s3:DeleteObject"},
|
Action: []string{"s3:ListBucket", "s3:ListBucketVersions", "s3:ListBucketMultipartUploads", "s3:PutObject", "s3:DeleteObject"},
|
||||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
Resource: []string{arnAwsPrefix + resInfo.Name()},
|
||||||
}, {
|
}, {
|
||||||
Effect: "Allow",
|
Effect: "Allow",
|
||||||
Principal: principal{AWS: allUsersWildcard},
|
Principal: principal{AWS: allUsersWildcard},
|
||||||
Action: []string{"s3:ListBucket", "s3:ListBucketVersions", "s3:ListBucketMultipartUploads"},
|
Action: []string{"s3:ListBucket", "s3:ListBucketVersions", "s3:ListBucketMultipartUploads"},
|
||||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
Resource: []string{arnAwsPrefix + resInfo.Name()},
|
||||||
}, {
|
}, {
|
||||||
Effect: "Allow",
|
Effect: "Allow",
|
||||||
Principal: principal{
|
Principal: principal{
|
||||||
CanonicalUser: id2,
|
CanonicalUser: id2,
|
||||||
},
|
},
|
||||||
Action: []string{"s3:PutObject", "s3:DeleteObject"},
|
Action: []string{"s3:PutObject", "s3:DeleteObject"},
|
||||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
Resource: []string{arnAwsPrefix + resInfo.Name()},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
actualPolicy, err := aclToPolicy(acl)
|
actualPolicy, err := aclToPolicy(acl, resInfo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedPolicy, actualPolicy)
|
require.Equal(t, expectedPolicy, actualPolicy)
|
||||||
}
|
}
|
||||||
|
@ -459,8 +511,11 @@ func TestObjectAclToPolicy(t *testing.T) {
|
||||||
},
|
},
|
||||||
Permission: aclRead,
|
Permission: aclRead,
|
||||||
}},
|
}},
|
||||||
Resource: "bucketName/object",
|
}
|
||||||
IsBucket: false,
|
|
||||||
|
resInfo := &resourceInfo{
|
||||||
|
Bucket: "bucketName",
|
||||||
|
Object: "object",
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedPolicy := &bucketPolicy{
|
expectedPolicy := &bucketPolicy{
|
||||||
|
@ -471,7 +526,7 @@ func TestObjectAclToPolicy(t *testing.T) {
|
||||||
CanonicalUser: id,
|
CanonicalUser: id,
|
||||||
},
|
},
|
||||||
Action: []string{"s3:GetObject", "s3:GetObjectVersion"},
|
Action: []string{"s3:GetObject", "s3:GetObjectVersion"},
|
||||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
Resource: []string{arnAwsPrefix + resInfo.Name()},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Effect: "Allow",
|
Effect: "Allow",
|
||||||
|
@ -479,17 +534,17 @@ func TestObjectAclToPolicy(t *testing.T) {
|
||||||
CanonicalUser: id2,
|
CanonicalUser: id2,
|
||||||
},
|
},
|
||||||
Action: []string{"s3:GetObject", "s3:GetObjectVersion"},
|
Action: []string{"s3:GetObject", "s3:GetObjectVersion"},
|
||||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
Resource: []string{arnAwsPrefix + resInfo.Name()},
|
||||||
}, {
|
}, {
|
||||||
Effect: "Allow",
|
Effect: "Allow",
|
||||||
Principal: principal{AWS: allUsersWildcard},
|
Principal: principal{AWS: allUsersWildcard},
|
||||||
Action: []string{"s3:GetObject", "s3:GetObjectVersion"},
|
Action: []string{"s3:GetObject", "s3:GetObjectVersion"},
|
||||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
Resource: []string{arnAwsPrefix + resInfo.Name()},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
actualPolicy, err := aclToPolicy(acl)
|
actualPolicy, err := aclToPolicy(acl, resInfo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedPolicy, actualPolicy)
|
require.Equal(t, expectedPolicy, actualPolicy)
|
||||||
}
|
}
|
||||||
|
@ -674,8 +729,6 @@ func TestBucketAclToTable(t *testing.T) {
|
||||||
},
|
},
|
||||||
Permission: aclWrite,
|
Permission: aclWrite,
|
||||||
}},
|
}},
|
||||||
Resource: "bucketName",
|
|
||||||
IsBucket: true,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
expectedTable := new(eacl.Table)
|
expectedTable := new(eacl.Table)
|
||||||
|
@ -691,8 +744,164 @@ func TestBucketAclToTable(t *testing.T) {
|
||||||
for _, op := range fullOps {
|
for _, op := range fullOps {
|
||||||
expectedTable.AddRecord(getOthersRecord(op, eacl.ActionDeny))
|
expectedTable.AddRecord(getOthersRecord(op, eacl.ActionDeny))
|
||||||
}
|
}
|
||||||
|
resInfo := &resourceInfo{
|
||||||
|
Bucket: "bucketName",
|
||||||
|
}
|
||||||
|
|
||||||
actualTable, err := bucketACLToTable(acl)
|
actualTable, err := bucketACLToTable(acl, resInfo)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, expectedTable.Records(), actualTable.Records())
|
require.Equal(t, expectedTable.Records(), actualTable.Records())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestObjectAclToAst(t *testing.T) {
|
||||||
|
b := make([]byte, 32)
|
||||||
|
_, err := io.ReadFull(rand.Reader, b)
|
||||||
|
require.NoError(t, err)
|
||||||
|
oid := object.NewID()
|
||||||
|
oid.SetSHA256(sha256.Sum256(b))
|
||||||
|
|
||||||
|
key, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
key2, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
id := hex.EncodeToString(key.PublicKey().Bytes())
|
||||||
|
id2 := hex.EncodeToString(key2.PublicKey().Bytes())
|
||||||
|
|
||||||
|
acl := &AccessControlPolicy{
|
||||||
|
Owner: Owner{
|
||||||
|
ID: id,
|
||||||
|
DisplayName: "user1",
|
||||||
|
},
|
||||||
|
AccessControlList: []*Grant{{
|
||||||
|
Grantee: &Grantee{
|
||||||
|
ID: id,
|
||||||
|
Type: acpCanonicalUser,
|
||||||
|
},
|
||||||
|
Permission: aclFullControl,
|
||||||
|
}, {
|
||||||
|
Grantee: &Grantee{
|
||||||
|
ID: id2,
|
||||||
|
Type: acpCanonicalUser,
|
||||||
|
},
|
||||||
|
Permission: aclRead,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
resInfo := &resourceInfo{
|
||||||
|
Bucket: "bucketName",
|
||||||
|
Object: "object",
|
||||||
|
Version: oid.String(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var operations []*astOperation
|
||||||
|
for _, op := range readOps {
|
||||||
|
astOp := &astOperation{Users: []string{
|
||||||
|
hex.EncodeToString(key.PublicKey().Bytes()),
|
||||||
|
hex.EncodeToString(key2.PublicKey().Bytes()),
|
||||||
|
},
|
||||||
|
Role: eacl.RoleUser,
|
||||||
|
Op: op,
|
||||||
|
Action: eacl.ActionAllow,
|
||||||
|
}
|
||||||
|
operations = append(operations, astOp)
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedAst := &ast{
|
||||||
|
Resources: []*astResource{
|
||||||
|
{
|
||||||
|
resourceInfo: *resInfo,
|
||||||
|
Operations: operations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actualAst, err := aclToAst(acl, resInfo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedAst, actualAst)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBucketAclToAst(t *testing.T) {
|
||||||
|
b := make([]byte, 32)
|
||||||
|
_, err := io.ReadFull(rand.Reader, b)
|
||||||
|
require.NoError(t, err)
|
||||||
|
oid := object.NewID()
|
||||||
|
oid.SetSHA256(sha256.Sum256(b))
|
||||||
|
|
||||||
|
key, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
key2, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
id := hex.EncodeToString(key.PublicKey().Bytes())
|
||||||
|
id2 := hex.EncodeToString(key2.PublicKey().Bytes())
|
||||||
|
|
||||||
|
acl := &AccessControlPolicy{
|
||||||
|
Owner: Owner{
|
||||||
|
ID: id,
|
||||||
|
DisplayName: "user1",
|
||||||
|
},
|
||||||
|
AccessControlList: []*Grant{
|
||||||
|
{
|
||||||
|
Grantee: &Grantee{
|
||||||
|
ID: id2,
|
||||||
|
Type: acpCanonicalUser,
|
||||||
|
},
|
||||||
|
Permission: aclWrite,
|
||||||
|
}, {
|
||||||
|
Grantee: &Grantee{
|
||||||
|
URI: allUsersGroup,
|
||||||
|
Type: acpGroup,
|
||||||
|
},
|
||||||
|
Permission: aclRead,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var operations []*astOperation
|
||||||
|
for _, op := range readOps {
|
||||||
|
astOp := &astOperation{Users: []string{
|
||||||
|
hex.EncodeToString(key.PublicKey().Bytes()),
|
||||||
|
},
|
||||||
|
Role: eacl.RoleUser,
|
||||||
|
Op: op,
|
||||||
|
Action: eacl.ActionAllow,
|
||||||
|
}
|
||||||
|
operations = append(operations, astOp)
|
||||||
|
}
|
||||||
|
for _, op := range writeOps {
|
||||||
|
astOp := &astOperation{Users: []string{
|
||||||
|
hex.EncodeToString(key.PublicKey().Bytes()),
|
||||||
|
hex.EncodeToString(key2.PublicKey().Bytes()),
|
||||||
|
},
|
||||||
|
Role: eacl.RoleUser,
|
||||||
|
Op: op,
|
||||||
|
Action: eacl.ActionAllow,
|
||||||
|
}
|
||||||
|
operations = append(operations, astOp)
|
||||||
|
}
|
||||||
|
for _, op := range readOps {
|
||||||
|
astOp := &astOperation{
|
||||||
|
Role: eacl.RoleOthers,
|
||||||
|
Op: op,
|
||||||
|
Action: eacl.ActionAllow,
|
||||||
|
}
|
||||||
|
operations = append(operations, astOp)
|
||||||
|
}
|
||||||
|
|
||||||
|
resInfo := &resourceInfo{Bucket: "bucketName"}
|
||||||
|
|
||||||
|
expectedAst := &ast{
|
||||||
|
Resources: []*astResource{
|
||||||
|
{
|
||||||
|
resourceInfo: *resInfo,
|
||||||
|
Operations: operations,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
actualAst, err := aclToAst(acl, resInfo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedAst, actualAst)
|
||||||
|
}
|
||||||
|
|
|
@ -38,47 +38,6 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if containsACLHeaders(r) {
|
|
||||||
objectACL, err := parseACLHeaders(r)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "could not parse object acl", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
objectACL.Resource = reqInfo.BucketName + "/" + reqInfo.ObjectName
|
|
||||||
|
|
||||||
bktPolicy, err := aclToPolicy(objectACL)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "could not translate object acl to bucket policy", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
astChild, err := policyToAst(bktPolicy)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "could not translate policy to ast", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bacl, err := h.obj.GetBucketACL(r.Context(), reqInfo.BucketName)
|
|
||||||
if err != nil {
|
|
||||||
h.logAndSendError(w, "could not get bucket eacl", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
parentAst := tableToAst(bacl.EACL, reqInfo.BucketName)
|
|
||||||
for _, resource := range parentAst.Resources {
|
|
||||||
if resource.Name == bacl.Info.CID.String() {
|
|
||||||
resource.Name = reqInfo.BucketName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if resAst, updated := mergeAst(parentAst, astChild); updated {
|
|
||||||
if newEaclTable, err = astToTable(resAst, reqInfo.BucketName); err != nil {
|
|
||||||
h.logAndSendError(w, "could not translate ast to table", reqInfo, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName)
|
bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could not get bucket eacl", reqInfo, err)
|
h.logAndSendError(w, "could not get bucket eacl", reqInfo, err)
|
||||||
|
@ -108,6 +67,52 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if containsACLHeaders(r) {
|
||||||
|
objectACL, err := parseACLHeaders(r)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not parse object acl", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resInfo := &resourceInfo{
|
||||||
|
Bucket: reqInfo.BucketName,
|
||||||
|
Object: reqInfo.ObjectName,
|
||||||
|
Version: info.Version(),
|
||||||
|
}
|
||||||
|
|
||||||
|
bktPolicy, err := aclToPolicy(objectACL, resInfo)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not translate object acl to bucket policy", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
astChild, err := policyToAst(bktPolicy)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not translate policy to ast", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bacl, err := h.obj.GetBucketACL(r.Context(), reqInfo.BucketName)
|
||||||
|
if err != nil {
|
||||||
|
h.logAndSendError(w, "could not get bucket eacl", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
parentAst := tableToAst(bacl.EACL, reqInfo.BucketName)
|
||||||
|
for _, resource := range parentAst.Resources {
|
||||||
|
if resource.Bucket == bacl.Info.CID.String() {
|
||||||
|
resource.Bucket = reqInfo.BucketName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if resAst, updated := mergeAst(parentAst, astChild); updated {
|
||||||
|
if newEaclTable, err = astToTable(resAst); err != nil {
|
||||||
|
h.logAndSendError(w, "could not translate ast to table", reqInfo, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if tagSet != nil {
|
if tagSet != nil {
|
||||||
if err = h.obj.PutObjectTagging(r.Context(), &layer.PutTaggingParams{ObjectInfo: info, TagSet: tagSet}); err != nil {
|
if err = h.obj.PutObjectTagging(r.Context(), &layer.PutTaggingParams{ObjectInfo: info, TagSet: tagSet}); err != nil {
|
||||||
h.logAndSendError(w, "could not upload object tagging", reqInfo, err)
|
h.logAndSendError(w, "could not upload object tagging", reqInfo, err)
|
||||||
|
@ -191,9 +196,9 @@ func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
h.logAndSendError(w, "could not parse bucket acl", reqInfo, err)
|
h.logAndSendError(w, "could not parse bucket acl", reqInfo, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
bktACL.IsBucket = true
|
resInfo := &resourceInfo{Bucket: reqInfo.BucketName}
|
||||||
|
|
||||||
p.EACL, err = bucketACLToTable(bktACL)
|
p.EACL, err = bucketACLToTable(bktACL, resInfo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logAndSendError(w, "could translate bucket acl to eacl", reqInfo, err)
|
h.logAndSendError(w, "could translate bucket acl to eacl", reqInfo, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -57,8 +57,6 @@ type AccessControlPolicy struct {
|
||||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ AccessControlPolicy" json:"-"`
|
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ AccessControlPolicy" json:"-"`
|
||||||
Owner Owner
|
Owner Owner
|
||||||
AccessControlList []*Grant `xml:"AccessControlList>Grant"`
|
AccessControlList []*Grant `xml:"AccessControlList>Grant"`
|
||||||
Resource string `xml:"-"`
|
|
||||||
IsBucket bool `xml:"-"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grant is container for Grantee data.
|
// Grant is container for Grantee data.
|
||||||
|
|
Loading…
Reference in a new issue