diff --git a/api/data/info.go b/api/data/info.go index 3d85788..78a124a 100644 --- a/api/data/info.go +++ b/api/data/info.go @@ -31,7 +31,6 @@ type ( LocationConstraint string ObjectLockEnabled bool HomomorphicHashDisabled bool - APEEnabled bool } // ObjectInfo holds S3 object data. diff --git a/api/handler/acl.go b/api/handler/acl.go index 7933340..e463874 100644 --- a/api/handler/acl.go +++ b/api/handler/acl.go @@ -2,7 +2,6 @@ package handler import ( "context" - "crypto/ecdsa" "crypto/elliptic" "encoding/hex" "encoding/json" @@ -10,11 +9,8 @@ import ( "fmt" "io" "net/http" - "sort" - "strconv" "strings" - v2acl "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" @@ -22,43 +18,15 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" - oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" engineiam "git.frostfs.info/TrueCloudLab/policy-engine/iam" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "go.uber.org/zap" ) -var ( - writeOps = []eacl.Operation{eacl.OperationPut, eacl.OperationDelete} - readOps = []eacl.Operation{eacl.OperationGet, eacl.OperationHead, - eacl.OperationSearch, eacl.OperationRange, eacl.OperationRangeHash} - fullOps = []eacl.Operation{eacl.OperationGet, eacl.OperationHead, eacl.OperationPut, - eacl.OperationDelete, eacl.OperationSearch, eacl.OperationRange, eacl.OperationRangeHash} -) - -var actionToOpMap = map[string][]eacl.Operation{ - s3DeleteObject: {eacl.OperationDelete}, - s3GetObject: readOps, - s3PutObject: {eacl.OperationPut}, - s3ListBucket: readOps, -} - const ( - arnAwsPrefix = "arn:aws:s3:::" - allUsersWildcard = "*" - allUsersGroup = "http://acs.amazonaws.com/groups/global/AllUsers" - - s3DeleteObject = "s3:DeleteObject" - s3GetObject = "s3:GetObject" - s3PutObject = "s3:PutObject" - s3ListBucket = "s3:ListBucket" - s3ListBucketVersions = "s3:ListBucketVersions" - s3ListBucketMultipartUploads = "s3:ListBucketMultipartUploads" - s3GetObjectVersion = "s3:GetObjectVersion" + arnAwsPrefix = "arn:aws:s3:::" + allUsersGroup = "http://acs.amazonaws.com/groups/global/AllUsers" ) // AWSACL is aws permission constants. @@ -74,179 +42,10 @@ const ( type GranteeType string const ( - acpCanonicalUser GranteeType = "CanonicalUser" - acpAmazonCustomerByEmail GranteeType = "AmazonCustomerByEmail" - acpGroup GranteeType = "Group" + acpCanonicalUser GranteeType = "CanonicalUser" + acpGroup GranteeType = "Group" ) -type bucketPolicy struct { - Version string `json:"Version"` - ID string `json:"Id"` - Statement []statement `json:"Statement"` - Bucket string `json:"-"` -} - -type statement struct { - Sid string `json:"Sid"` - Effect string `json:"Effect"` - Principal principal `json:"Principal"` - Action []string `json:"Action"` - Resource []string `json:"Resource"` -} - -type principal struct { - AWS string `json:"AWS,omitempty"` - CanonicalUser string `json:"CanonicalUser,omitempty"` -} - -type orderedAstResource struct { - Index int - Resource *astResource -} - -type ast struct { - Resources []*astResource -} - -type astResource struct { - resourceInfo - 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 { - Users []string - Op eacl.Operation - Action eacl.Action -} - -func (a astOperation) IsGroupGrantee() bool { - return len(a.Users) == 0 -} - -const ( - serviceRecordResourceKey = "Resource" - serviceRecordGroupLengthKey = "GroupLength" -) - -type ServiceRecord struct { - Resource string - GroupRecordsLength int -} - -func (s ServiceRecord) ToEACLRecord() *eacl.Record { - serviceRecord := eacl.NewRecord() - serviceRecord.SetAction(eacl.ActionAllow) - serviceRecord.SetOperation(eacl.OperationGet) - serviceRecord.AddFilter(eacl.HeaderFromService, eacl.MatchUnknown, serviceRecordResourceKey, s.Resource) - serviceRecord.AddFilter(eacl.HeaderFromService, eacl.MatchUnknown, serviceRecordGroupLengthKey, strconv.Itoa(s.GroupRecordsLength)) - eacl.AddFormedTarget(serviceRecord, eacl.RoleSystem) - return serviceRecord -} - -var ( - errInvalidStatement = stderrors.New("invalid statement") - errInvalidPrincipal = stderrors.New("invalid principal") -) - -func (s *statement) UnmarshalJSON(data []byte) error { - var statementMap map[string]interface{} - if err := json.Unmarshal(data, &statementMap); err != nil { - return err - } - - sidField, ok := statementMap["Sid"] - if ok { - if s.Sid, ok = sidField.(string); !ok { - return errInvalidStatement - } - } - - effectField, ok := statementMap["Effect"] - if ok { - if s.Effect, ok = effectField.(string); !ok { - return errInvalidStatement - } - } - - principalField, ok := statementMap["Principal"] - if ok { - principalMap, ok := principalField.(map[string]interface{}) - if !ok { - return errInvalidPrincipal - } - - awsField, ok := principalMap["AWS"] - if ok { - if s.Principal.AWS, ok = awsField.(string); !ok { - return fmt.Errorf("%w: 'AWS' field must be string", errInvalidPrincipal) - } - } - - canonicalUserField, ok := principalMap["CanonicalUser"] - if ok { - if s.Principal.CanonicalUser, ok = canonicalUserField.(string); !ok { - return errInvalidPrincipal - } - } - } - - actionField, ok := statementMap["Action"] - if ok { - switch actionField := actionField.(type) { - case []interface{}: - s.Action = make([]string, len(actionField)) - for i, action := range actionField { - if s.Action[i], ok = action.(string); !ok { - return errInvalidStatement - } - } - case string: - s.Action = []string{actionField} - default: - return errInvalidStatement - } - } - - resourceField, ok := statementMap["Resource"] - if ok { - switch resourceField := resourceField.(type) { - case []interface{}: - s.Resource = make([]string, len(resourceField)) - for i, action := range resourceField { - if s.Resource[i], ok = action.(string); !ok { - return errInvalidStatement - } - } - case string: - s.Resource = []string{resourceField} - default: - return errInvalidStatement - } - } - - return nil -} - func (h *handler) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() reqInfo := middleware.GetReqInfo(ctx) @@ -263,21 +62,7 @@ func (h *handler) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) { return } - if bktInfo.APEEnabled || len(settings.CannedACL) != 0 { - if err = middleware.EncodeToResponse(w, h.encodeBucketCannedACL(ctx, bktInfo, settings)); err != nil { - h.logAndSendError(w, "something went wrong", reqInfo, err) - return - } - return - } - - bucketACL, err := h.obj.GetBucketACL(ctx, bktInfo) - if err != nil { - h.logAndSendError(w, "could not fetch bucket acl", reqInfo, err) - return - } - - if err = middleware.EncodeToResponse(w, h.encodeBucketACL(ctx, bktInfo.Name, bucketACL)); err != nil { + if err = middleware.EncodeToResponse(w, h.encodeBucketCannedACL(ctx, bktInfo, settings)); err != nil { h.logAndSendError(w, "something went wrong", reqInfo, err) return } @@ -328,15 +113,6 @@ func (h *handler) encodePrivateCannedACL(ctx context.Context, bktInfo *data.Buck return res } -func (h *handler) bearerTokenIssuerKey(ctx context.Context) (*keys.PublicKey, error) { - box, err := middleware.GetBoxData(ctx) - if err != nil { - return nil, err - } - - return getTokenIssuerKey(box) -} - func getTokenIssuerKey(box *accessbox.Box) (*keys.PublicKey, error) { if box.Gate.BearerToken == nil { return nil, stderrors.New("bearer token is missing") @@ -365,47 +141,7 @@ func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) { return } - if bktInfo.APEEnabled || len(settings.CannedACL) != 0 { - h.putBucketACLAPEHandler(w, r, reqInfo, bktInfo, settings) - return - } - - key, err := h.bearerTokenIssuerKey(r.Context()) - if err != nil { - h.logAndSendError(w, "couldn't get bearer token issuer key", reqInfo, err) - return - } - - token, err := getSessionTokenSetEACL(r.Context()) - if err != nil { - h.logAndSendError(w, "couldn't get eacl token", reqInfo, err) - return - } - - list := &AccessControlPolicy{} - if r.ContentLength == 0 { - list, err = parseACLHeaders(r.Header, key) - if err != nil { - h.logAndSendError(w, "could not parse bucket acl", reqInfo, err) - return - } - } else if err = h.cfg.NewXMLDecoder(r.Body).Decode(list); err != nil { - h.logAndSendError(w, "could not parse bucket acl", reqInfo, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrMalformedXML), err.Error())) - return - } - - resInfo := &resourceInfo{Bucket: reqInfo.BucketName} - astBucket, err := aclToAst(list, resInfo) - if err != nil { - h.logAndSendError(w, "could not translate acl to policy", reqInfo, err) - return - } - - if _, err = h.updateBucketACL(r, astBucket, bktInfo, token); err != nil { - h.logAndSendError(w, "could not update bucket acl", reqInfo, err) - return - } - w.WriteHeader(http.StatusOK) + h.putBucketACLAPEHandler(w, r, reqInfo, bktInfo, settings) } func (h *handler) putBucketACLAPEHandler(w http.ResponseWriter, r *http.Request, reqInfo *middleware.ReqInfo, bktInfo *data.BucketInfo, settings *data.BucketSettings) { @@ -456,44 +192,6 @@ func (h *handler) putBucketACLAPEHandler(w http.ResponseWriter, r *http.Request, w.WriteHeader(http.StatusOK) } -func (h *handler) updateBucketACL(r *http.Request, astChild *ast, bktInfo *data.BucketInfo, sessionToken *session.Container) (bool, error) { - bucketACL, err := h.obj.GetBucketACL(r.Context(), bktInfo) - if err != nil { - return false, fmt.Errorf("could not get bucket eacl: %w", err) - } - - parentAst := tableToAst(bucketACL.EACL, bktInfo.Name) - strCID := bucketACL.Info.CID.EncodeToString() - - for _, resource := range parentAst.Resources { - if resource.Bucket == strCID { - resource.Bucket = bktInfo.Name - } - } - - resAst, updated := mergeAst(parentAst, astChild) - if !updated { - return false, nil - } - - table, err := astToTable(resAst) - if err != nil { - return false, fmt.Errorf("could not translate ast to table: %w", err) - } - - p := &layer.PutBucketACLParams{ - BktInfo: bktInfo, - EACL: table, - SessionToken: sessionToken, - } - - if err = h.obj.PutBucketACL(r.Context(), p); err != nil { - return false, fmt.Errorf("could not put bucket acl: %w", err) - } - - return true, nil -} - func (h *handler) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() reqInfo := middleware.GetReqInfo(ctx) @@ -510,118 +208,22 @@ func (h *handler) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) { return } - if bktInfo.APEEnabled || len(settings.CannedACL) != 0 { - if err = middleware.EncodeToResponse(w, h.encodePrivateCannedACL(ctx, bktInfo, settings)); err != nil { - h.logAndSendError(w, "something went wrong", reqInfo, err) - return - } + if err = middleware.EncodeToResponse(w, h.encodePrivateCannedACL(ctx, bktInfo, settings)); err != nil { + h.logAndSendError(w, "something went wrong", reqInfo, err) return } - - bucketACL, err := h.obj.GetBucketACL(ctx, bktInfo) - if err != nil { - h.logAndSendError(w, "could not fetch bucket acl", reqInfo, err) - return - } - - prm := &layer.HeadObjectParams{ - BktInfo: bktInfo, - Object: reqInfo.ObjectName, - VersionID: reqInfo.URL.Query().Get(api.QueryVersionID), - } - - objInfo, err := h.obj.GetObjectInfo(ctx, prm) - if err != nil { - h.logAndSendError(w, "could not get object info", reqInfo, err) - return - } - - if err = middleware.EncodeToResponse(w, h.encodeObjectACL(ctx, bucketACL, reqInfo.BucketName, objInfo.VersionID())); err != nil { - h.logAndSendError(w, "failed to encode response", reqInfo, err) - } } func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) { ctx := r.Context() reqInfo := middleware.GetReqInfo(ctx) - bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName) - if err != nil { + if _, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName); err != nil { h.logAndSendError(w, "could not get bucket info", reqInfo, err) return } - apeEnabled := bktInfo.APEEnabled - - if !apeEnabled { - settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo) - if err != nil { - h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err) - return - } - apeEnabled = len(settings.CannedACL) != 0 - } - - if apeEnabled { - h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported)) - return - } - - versionID := reqInfo.URL.Query().Get(api.QueryVersionID) - key, err := h.bearerTokenIssuerKey(ctx) - if err != nil { - h.logAndSendError(w, "couldn't get gate key", reqInfo, err) - return - } - - token, err := getSessionTokenSetEACL(ctx) - if err != nil { - h.logAndSendError(w, "couldn't get eacl token", reqInfo, err) - return - } - - p := &layer.HeadObjectParams{ - BktInfo: bktInfo, - Object: reqInfo.ObjectName, - VersionID: versionID, - } - - objInfo, err := h.obj.GetObjectInfo(ctx, p) - if err != nil { - h.logAndSendError(w, "could not get object info", reqInfo, err) - return - } - - list := &AccessControlPolicy{} - if r.ContentLength == 0 { - list, err = parseACLHeaders(r.Header, key) - if err != nil { - h.logAndSendError(w, "could not parse bucket acl", reqInfo, err) - return - } - } else if err = h.cfg.NewXMLDecoder(r.Body).Decode(list); err != nil { - h.logAndSendError(w, "could not parse bucket acl", reqInfo, fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrMalformedXML), err.Error())) - return - } - - resInfo := &resourceInfo{ - Bucket: reqInfo.BucketName, - Object: reqInfo.ObjectName, - Version: objInfo.VersionID(), - } - - astObject, err := aclToAst(list, resInfo) - if err != nil { - h.logAndSendError(w, "could not translate acl to ast", reqInfo, err) - return - } - - if _, err = h.updateBucketACL(r, astObject, bktInfo, token); err != nil { - h.logAndSendError(w, "could not update bucket acl", reqInfo, err) - return - } - - w.WriteHeader(http.StatusOK) + h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported)) } func (h *handler) GetBucketPolicyStatusHandler(w http.ResponseWriter, r *http.Request) { @@ -814,977 +416,3 @@ func (h *handler) nativeResolver(ns string, bktInfo *data.BucketInfo) engineiam. func getBucketChainID(prefix chain.Name, bktInfo *data.BucketInfo) chain.ID { return chain.ID(string(prefix) + ":bkt" + string(bktInfo.CID[:])) } - -func parseACLHeaders(header http.Header, key *keys.PublicKey) (*AccessControlPolicy, error) { - var err error - acp := &AccessControlPolicy{Owner: Owner{ - ID: hex.EncodeToString(key.Bytes()), - DisplayName: key.Address(), - }} - acp.AccessControlList = []*Grant{{ - Grantee: &Grantee{ - ID: hex.EncodeToString(key.Bytes()), - DisplayName: key.Address(), - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }} - - cannedACL := header.Get(api.AmzACL) - if cannedACL != "" { - return addPredefinedACP(acp, cannedACL) - } - - if acp.AccessControlList, err = addGrantees(acp.AccessControlList, header, api.AmzGrantFullControl); err != nil { - return nil, fmt.Errorf("add grantees full control: %w", err) - } - if acp.AccessControlList, err = addGrantees(acp.AccessControlList, header, api.AmzGrantRead); err != nil { - return nil, fmt.Errorf("add grantees read: %w", err) - } - if acp.AccessControlList, err = addGrantees(acp.AccessControlList, header, api.AmzGrantWrite); err != nil { - return nil, fmt.Errorf("add grantees write: %w", err) - } - - return acp, nil -} - -func addGrantees(list []*Grant, headers http.Header, hdr string) ([]*Grant, error) { - grant := headers.Get(hdr) - if grant == "" { - return list, nil - } - - permission, err := grantHdrToPermission(hdr) - if err != nil { - return nil, fmt.Errorf("parse header: %w", err) - } - - grantees, err := parseGrantee(grant) - if err != nil { - return nil, fmt.Errorf("parse grantee: %w", err) - } - - for _, grantee := range grantees { - if grantee.Type == acpAmazonCustomerByEmail || (grantee.Type == acpGroup && grantee.URI != allUsersGroup) { - return nil, stderrors.New("unsupported grantee type") - } - - list = append(list, &Grant{ - Grantee: grantee, - Permission: permission, - }) - } - return list, nil -} - -func grantHdrToPermission(grant string) (AWSACL, error) { - switch grant { - case api.AmzGrantFullControl: - return aclFullControl, nil - case api.AmzGrantRead: - return aclRead, nil - case api.AmzGrantWrite: - return aclWrite, nil - } - return "", fmt.Errorf("unsuppoted header: %s", grant) -} - -func parseGrantee(grantees string) ([]*Grantee, error) { - var result []*Grantee - - split := strings.Split(grantees, ", ") - for _, pair := range split { - split2 := strings.Split(pair, "=") - if len(split2) != 2 { - return nil, errors.GetAPIError(errors.ErrInvalidArgument) - } - - grantee, err := formGrantee(split2[0], split2[1]) - if err != nil { - return nil, fmt.Errorf("form grantee: %w", err) - } - result = append(result, grantee) - } - - return result, nil -} - -func formGrantee(granteeType, value string) (*Grantee, error) { - value = data.UnQuote(value) - switch granteeType { - case "id": - return &Grantee{ - ID: value, - Type: acpCanonicalUser, - }, nil - case "uri": - return &Grantee{ - URI: value, - Type: acpGroup, - }, nil - case "emailAddress": - return &Grantee{ - EmailAddress: value, - Type: acpAmazonCustomerByEmail, - }, nil - } - // do not return grantee type to avoid sensitive data logging (#489) - return nil, fmt.Errorf("unknown grantee type") -} - -func addPredefinedACP(acp *AccessControlPolicy, cannedACL string) (*AccessControlPolicy, error) { - switch cannedACL { - case basicACLPrivate: - case basicACLPublic: - acp.AccessControlList = append(acp.AccessControlList, &Grant{ - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclFullControl, - }) - case cannedACLAuthRead: - fallthrough - case basicACLReadOnly: - acp.AccessControlList = append(acp.AccessControlList, &Grant{ - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }) - default: - return nil, errors.GetAPIError(errors.ErrInvalidArgument) - } - - return acp, nil -} - -func tableToAst(table *eacl.Table, bktName string) *ast { - resourceMap := make(map[string]orderedAstResource) - - var groupRecordsLeft int - var currentResource orderedAstResource - for i, record := range table.Records() { - if serviceRec := tryServiceRecord(record); serviceRec != nil { - resInfo := resourceInfoFromName(serviceRec.Resource, bktName) - groupRecordsLeft = serviceRec.GroupRecordsLength - - currentResource = getResourceOrCreate(resourceMap, i, resInfo) - resourceMap[resInfo.Name()] = currentResource - } else if groupRecordsLeft != 0 { - groupRecordsLeft-- - addOperationsAndUpdateMap(currentResource, record, resourceMap) - } else { - resInfo := resInfoFromFilters(bktName, record.Filters()) - resource := getResourceOrCreate(resourceMap, i, resInfo) - addOperationsAndUpdateMap(resource, record, resourceMap) - } - } - - return &ast{ - Resources: formReverseOrderResources(resourceMap), - } -} - -func formReverseOrderResources(resourceMap map[string]orderedAstResource) []*astResource { - orderedResources := make([]orderedAstResource, 0, len(resourceMap)) - for _, resource := range resourceMap { - orderedResources = append(orderedResources, resource) - } - sort.Slice(orderedResources, func(i, j int) bool { - return orderedResources[i].Index >= orderedResources[j].Index // reverse order - }) - - result := make([]*astResource, len(orderedResources)) - for i, ordered := range orderedResources { - res := ordered.Resource - for j, k := 0, len(res.Operations)-1; j < k; j, k = j+1, k-1 { - res.Operations[j], res.Operations[k] = res.Operations[k], res.Operations[j] - } - - result[i] = res - } - - return result -} - -func addOperationsAndUpdateMap(orderedRes orderedAstResource, record eacl.Record, resMap map[string]orderedAstResource) { - for _, target := range record.Targets() { - orderedRes.Resource.Operations = addToList(orderedRes.Resource.Operations, record, target) - } - resMap[orderedRes.Resource.Name()] = orderedRes -} - -func getResourceOrCreate(resMap map[string]orderedAstResource, index int, resInfo resourceInfo) orderedAstResource { - resource, ok := resMap[resInfo.Name()] - if !ok { - resource = orderedAstResource{ - Index: index, - Resource: &astResource{resourceInfo: resInfo}, - } - } - return resource -} - -func resInfoFromFilters(bucketName string, filters []eacl.Filter) resourceInfo { - resInfo := resourceInfo{Bucket: bucketName} - for _, filter := range filters { - if filter.Matcher() == eacl.MatchStringEqual { - if filter.Key() == object.AttributeFilePath { - resInfo.Object = filter.Value() - } else if filter.Key() == v2acl.FilterObjectID { - resInfo.Version = filter.Value() - } - } - } - - return resInfo -} - -func mergeAst(parent, child *ast) (*ast, bool) { - updated := false - for _, resource := range child.Resources { - parentResource := getParentResource(parent, resource) - if parentResource == nil { - parent.Resources = append(parent.Resources, resource) - updated = true - continue - } - - var newOps []*astOperation - for _, astOp := range resource.Operations { - // get parent matched operations - ops := getAstOps(parentResource, astOp) - switch len(ops) { - case 2: // parent contains different actions for the same child operation - // potential inconsistency - if groupGrantee := astOp.IsGroupGrantee(); groupGrantee { - // it is not likely (such state must be detected early) - // inconsistency - action := eacl.ActionAllow - if astOp.Action == eacl.ActionAllow { - action = eacl.ActionDeny - } - removeAstOp(parentResource, groupGrantee, astOp.Op, action) - updated = true - continue - } - - opToAdd, opToDelete := ops[0], ops[1] - if ops[1].Action == astOp.Action { - opToAdd, opToDelete = ops[1], ops[0] - } - - if handleAddOperations(parentResource, astOp, opToAdd) { - updated = true - } - if handleRemoveOperations(parentResource, astOp, opToDelete) { - updated = true - } - case 1: // parent contains some action for the same child operation - if astOp.Action != ops[0].Action { - // potential inconsistency - if groupGrantee := astOp.IsGroupGrantee(); groupGrantee { - // inconsistency - ops[0].Action = astOp.Action - updated = true - continue - } - - if handleRemoveOperations(parentResource, astOp, ops[0]) { - updated = true - } - parentResource.Operations = append(parentResource.Operations, astOp) - continue - } - - if handleAddOperations(parentResource, astOp, ops[0]) { - updated = true - } - case 0: // parent doesn't contain actions for the same child operation - newOps = append(newOps, astOp) - updated = true - } - } - - if newOps != nil { - parentResource.Operations = append(newOps, parentResource.Operations...) - } - } - - return parent, updated -} - -func handleAddOperations(parentResource *astResource, astOp, existedOp *astOperation) bool { - var needToAdd []string - for _, user := range astOp.Users { - if !containsStr(existedOp.Users, user) { - needToAdd = append(needToAdd, user) - } - } - if len(needToAdd) != 0 { - addUsers(parentResource, existedOp, needToAdd) - return true - } - return false -} - -func handleRemoveOperations(parentResource *astResource, astOp, existedOp *astOperation) bool { - var needToRemove []string - for _, user := range astOp.Users { - if containsStr(existedOp.Users, user) { - needToRemove = append(needToRemove, user) - } - } - if len(needToRemove) != 0 { - removeUsers(parentResource, existedOp, needToRemove) - return true - } - - return false -} - -func containsStr(list []string, element string) bool { - for _, str := range list { - if str == element { - return true - } - } - return false -} - -func getAstOps(resource *astResource, childOp *astOperation) []*astOperation { - var res []*astOperation - for _, astOp := range resource.Operations { - if astOp.IsGroupGrantee() == childOp.IsGroupGrantee() && astOp.Op == childOp.Op { - res = append(res, astOp) - } - } - return res -} - -func removeAstOp(resource *astResource, group bool, op eacl.Operation, action eacl.Action) { - for i, astOp := range resource.Operations { - if astOp.IsGroupGrantee() == group && astOp.Op == op && astOp.Action == action { - resource.Operations = append(resource.Operations[:i], resource.Operations[i+1:]...) - return - } - } -} - -func addUsers(resource *astResource, astO *astOperation, users []string) { - for _, astOp := range resource.Operations { - if astOp.IsGroupGrantee() == astO.IsGroupGrantee() && astOp.Op == astO.Op && astOp.Action == astO.Action { - astOp.Users = append(astO.Users, users...) - return - } - } -} - -func removeUsers(resource *astResource, astOperation *astOperation, users []string) { - for ind, astOp := range resource.Operations { - if !astOp.IsGroupGrantee() && astOp.Op == astOperation.Op && astOp.Action == astOperation.Action { - filteredUsers := astOp.Users[:0] // new slice without allocation - for _, user := range astOp.Users { - if !containsStr(users, user) { - filteredUsers = append(filteredUsers, user) - } - } - if len(filteredUsers) == 0 { // remove ast resource - resource.Operations = append(resource.Operations[:ind], resource.Operations[ind+1:]...) - } else { - astOp.Users = filteredUsers - } - return - } - } -} - -func getParentResource(parent *ast, resource *astResource) *astResource { - for _, parentResource := range parent.Resources { - if resource.Bucket == parentResource.Bucket && resource.Object == parentResource.Object && - resource.Version == parentResource.Version { - return parentResource - } - } - return nil -} - -func astToTable(ast *ast) (*eacl.Table, error) { - table := eacl.NewTable() - - for i := len(ast.Resources) - 1; i >= 0; i-- { - records, err := formRecords(ast.Resources[i]) - if err != nil { - return nil, fmt.Errorf("form records: %w", err) - } - - serviceRecord := ServiceRecord{ - Resource: ast.Resources[i].Name(), - GroupRecordsLength: len(records), - } - table.AddRecord(serviceRecord.ToEACLRecord()) - - for _, rec := range records { - table.AddRecord(rec) - } - } - - return table, nil -} - -func tryServiceRecord(record eacl.Record) *ServiceRecord { - if record.Action() != eacl.ActionAllow || record.Operation() != eacl.OperationGet || - len(record.Targets()) != 1 || len(record.Filters()) != 2 { - return nil - } - - target := record.Targets()[0] - if target.Role() != eacl.RoleSystem { - return nil - } - - resourceFilter := record.Filters()[0] - recordsFilter := record.Filters()[1] - if resourceFilter.From() != eacl.HeaderFromService || recordsFilter.From() != eacl.HeaderFromService || - resourceFilter.Matcher() != eacl.MatchUnknown || recordsFilter.Matcher() != eacl.MatchUnknown || - resourceFilter.Key() != serviceRecordResourceKey || recordsFilter.Key() != serviceRecordGroupLengthKey { - return nil - } - - groupLength, err := strconv.Atoi(recordsFilter.Value()) - if err != nil { - return nil - } - - return &ServiceRecord{ - Resource: resourceFilter.Value(), - GroupRecordsLength: groupLength, - } -} - -func formRecords(resource *astResource) ([]*eacl.Record, error) { - var res []*eacl.Record - - for i := len(resource.Operations) - 1; i >= 0; i-- { - astOp := resource.Operations[i] - record := eacl.NewRecord() - record.SetOperation(astOp.Op) - record.SetAction(astOp.Action) - if astOp.IsGroupGrantee() { - eacl.AddFormedTarget(record, eacl.RoleOthers) - } else { - targetKeys := make([]ecdsa.PublicKey, 0, len(astOp.Users)) - for _, user := range astOp.Users { - pk, err := keys.NewPublicKeyFromString(user) - if err != nil { - return nil, fmt.Errorf("public key from string: %w", err) - } - targetKeys = append(targetKeys, (ecdsa.PublicKey)(*pk)) - } - // Unknown role is used, because it is ignored when keys are set - eacl.AddFormedTarget(record, eacl.RoleUnknown, targetKeys...) - } - if len(resource.Object) != 0 { - if len(resource.Version) != 0 { - var id oid.ID - if err := id.DecodeString(resource.Version); err != nil { - return nil, fmt.Errorf("parse object version (oid): %w", err) - } - record.AddObjectIDFilter(eacl.MatchStringEqual, id) - } else { - record.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, resource.Object) - } - } - res = append(res, record) - } - - return res, nil -} - -func addToList(operations []*astOperation, rec eacl.Record, target eacl.Target) []*astOperation { - var ( - found *astOperation - groupTarget = target.Role() == eacl.RoleOthers - ) - - for _, astOp := range operations { - if astOp.Op == rec.Operation() && astOp.IsGroupGrantee() == groupTarget { - found = astOp - } - } - - if found != nil { - if !groupTarget { - for _, key := range target.BinaryKeys() { - found.Users = append(found.Users, hex.EncodeToString(key)) - } - } - } else { - astOperation := &astOperation{ - Op: rec.Operation(), - Action: rec.Action(), - } - if !groupTarget { - for _, key := range target.BinaryKeys() { - astOperation.Users = append(astOperation.Users, hex.EncodeToString(key)) - } - } - - operations = append(operations, astOperation) - } - - return operations -} - -func policyToAst(bktPolicy *bucketPolicy) (*ast, error) { - res := &ast{} - - rr := make(map[string]*astResource) - - for _, state := range bktPolicy.Statement { - if state.Principal.AWS != "" && state.Principal.AWS != allUsersWildcard || - state.Principal.AWS == "" && state.Principal.CanonicalUser == "" { - return nil, fmt.Errorf("unsupported principal: %v", state.Principal) - } - var groupGrantee bool - if state.Principal.AWS == allUsersWildcard { - groupGrantee = true - } - - for _, resource := range state.Resource { - trimmedResource := strings.TrimPrefix(resource, arnAwsPrefix) - r, ok := rr[trimmedResource] - if !ok { - if !strings.HasPrefix(trimmedResource, bktPolicy.Bucket) { - return nil, fmt.Errorf("resource '%s' must be in the same bucket '%s'", trimmedResource, bktPolicy.Bucket) - } - - r = &astResource{ - resourceInfo: resourceInfoFromName(trimmedResource, bktPolicy.Bucket), - } - } - for _, action := range state.Action { - for _, op := range actionToOpMap[action] { - toAction := effectToAction(state.Effect) - r.Operations = addTo(r.Operations, state.Principal.CanonicalUser, op, groupGrantee, toAction) - } - } - - rr[trimmedResource] = r - } - } - - for _, val := range rr { - res.Resources = append(res.Resources, val) - } - - return res, nil -} - -func resourceInfoFromName(name, bucketName string) resourceInfo { - resInfo := resourceInfo{Bucket: bucketName} - if name != bucketName { - versionedObject := strings.TrimPrefix(name, bucketName+"/") - objVersion := strings.Split(versionedObject, ":") - if len(objVersion) <= 2 { - resInfo.Object = objVersion[0] - if len(objVersion) == 2 { - resInfo.Version = objVersion[1] - } - } else { - resInfo.Object = strings.Join(objVersion[:len(objVersion)-1], ":") - resInfo.Version = objVersion[len(objVersion)-1] - } - } - - return resInfo -} - -func addTo(list []*astOperation, userID string, op eacl.Operation, groupGrantee bool, action eacl.Action) []*astOperation { - var found *astOperation - for _, astop := range list { - if astop.Op == op && astop.IsGroupGrantee() == groupGrantee { - found = astop - } - } - - if found != nil { - if !groupGrantee { - found.Users = append(found.Users, userID) - } - } else { - astoperation := &astOperation{ - Op: op, - Action: action, - } - if !groupGrantee { - astoperation.Users = append(astoperation.Users, userID) - } - - list = append(list, astoperation) - } - - return list -} - -func aclToAst(acl *AccessControlPolicy, resInfo *resourceInfo) (*ast, error) { - res := &ast{} - - resource := &astResource{resourceInfo: *resInfo} - - ops := readOps - if resInfo.IsBucket() { - ops = append(ops, writeOps...) - } - - // Expect to have at least 1 full control grant for owner which is set in - // parseACLHeaders(). If there is no other grants, then user sets private - // canned ACL, which is processed in this branch. - if len(acl.AccessControlList) < 2 { - for _, op := range ops { - operation := &astOperation{ - Op: op, - Action: eacl.ActionDeny, - } - resource.Operations = append(resource.Operations, operation) - } - } - - for _, op := range ops { - operation := &astOperation{ - Users: []string{acl.Owner.ID}, - 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, stderrors.New("unsupported grantee type") - } - - var groupGrantee bool - if grant.Grantee.Type == acpGroup { - groupGrantee = true - } 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, groupGrantee, 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{ - getAllowStatement(resInfo, acl.Owner.ID, aclFullControl), - } - - // Expect to have at least 1 full control grant for owner which is set in - // parseACLHeaders(). If there is no other grants, then user sets private - // canned ACL, which is processed in this branch. - if len(acl.AccessControlList) < 2 { - results = append([]statement{getDenyStatement(resInfo, allUsersWildcard, aclFullControl)}, results...) - } - - for _, grant := range acl.AccessControlList { - if grant.Grantee.Type == acpAmazonCustomerByEmail || (grant.Grantee.Type == acpGroup && grant.Grantee.URI != allUsersGroup) { - return nil, stderrors.New("unsupported grantee type") - } - - user := grant.Grantee.ID - if grant.Grantee.Type == acpGroup { - user = allUsersWildcard - } else if user == acl.Owner.ID { - continue - } - results = append(results, getAllowStatement(resInfo, user, grant.Permission)) - } - - return &bucketPolicy{ - Statement: results, - Bucket: resInfo.Bucket, - }, nil -} - -func getAllowStatement(resInfo *resourceInfo, id string, permission AWSACL) statement { - state := statement{ - Effect: "Allow", - Principal: principal{ - CanonicalUser: id, - }, - Action: getActions(permission, resInfo.IsBucket()), - Resource: []string{arnAwsPrefix + resInfo.Name()}, - } - - if id == allUsersWildcard { - state.Principal = principal{AWS: allUsersWildcard} - } - - return state -} - -func getDenyStatement(resInfo *resourceInfo, id string, permission AWSACL) statement { - state := statement{ - Effect: "Deny", - Principal: principal{ - CanonicalUser: id, - }, - Action: getActions(permission, resInfo.IsBucket()), - Resource: []string{arnAwsPrefix + resInfo.Name()}, - } - - if id == allUsersWildcard { - state.Principal = principal{AWS: allUsersWildcard} - } - - return state -} - -func getActions(permission AWSACL, isBucket bool) []string { - var res []string - switch permission { - case aclRead: - if isBucket { - res = []string{s3ListBucket, s3ListBucketVersions, s3ListBucketMultipartUploads} - } else { - res = []string{s3GetObject, s3GetObjectVersion} - } - case aclWrite: - if isBucket { - res = []string{s3PutObject, s3DeleteObject} - } - case aclFullControl: - if isBucket { - res = []string{s3ListBucket, s3ListBucketVersions, s3ListBucketMultipartUploads, s3PutObject, s3DeleteObject} - } else { - res = []string{s3GetObject, s3GetObjectVersion} - } - } - - return res -} - -func effectToAction(effect string) eacl.Action { - switch effect { - case "Allow": - return eacl.ActionAllow - case "Deny": - return eacl.ActionDeny - } - return eacl.ActionUnknown -} - -func permissionToOperations(permission AWSACL) []eacl.Operation { - switch permission { - case aclFullControl: - return fullOps - case aclRead: - return readOps - case aclWrite: - return writeOps - } - return nil -} - -func isWriteOperation(op eacl.Operation) bool { - return op == eacl.OperationDelete || op == eacl.OperationPut -} - -type access struct { - recipient string - operations []eacl.Operation -} - -type accessList struct { - list []access -} - -func (c *accessList) addAccess(recipient string, operation eacl.Operation) { - for i, v := range c.list { - if v.recipient == recipient { - c.list[i].operations = append(c.list[i].operations, operation) - return - } - } - - c.list = append(c.list, access{recipient, []eacl.Operation{operation}}) -} - -func (h *handler) encodeObjectACL(ctx context.Context, bucketACL *layer.BucketACL, bucketName, objectVersion string) *AccessControlPolicy { - res := &AccessControlPolicy{ - Owner: Owner{ - ID: bucketACL.Info.Owner.String(), - DisplayName: bucketACL.Info.Owner.String(), - }, - } - - m := &accessList{} - - astList := tableToAst(bucketACL.EACL, bucketName) - - for _, resource := range astList.Resources { - if resource.Version != objectVersion { - continue - } - - for _, op := range resource.Operations { - if op.Action != eacl.ActionAllow { - continue - } - - if len(op.Users) == 0 { - m.addAccess(allUsersGroup, op.Op) - } else { - for _, user := range op.Users { - m.addAccess(user, op.Op) - } - } - } - } - - for _, val := range m.list { - permission := aclFullControl - read := true - for op := eacl.OperationGet; op <= eacl.OperationRangeHash; op++ { - if !contains(val.operations, op) && !isWriteOperation(op) { - read = false - } - } - - if read { - permission = aclFullControl - } else { - h.reqLogger(ctx).Warn(logs.SomeACLNotFullyMapped) - } - - var grantee *Grantee - if val.recipient == allUsersGroup { - grantee = NewGrantee(acpGroup) - grantee.URI = allUsersGroup - } else { - grantee = NewGrantee(acpCanonicalUser) - grantee.ID = val.recipient - } - - grant := &Grant{ - Grantee: grantee, - Permission: permission, - } - res.AccessControlList = append(res.AccessControlList, grant) - } - - return res -} - -func (h *handler) encodeBucketACL(ctx context.Context, bucketName string, bucketACL *layer.BucketACL) *AccessControlPolicy { - return h.encodeObjectACL(ctx, bucketACL, bucketName, "") -} - -func contains(list []eacl.Operation, op eacl.Operation) bool { - for _, operation := range list { - if operation == op { - return true - } - } - return false -} - -type getRecordFunc func(op eacl.Operation) *eacl.Record - -func bucketACLToTable(acp *AccessControlPolicy, resInfo *resourceInfo) (*eacl.Table, error) { - if !resInfo.IsBucket() { - return nil, fmt.Errorf("allowed only bucket acl") - } - - var found bool - table := eacl.NewTable() - - ownerKey, err := keys.NewPublicKeyFromString(acp.Owner.ID) - if err != nil { - return nil, fmt.Errorf("public key from string: %w", err) - } - - for _, grant := range acp.AccessControlList { - if !isValidGrant(grant) { - return nil, stderrors.New("unsupported grantee") - } - if grant.Grantee.ID == acp.Owner.ID { - found = true - } - - getRecord, err := getRecordFunction(grant.Grantee) - if err != nil { - return nil, fmt.Errorf("record func from grantee: %w", err) - } - for _, op := range permissionToOperations(grant.Permission) { - table.AddRecord(getRecord(op)) - } - } - - if !found { - for _, op := range fullOps { - table.AddRecord(getAllowRecord(op, ownerKey)) - } - } - - for _, op := range fullOps { - table.AddRecord(getOthersRecord(op, eacl.ActionDeny)) - } - - return table, nil -} - -func getRecordFunction(grantee *Grantee) (getRecordFunc, error) { - switch grantee.Type { - case acpAmazonCustomerByEmail: - case acpCanonicalUser: - pk, err := keys.NewPublicKeyFromString(grantee.ID) - if err != nil { - return nil, fmt.Errorf("couldn't parse canonical ID %s: %w", grantee.ID, err) - } - return func(op eacl.Operation) *eacl.Record { - return getAllowRecord(op, pk) - }, nil - case acpGroup: - return func(op eacl.Operation) *eacl.Record { - return getOthersRecord(op, eacl.ActionAllow) - }, nil - } - return nil, fmt.Errorf("unknown type: %s", grantee.Type) -} - -func isValidGrant(grant *Grant) bool { - return (grant.Permission == aclFullControl || grant.Permission == aclRead || grant.Permission == aclWrite) && - (grant.Grantee.Type == acpCanonicalUser || (grant.Grantee.Type == acpGroup && grant.Grantee.URI == allUsersGroup)) -} - -func getAllowRecord(op eacl.Operation, pk *keys.PublicKey) *eacl.Record { - record := eacl.NewRecord() - record.SetOperation(op) - record.SetAction(eacl.ActionAllow) - // Unknown role is used, because it is ignored when keys are set - eacl.AddFormedTarget(record, eacl.RoleUnknown, (ecdsa.PublicKey)(*pk)) - return record -} - -func getOthersRecord(op eacl.Operation, action eacl.Action) *eacl.Record { - record := eacl.NewRecord() - record.SetOperation(op) - record.SetAction(action) - eacl.AddFormedTarget(record, eacl.RoleOthers) - return record -} diff --git a/api/handler/acl_test.go b/api/handler/acl_test.go index 1081e8f..fb1c46b 100644 --- a/api/handler/acl_test.go +++ b/api/handler/acl_test.go @@ -2,14 +2,9 @@ package handler import ( "bytes" - "crypto/ecdsa" - "crypto/rand" - "crypto/sha256" "encoding/hex" "encoding/json" "encoding/xml" - "fmt" - "io" "net/http" "net/http/httptest" "testing" @@ -17,13 +12,9 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" - "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" - oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" engineiam "git.frostfs.info/TrueCloudLab/policy-engine/iam" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" @@ -31,1290 +22,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestTableToAst(t *testing.T) { - b := make([]byte, 32) - _, err := io.ReadFull(rand.Reader, b) - require.NoError(t, err) - var id oid.ID - id.SetSHA256(sha256.Sum256(b)) - - key, err := keys.NewPrivateKey() - require.NoError(t, err) - key2, err := keys.NewPrivateKey() - require.NoError(t, err) - - table := new(eacl.Table) - record := eacl.NewRecord() - record.SetAction(eacl.ActionAllow) - record.SetOperation(eacl.OperationGet) - eacl.AddFormedTarget(record, eacl.RoleOthers) - table.AddRecord(record) - record2 := eacl.NewRecord() - record2.SetAction(eacl.ActionDeny) - record2.SetOperation(eacl.OperationPut) - // Unknown role is used, because it is ignored when keys are set - eacl.AddFormedTarget(record2, eacl.RoleUnknown, *(*ecdsa.PublicKey)(key.PublicKey()), *((*ecdsa.PublicKey)(key2.PublicKey()))) - record2.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, "objectName") - record2.AddObjectIDFilter(eacl.MatchStringEqual, id) - table.AddRecord(record2) - - expectedAst := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{Bucket: "bucketName"}, - Operations: []*astOperation{{ - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }}}, - { - resourceInfo: resourceInfo{ - Bucket: "bucketName", - Object: "objectName", - Version: id.EncodeToString(), - }, - Operations: []*astOperation{{ - Users: []string{ - hex.EncodeToString(key.PublicKey().Bytes()), - hex.EncodeToString(key2.PublicKey().Bytes()), - }, - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }}}, - }, - } - - actualAst := tableToAst(table, expectedAst.Resources[0].Bucket) - - if actualAst.Resources[0].Name() == expectedAst.Resources[0].Name() { - require.Equal(t, expectedAst, actualAst) - } else { - require.Equal(t, len(expectedAst.Resources), len(actualAst.Resources)) - require.Equal(t, expectedAst.Resources[0], actualAst.Resources[1]) - require.Equal(t, expectedAst.Resources[1], actualAst.Resources[0]) - } -} - -func TestPolicyToAst(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - - policy := &bucketPolicy{ - Statement: []statement{ - { - Effect: "Allow", - Principal: principal{AWS: allUsersWildcard}, - Action: []string{"s3:PutObject"}, - Resource: []string{"arn:aws:s3:::bucketName"}, - }, - { - Effect: "Deny", - Principal: principal{ - CanonicalUser: hex.EncodeToString(key.PublicKey().Bytes()), - }, - Action: []string{"s3:GetObject"}, - Resource: []string{"arn:aws:s3:::bucketName/object"}, - }}, - } - policy.Bucket = "bucketName" - - expectedAst := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucketName", - }, - Operations: []*astOperation{{ - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }}, - }, - { - resourceInfo: resourceInfo{ - Bucket: "bucketName", - Object: "object", - }, - Operations: getReadOps(key, false, eacl.ActionDeny), - }, - }, - } - - actualAst, err := policyToAst(policy) - require.NoError(t, err) - - if actualAst.Resources[0].Name() == expectedAst.Resources[0].Name() { - require.Equal(t, expectedAst, actualAst) - } else { - require.Equal(t, len(expectedAst.Resources), len(actualAst.Resources)) - require.Equal(t, expectedAst.Resources[0], actualAst.Resources[1]) - require.Equal(t, expectedAst.Resources[1], actualAst.Resources[0]) - } -} - -func getReadOps(key *keys.PrivateKey, groupGrantee bool, action eacl.Action) []*astOperation { - var ( - result []*astOperation - users []string - ) - if !groupGrantee { - users = append(users, hex.EncodeToString(key.PublicKey().Bytes())) - } - - for _, op := range readOps { - result = append(result, &astOperation{ - Users: users, - Op: op, - Action: action, - }) - } - - return result -} - -func TestMergeAstUnModified(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - - child := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{{ - Users: []string{hex.EncodeToString(key.PublicKey().Bytes())}, - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }}, - }, - }, - } - - parent := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - }, - Operations: []*astOperation{{ - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }}, - }, - child.Resources[0], - }, - } - - result, updated := mergeAst(parent, child) - require.False(t, updated) - require.Equal(t, parent, result) -} - -func TestMergeAstModified(t *testing.T) { - child := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{{ - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, { - Users: []string{"user2"}, - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }}, - }, - }, - } - - parent := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{{ - Users: []string{"user1"}, - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }}, - }, - }, - } - - expected := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{ - child.Resources[0].Operations[0], - { - Users: []string{"user1", "user2"}, - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }, - }, - }, - }, - } - - actual, updated := mergeAst(parent, child) - require.True(t, updated) - require.Equal(t, expected, actual) -} - -func TestMergeAppended(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - users := []string{hex.EncodeToString(key.PublicKey().Bytes())} - - parent := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - }, - Operations: []*astOperation{ - { - Users: users, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationDelete, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }, - { - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, - { - Op: eacl.OperationDelete, - Action: eacl.ActionDeny, - }, - }, - }, - }, - } - - child := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{ - { - Users: users, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationDelete, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationDelete, - Action: eacl.ActionAllow, - }, - }, - }, - }, - } - - expected := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - }, - Operations: []*astOperation{ - { - Users: users, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationDelete, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }, - { - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, - { - Op: eacl.OperationDelete, - Action: eacl.ActionDeny, - }, - }, - }, - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{ - { - Users: users, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Users: users, - Op: eacl.OperationDelete, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationDelete, - Action: eacl.ActionAllow, - }, - }, - }, - }, - } - actual, updated := mergeAst(parent, child) - require.True(t, updated) - require.Equal(t, expected, actual) -} - -func TestOrder(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - users := []string{hex.EncodeToString(key.PublicKey().Bytes())} - targetUser := eacl.NewTarget() - targetUser.SetBinaryKeys([][]byte{key.PublicKey().Bytes()}) - targetOther := eacl.NewTarget() - targetOther.SetRole(eacl.RoleOthers) - bucketName := "bucket" - objectName := "objectName" - - expectedAst := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: bucketName, - }, - Operations: []*astOperation{ - { - Users: users, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }, - }, - }, - { - resourceInfo: resourceInfo{ - Bucket: bucketName, - Object: objectName, - }, - Operations: []*astOperation{ - { - Users: users, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, - }, - }, - }, - } - bucketServiceRec := &ServiceRecord{Resource: expectedAst.Resources[0].Name(), GroupRecordsLength: 2} - bucketUsersGetRec := eacl.NewRecord() - bucketUsersGetRec.SetOperation(eacl.OperationGet) - bucketUsersGetRec.SetAction(eacl.ActionAllow) - bucketUsersGetRec.SetTargets(*targetUser) - bucketOtherGetRec := eacl.NewRecord() - bucketOtherGetRec.SetOperation(eacl.OperationGet) - bucketOtherGetRec.SetAction(eacl.ActionDeny) - bucketOtherGetRec.SetTargets(*targetOther) - objectServiceRec := &ServiceRecord{Resource: expectedAst.Resources[1].Name(), GroupRecordsLength: 2} - objectUsersPutRec := eacl.NewRecord() - objectUsersPutRec.SetOperation(eacl.OperationPut) - objectUsersPutRec.SetAction(eacl.ActionAllow) - objectUsersPutRec.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, objectName) - objectUsersPutRec.SetTargets(*targetUser) - objectOtherPutRec := eacl.NewRecord() - objectOtherPutRec.SetOperation(eacl.OperationPut) - objectOtherPutRec.SetAction(eacl.ActionDeny) - objectOtherPutRec.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, objectName) - objectOtherPutRec.SetTargets(*targetOther) - - expectedEacl := eacl.NewTable() - expectedEacl.AddRecord(objectServiceRec.ToEACLRecord()) - expectedEacl.AddRecord(objectOtherPutRec) - expectedEacl.AddRecord(objectUsersPutRec) - expectedEacl.AddRecord(bucketServiceRec.ToEACLRecord()) - expectedEacl.AddRecord(bucketOtherGetRec) - expectedEacl.AddRecord(bucketUsersGetRec) - - t.Run("astToTable order and vice versa", func(t *testing.T) { - actualEacl, err := astToTable(expectedAst) - require.NoError(t, err) - require.Equal(t, expectedEacl, actualEacl) - - actualAst := tableToAst(actualEacl, bucketName) - require.Equal(t, expectedAst, actualAst) - }) - - t.Run("tableToAst order and vice versa", func(t *testing.T) { - actualAst := tableToAst(expectedEacl, bucketName) - require.Equal(t, expectedAst, actualAst) - - actualEacl, err := astToTable(actualAst) - require.NoError(t, err) - require.Equal(t, expectedEacl, actualEacl) - }) - - t.Run("append a resource", func(t *testing.T) { - childName := "child" - child := &ast{Resources: []*astResource{{ - resourceInfo: resourceInfo{ - Bucket: bucketName, - Object: childName, - }, - Operations: []*astOperation{{Op: eacl.OperationDelete, Action: eacl.ActionDeny}}}}, - } - - childRecord := eacl.NewRecord() - childRecord.SetOperation(eacl.OperationDelete) - childRecord.SetAction(eacl.ActionDeny) - childRecord.SetTargets(*targetOther) - childRecord.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, childName) - - mergedAst, updated := mergeAst(expectedAst, child) - require.True(t, updated) - - mergedEacl, err := astToTable(mergedAst) - require.NoError(t, err) - - require.Equal(t, *childRecord, mergedEacl.Records()[1]) - }) -} - -func TestMergeAstModifiedConflict(t *testing.T) { - child := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{{ - Users: []string{"user1"}, - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, { - Users: []string{"user3"}, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }}, - }, - }, - } - - parent := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{{ - Users: []string{"user1"}, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, { - Users: []string{"user2"}, - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, { - Users: []string{"user3"}, - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }}, - }, - }, - } - - expected := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucket", - Object: "objectName", - }, - Operations: []*astOperation{ - { - Users: []string{"user2", "user1"}, - Op: eacl.OperationPut, - Action: eacl.ActionDeny, - }, { - Users: []string{"user3"}, - Op: eacl.OperationGet, - Action: eacl.ActionAllow, - }, - }, - }, - }, - } - - actual, updated := mergeAst(parent, child) - require.True(t, updated) - require.Equal(t, expected, actual) -} - -func TestAstToTable(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - - ast := &ast{ - Resources: []*astResource{ - { - resourceInfo: resourceInfo{ - Bucket: "bucketName", - }, - Operations: []*astOperation{{ - Users: []string{hex.EncodeToString(key.PublicKey().Bytes())}, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }}, - }, - { - resourceInfo: resourceInfo{ - Bucket: "bucketName", - Object: "objectName", - }, - Operations: []*astOperation{{ - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }}, - }, - }, - } - - expectedTable := eacl.NewTable() - serviceRec1 := &ServiceRecord{Resource: ast.Resources[0].Name(), GroupRecordsLength: 1} - record1 := eacl.NewRecord() - record1.SetAction(eacl.ActionAllow) - record1.SetOperation(eacl.OperationPut) - // Unknown role is used, because it is ignored when keys are set - eacl.AddFormedTarget(record1, eacl.RoleUnknown, *(*ecdsa.PublicKey)(key.PublicKey())) - - serviceRec2 := &ServiceRecord{Resource: ast.Resources[1].Name(), GroupRecordsLength: 1} - record2 := eacl.NewRecord() - record2.SetAction(eacl.ActionDeny) - record2.SetOperation(eacl.OperationGet) - eacl.AddFormedTarget(record2, eacl.RoleOthers) - record2.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, "objectName") - - expectedTable.AddRecord(serviceRec2.ToEACLRecord()) - expectedTable.AddRecord(record2) - expectedTable.AddRecord(serviceRec1.ToEACLRecord()) - expectedTable.AddRecord(record1) - - actualTable, err := astToTable(ast) - require.NoError(t, err) - require.Equal(t, expectedTable, actualTable) -} - -func TestRemoveUsers(t *testing.T) { - resource := &astResource{ - resourceInfo: resourceInfo{ - Bucket: "bucket", - }, - Operations: []*astOperation{{ - Users: []string{"user1", "user3", "user4"}, - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - }, - { - Users: []string{"user5"}, - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - }, - }, - } - - op1 := &astOperation{ - Op: eacl.OperationPut, - Action: eacl.ActionAllow, - } - op2 := &astOperation{ - Op: eacl.OperationGet, - Action: eacl.ActionDeny, - } - - removeUsers(resource, op1, []string{"user1", "user2", "user4"}) // modify astOperation - removeUsers(resource, op2, []string{"user5"}) // remove astOperation - - require.Equal(t, len(resource.Operations), 1) - require.Equal(t, []string{"user3"}, resource.Operations[0].Users) -} - -func TestBucketAclToPolicy(t *testing.T) { - 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{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }, { - Grantee: &Grantee{ - ID: id2, - Type: acpCanonicalUser, - }, - Permission: aclWrite, - }}, - } - - resInfo := &resourceInfo{ - Bucket: "bucketName", - } - - expectedPolicy := &bucketPolicy{ - Bucket: resInfo.Bucket, - Statement: []statement{ - { - Effect: "Allow", - Principal: principal{ - CanonicalUser: id, - }, - Action: []string{"s3:ListBucket", "s3:ListBucketVersions", "s3:ListBucketMultipartUploads", "s3:PutObject", "s3:DeleteObject"}, - Resource: []string{arnAwsPrefix + resInfo.Name()}, - }, - { - Effect: "Allow", - Principal: principal{AWS: allUsersWildcard}, - Action: []string{"s3:ListBucket", "s3:ListBucketVersions", "s3:ListBucketMultipartUploads"}, - Resource: []string{arnAwsPrefix + resInfo.Name()}, - }, - { - Effect: "Allow", - Principal: principal{ - CanonicalUser: id2, - }, - Action: []string{"s3:PutObject", "s3:DeleteObject"}, - Resource: []string{arnAwsPrefix + resInfo.Name()}, - }, - }, - } - - actualPolicy, err := aclToPolicy(acl, resInfo) - require.NoError(t, err) - require.Equal(t, expectedPolicy, actualPolicy) -} - -func TestObjectAclToPolicy(t *testing.T) { - 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: aclFullControl, - }, { - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }}, - } - - resInfo := &resourceInfo{ - Bucket: "bucketName", - Object: "object", - } - - expectedPolicy := &bucketPolicy{ - Bucket: resInfo.Bucket, - Statement: []statement{ - { - Effect: "Allow", - Principal: principal{ - CanonicalUser: id, - }, - Action: []string{"s3:GetObject", "s3:GetObjectVersion"}, - Resource: []string{arnAwsPrefix + resInfo.Name()}, - }, - { - Effect: "Allow", - Principal: principal{ - CanonicalUser: id2, - }, - Action: []string{"s3:GetObject", "s3:GetObjectVersion"}, - Resource: []string{arnAwsPrefix + resInfo.Name()}, - }, - { - Effect: "Allow", - Principal: principal{AWS: allUsersWildcard}, - Action: []string{"s3:GetObject", "s3:GetObjectVersion"}, - Resource: []string{arnAwsPrefix + resInfo.Name()}, - }, - }, - } - - actualPolicy, err := aclToPolicy(acl, resInfo) - require.NoError(t, err) - require.Equal(t, expectedPolicy, actualPolicy) -} - -func TestObjectWithVersionAclToTable(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - id := hex.EncodeToString(key.PublicKey().Bytes()) - - acl := &AccessControlPolicy{ - Owner: Owner{ - ID: id, - DisplayName: "user1", - }, - AccessControlList: []*Grant{{ - Grantee: &Grantee{ - ID: id, - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }}, - } - - resInfoObject := &resourceInfo{ - Bucket: "bucketName", - Object: "object", - } - expectedTable := allowedTableForPrivateObject(t, key, resInfoObject) - actualTable := tableFromACL(t, acl, resInfoObject) - checkTables(t, expectedTable, actualTable) - - resInfoObjectVersion := &resourceInfo{ - Bucket: "bucketName", - Object: "objectVersion", - Version: "Gfrct4Afhio8pCGCCKVNTf1kyexQjMBeaUfvDtQCkAvg", - } - expectedTable = allowedTableForPrivateObject(t, key, resInfoObjectVersion) - actualTable = tableFromACL(t, acl, resInfoObjectVersion) - checkTables(t, expectedTable, actualTable) -} - -func allowedTableForPrivateObject(t *testing.T, key *keys.PrivateKey, resInfo *resourceInfo) *eacl.Table { - var isVersion bool - var objID oid.ID - if resInfo.Version != "" { - isVersion = true - err := objID.DecodeString(resInfo.Version) - require.NoError(t, err) - } - - expectedTable := eacl.NewTable() - serviceRec := &ServiceRecord{Resource: resInfo.Name(), GroupRecordsLength: len(readOps) * 2} - expectedTable.AddRecord(serviceRec.ToEACLRecord()) - - for i := len(readOps) - 1; i >= 0; i-- { - op := readOps[i] - record := getAllowRecord(op, key.PublicKey()) - if isVersion { - record.AddObjectIDFilter(eacl.MatchStringEqual, objID) - } else { - record.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, resInfo.Object) - } - expectedTable.AddRecord(record) - } - for i := len(readOps) - 1; i >= 0; i-- { - op := readOps[i] - record := getOthersRecord(op, eacl.ActionDeny) - if isVersion { - record.AddObjectIDFilter(eacl.MatchStringEqual, objID) - } else { - record.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFilePath, resInfo.Object) - } - expectedTable.AddRecord(record) - } - - return expectedTable -} - -func tableFromACL(t *testing.T, acl *AccessControlPolicy, resInfo *resourceInfo) *eacl.Table { - actualPolicy, err := aclToPolicy(acl, resInfo) - require.NoError(t, err) - actualAst, err := policyToAst(actualPolicy) - require.NoError(t, err) - actualTable, err := astToTable(actualAst) - require.NoError(t, err) - return actualTable -} - -func checkTables(t *testing.T, expectedTable, actualTable *eacl.Table) { - require.Equal(t, len(expectedTable.Records()), len(actualTable.Records()), "different number of records") - for i, record := range expectedTable.Records() { - actRecord := actualTable.Records()[i] - - require.Equal(t, len(record.Targets()), len(actRecord.Targets()), "different number of targets") - for j, target := range record.Targets() { - actTarget := actRecord.Targets()[j] - - expected := fmt.Sprintf("%s %v", target.Role().String(), target.BinaryKeys()) - actual := fmt.Sprintf("%s %v", actTarget.Role().String(), actTarget.BinaryKeys()) - require.Equalf(t, target, actTarget, "want: '%s'\ngot: '%s'", expected, actual) - } - - require.Equal(t, len(record.Filters()), len(actRecord.Filters()), "different number of filters") - for j, filter := range record.Filters() { - actFilter := actRecord.Filters()[j] - - expected := fmt.Sprintf("%s:%s %s %s", filter.From().String(), filter.Key(), filter.Matcher().String(), filter.Value()) - actual := fmt.Sprintf("%s:%s %s %s", actFilter.From().String(), actFilter.Key(), actFilter.Matcher().String(), actFilter.Value()) - require.Equalf(t, filter, actFilter, "want: '%s'\ngot: '%s'", expected, actual) - } - - require.Equal(t, record.Action().String(), actRecord.Action().String()) - require.Equal(t, record.Operation().String(), actRecord.Operation().String()) - } -} - -func TestParseCannedACLHeaders(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - - id := hex.EncodeToString(key.PublicKey().Bytes()) - address := key.PublicKey().Address() - - req := &http.Request{ - Header: map[string][]string{ - api.AmzACL: {basicACLReadOnly}, - }, - } - - expectedACL := &AccessControlPolicy{ - Owner: Owner{ - ID: id, - DisplayName: address, - }, - AccessControlList: []*Grant{{ - Grantee: &Grantee{ - ID: id, - DisplayName: address, - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }, { - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }}, - } - - actualACL, err := parseACLHeaders(req.Header, key.PublicKey()) - require.NoError(t, err) - require.Equal(t, expectedACL, actualACL) -} - -func TestParseACLHeaders(t *testing.T) { - key, err := keys.NewPrivateKey() - require.NoError(t, err) - - id := hex.EncodeToString(key.PublicKey().Bytes()) - address := key.PublicKey().Address() - - req := &http.Request{ - Header: map[string][]string{ - api.AmzGrantFullControl: {"id=\"user1\""}, - api.AmzGrantRead: {"uri=\"" + allUsersGroup + "\", id=\"user2\""}, - api.AmzGrantWrite: {"id=\"user2\", id=\"user3\""}, - }, - } - - expectedACL := &AccessControlPolicy{ - Owner: Owner{ - ID: id, - DisplayName: address, - }, - AccessControlList: []*Grant{{ - Grantee: &Grantee{ - ID: id, - DisplayName: address, - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }, { - Grantee: &Grantee{ - ID: "user1", - Type: acpCanonicalUser, - }, - Permission: aclFullControl, - }, { - Grantee: &Grantee{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }, { - Grantee: &Grantee{ - ID: "user2", - Type: acpCanonicalUser, - }, - Permission: aclRead, - }, { - Grantee: &Grantee{ - ID: "user2", - Type: acpCanonicalUser, - }, - Permission: aclWrite, - }, { - Grantee: &Grantee{ - ID: "user3", - Type: acpCanonicalUser, - }, - Permission: aclWrite, - }}, - } - - actualACL, err := parseACLHeaders(req.Header, key.PublicKey()) - require.NoError(t, err) - require.Equal(t, expectedACL, actualACL) -} - -func TestAddGranteeError(t *testing.T) { - headers := map[string][]string{ - api.AmzGrantFullControl: {"i=\"user1\""}, - api.AmzGrantRead: {"uri, id=\"user2\""}, - api.AmzGrantWrite: {"emailAddress=\"user2\""}, - "unknown header": {"something"}, - } - - expectedList := []*Grant{{ - Permission: "predefined", - }} - - actualList, err := addGrantees(expectedList, headers, "unknown header1") - require.NoError(t, err) - require.Equal(t, expectedList, actualList) - - actualList, err = addGrantees(expectedList, headers, "unknown header") - require.Error(t, err) - require.Nil(t, actualList) - - actualList, err = addGrantees(expectedList, headers, api.AmzGrantFullControl) - require.Error(t, err) - require.Nil(t, actualList) - - actualList, err = addGrantees(expectedList, headers, api.AmzGrantRead) - require.Error(t, err) - require.Nil(t, actualList) - - actualList, err = addGrantees(expectedList, headers, api.AmzGrantWrite) - require.Error(t, err) - require.Nil(t, actualList) -} - -func TestBucketAclToTable(t *testing.T) { - 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{ - URI: allUsersGroup, - Type: acpGroup, - }, - Permission: aclRead, - }, { - Grantee: &Grantee{ - ID: id2, - Type: acpCanonicalUser, - }, - Permission: aclWrite, - }}, - } - - expectedTable := new(eacl.Table) - for _, op := range readOps { - expectedTable.AddRecord(getOthersRecord(op, eacl.ActionAllow)) - } - for _, op := range writeOps { - expectedTable.AddRecord(getAllowRecord(op, key2.PublicKey())) - } - for _, op := range fullOps { - expectedTable.AddRecord(getAllowRecord(op, key.PublicKey())) - } - for _, op := range fullOps { - expectedTable.AddRecord(getOthersRecord(op, eacl.ActionDeny)) - } - resInfo := &resourceInfo{ - Bucket: "bucketName", - } - - actualTable, err := bucketACLToTable(acl, resInfo) - require.NoError(t, err) - 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) - var objID oid.ID - objID.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: objID.EncodeToString(), - } - - var operations []*astOperation - for _, op := range readOps { - astOp := &astOperation{Users: []string{ - hex.EncodeToString(key.PublicKey().Bytes()), - hex.EncodeToString(key2.PublicKey().Bytes()), - }, - 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) - var objID oid.ID - objID.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()), - }, - 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()), - }, - Op: op, - Action: eacl.ActionAllow, - } - operations = append(operations, astOp) - } - for _, op := range readOps { - astOp := &astOperation{ - 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) -} - -func TestPutBucketAPE(t *testing.T) { - hc := prepareHandlerContext(t) - bktName := "bucket-for-acl-ape" - - info := createBucket(hc, bktName) - - _, err := hc.tp.ContainerEACL(hc.Context(), layer.PrmContainerEACL{ContainerID: info.BktInfo.CID}) - require.ErrorContains(t, err, "not found") - - chains, err := hc.h.ape.(*apeMock).ListChains(engine.ContainerTarget(info.BktInfo.CID.EncodeToString())) - require.NoError(t, err) - require.Len(t, chains, 2) -} - func TestPutObjectACLErrorAPE(t *testing.T) { hc := prepareHandlerContext(t) bktName, objName := "bucket-for-acl-ape", "object" @@ -1492,58 +199,6 @@ func TestDeleteBucketWithPolicy(t *testing.T) { require.Empty(t, chains) } -func TestBucketPolicyUnmarshal(t *testing.T) { - for _, tc := range []struct { - name string - policy string - }{ - { - name: "action/resource array", - policy: ` -{ - "Version": "2012-10-17", - "Statement": [{ - "Principal": { - "AWS": "arn:aws:iam::111122223333:role/JohnDoe" - }, - "Effect": "Allow", - "Action": [ - "s3:GetObject", - "s3:GetObjectVersion" - ], - "Resource": [ - "arn:aws:s3:::DOC-EXAMPLE-BUCKET/*", - "arn:aws:s3:::DOC-EXAMPLE-BUCKET2/*" - ] - }] -} -`, - }, - { - name: "action/resource string", - policy: ` -{ - "Version": "2012-10-17", - "Statement": [{ - "Principal": { - "AWS": "arn:aws:iam::111122223333:role/JohnDoe" - }, - "Effect": "Deny", - "Action": "s3:GetObject", - "Resource": "arn:aws:s3:::DOC-EXAMPLE-BUCKET/*" - }] -} -`, - }, - } { - t.Run(tc.name, func(t *testing.T) { - bktPolicy := &bucketPolicy{} - err := json.Unmarshal([]byte(tc.policy), bktPolicy) - require.NoError(t, err) - }) - } -} - func TestPutBucketPolicy(t *testing.T) { bktPolicy := ` { @@ -1621,18 +276,13 @@ func createAccessBox(t *testing.T) (*accessbox.Box, *keys.PrivateKey) { require.NoError(t, err) tok := new(session.Container) - tok.ForVerb(session.VerbContainerSetEACL) + tok.ForVerb(session.VerbContainerPut) err = tok.Sign(key.PrivateKey) require.NoError(t, err) - tok2 := new(session.Container) - tok2.ForVerb(session.VerbContainerPut) - err = tok2.Sign(key.PrivateKey) - require.NoError(t, err) - box := &accessbox.Box{ Gate: &accessbox.GateData{ - SessionTokens: []*session.Container{tok, tok2}, + SessionTokens: []*session.Container{tok}, BearerToken: &bearerToken, }, } diff --git a/api/handler/copy.go b/api/handler/copy.go index fbd931c..0f2e5b3 100644 --- a/api/handler/copy.go +++ b/api/handler/copy.go @@ -13,7 +13,6 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "go.uber.org/zap" ) @@ -42,11 +41,10 @@ func path2BucketObject(path string) (string, string, error) { func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { var ( - err error - versionID string - metadata map[string]string - tagSet map[string]string - sessionTokenEACL *session.Container + err error + versionID string + metadata map[string]string + tagSet map[string]string ctx = r.Context() reqInfo = middleware.GetReqInfo(ctx) @@ -93,20 +91,11 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { return } - apeEnabled := dstBktInfo.APEEnabled || settings.CannedACL != "" - if apeEnabled && cannedACLStatus == aclStatusYes { + if cannedACLStatus == aclStatusYes { h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported)) return } - needUpdateEACLTable := !(apeEnabled || cannedACLStatus == aclStatusNo) - if needUpdateEACLTable { - if sessionTokenEACL, err = getSessionTokenSetEACL(ctx); err != nil { - h.logAndSendError(w, "could not get eacl session token from a box", reqInfo, err) - return - } - } - extendedSrcObjInfo, err := h.obj.GetExtendedObjectInfo(ctx, srcObjPrm) if err != nil { h.logAndSendError(w, "could not find object", reqInfo, err) @@ -239,25 +228,6 @@ func (h *handler) CopyObjectHandler(w http.ResponseWriter, r *http.Request) { return } - if needUpdateEACLTable { - newEaclTable, err := h.getNewEAclTable(r, dstBktInfo, dstObjInfo) - if err != nil { - h.logAndSendError(w, "could not get new eacl table", reqInfo, err) - return - } - - p := &layer.PutBucketACLParams{ - BktInfo: dstBktInfo, - EACL: newEaclTable, - SessionToken: sessionTokenEACL, - } - - if err = h.obj.PutBucketACL(ctx, p); err != nil { - h.logAndSendError(w, "could not put bucket acl", reqInfo, err) - return - } - } - if tagSet != nil { tagPrm := &data.PutObjectTaggingParams{ ObjectVersion: &data.ObjectVersion{ diff --git a/api/handler/head_test.go b/api/handler/head_test.go index fc68a17..2225c96 100644 --- a/api/handler/head_test.go +++ b/api/handler/head_test.go @@ -7,12 +7,9 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" - "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" - cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/stretchr/testify/require" ) @@ -84,31 +81,6 @@ func headObject(t *testing.T, tc *handlerContext, bktName, objName string, heade assertStatus(t, w, status) } -func TestInvalidAccessThroughCache(t *testing.T) { - hc := prepareHandlerContext(t) - - bktName, objName := "bucket-for-cache", "obj-for-cache" - bktInfo, _ := createBucketAndObject(hc, bktName, objName) - setContainerEACL(hc, bktInfo.CID) - - headObject(t, hc, bktName, objName, nil, http.StatusOK) - - w, r := prepareTestRequest(hc, bktName, objName, nil) - hc.Handler().HeadObjectHandler(w, r.WithContext(middleware.SetBox(r.Context(), &middleware.Box{AccessBox: newTestAccessBox(t, nil)}))) - assertStatus(t, w, http.StatusForbidden) -} - -func setContainerEACL(hc *handlerContext, cnrID cid.ID) { - table := eacl.NewTable() - table.SetCID(cnrID) - for _, op := range fullOps { - table.AddRecord(getOthersRecord(op, eacl.ActionDeny)) - } - - err := hc.MockedPool().SetContainerEACL(hc.Context(), *table, nil) - require.NoError(hc.t, err) -} - func TestHeadObject(t *testing.T) { hc := prepareHandlerContextWithMinCache(t) bktName, objName := "bucket", "obj" @@ -155,7 +127,7 @@ func newTestAccessBox(t *testing.T, key *keys.PrivateKey) *accessbox.Box { } var btoken bearer.Token - btoken.SetEACLTable(*eacl.NewTable()) + btoken.SetImpersonate(true) err = btoken.Sign(key.PrivateKey) require.NoError(t, err) diff --git a/api/handler/multipart_upload.go b/api/handler/multipart_upload.go index 169f776..d0a42fb 100644 --- a/api/handler/multipart_upload.go +++ b/api/handler/multipart_upload.go @@ -112,14 +112,7 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re return } - settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo) - if err != nil { - h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err) - return - } - - apeEnabled := bktInfo.APEEnabled || settings.CannedACL != "" - if apeEnabled && cannedACLStatus == aclStatusYes { + if cannedACLStatus == aclStatusYes { h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported)) return } @@ -133,20 +126,6 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re Data: &layer.UploadData{}, } - needUpdateEACLTable := !(apeEnabled || cannedACLStatus == aclStatusNo) - if needUpdateEACLTable { - key, err := h.bearerTokenIssuerKey(r.Context()) - if err != nil { - h.logAndSendError(w, "couldn't get gate key", reqInfo, err, additional...) - return - } - if _, err = parseACLHeaders(r.Header, key); err != nil { - h.logAndSendError(w, "could not parse acl", reqInfo, err, additional...) - return - } - p.Data.ACLHeaders = formACLHeadersForMultipart(r.Header) - } - if len(r.Header.Get(api.AmzTagging)) > 0 { p.Data.TagSet, err = parseTaggingHeader(r.Header) if err != nil { @@ -196,25 +175,6 @@ func (h *handler) CreateMultipartUploadHandler(w http.ResponseWriter, r *http.Re } } -func formACLHeadersForMultipart(header http.Header) map[string]string { - result := make(map[string]string) - - if value := header.Get(api.AmzACL); value != "" { - result[api.AmzACL] = value - } - if value := header.Get(api.AmzGrantRead); value != "" { - result[api.AmzGrantRead] = value - } - if value := header.Get(api.AmzGrantFullControl); value != "" { - result[api.AmzGrantFullControl] = value - } - if value := header.Get(api.AmzGrantWrite); value != "" { - result[api.AmzGrantWrite] = value - } - - return result -} - func (h *handler) UploadPartHandler(w http.ResponseWriter, r *http.Request) { reqInfo := middleware.GetReqInfo(r.Context()) @@ -500,33 +460,6 @@ func (h *handler) completeMultipartUpload(r *http.Request, c *layer.CompleteMult } } - if len(uploadData.ACLHeaders) != 0 { - sessionTokenSetEACL, err := getSessionTokenSetEACL(ctx) - if err != nil { - return nil, fmt.Errorf("couldn't get eacl token: %w", err) - } - key, err := h.bearerTokenIssuerKey(ctx) - if err != nil { - return nil, fmt.Errorf("couldn't get gate key: %w", err) - } - acl, err := parseACLHeaders(r.Header, key) - if err != nil { - return nil, fmt.Errorf("could not parse acl: %w", err) - } - - resInfo := &resourceInfo{ - Bucket: objInfo.Bucket, - Object: objInfo.Name, - } - astObject, err := aclToAst(acl, resInfo) - if err != nil { - return nil, fmt.Errorf("could not translate acl of completed multipart upload to ast: %w", err) - } - if _, err = h.updateBucketACL(r, astObject, bktInfo, sessionTokenSetEACL); err != nil { - return nil, fmt.Errorf("could not update bucket acl while completing multipart upload: %w", err) - } - } - return objInfo, nil } diff --git a/api/handler/put.go b/api/handler/put.go index 050596b..0f71e7e 100644 --- a/api/handler/put.go +++ b/api/handler/put.go @@ -28,8 +28,6 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/retryer" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/tree" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/schema/native" "git.frostfs.info/TrueCloudLab/policy-engine/schema/s3" @@ -173,10 +171,9 @@ func (p *policyCondition) UnmarshalJSON(data []byte) error { // keywords of predefined basic ACL values. const ( - basicACLPrivate = "private" - basicACLReadOnly = "public-read" - basicACLPublic = "public-read-write" - cannedACLAuthRead = "authenticated-read" + basicACLPrivate = "private" + basicACLReadOnly = "public-read" + basicACLPublic = "public-read-write" ) type createBucketParams struct { @@ -186,12 +183,10 @@ type createBucketParams struct { func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { var ( - err error - newEaclTable *eacl.Table - sessionTokenEACL *session.Container - cannedACLStatus = aclHeadersStatus(r) - ctx = r.Context() - reqInfo = middleware.GetReqInfo(ctx) + err error + cannedACLStatus = aclHeadersStatus(r) + ctx = r.Context() + reqInfo = middleware.GetReqInfo(ctx) ) bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName) @@ -206,20 +201,11 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { return } - apeEnabled := bktInfo.APEEnabled || settings.CannedACL != "" - if apeEnabled && cannedACLStatus == aclStatusYes { + if cannedACLStatus == aclStatusYes { h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported)) return } - needUpdateEACLTable := !(apeEnabled || cannedACLStatus == aclStatusNo) - if needUpdateEACLTable { - if sessionTokenEACL, err = getSessionTokenSetEACL(r.Context()); err != nil { - h.logAndSendError(w, "could not get eacl session token from a box", reqInfo, err) - return - } - } - tagSet, err := parseTaggingHeader(r.Header) if err != nil { h.logAndSendError(w, "could not parse tagging header", reqInfo, err) @@ -292,13 +278,6 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { } objInfo := extendedObjInfo.ObjectInfo - if needUpdateEACLTable { - if newEaclTable, err = h.getNewEAclTable(r, bktInfo, objInfo); err != nil { - h.logAndSendError(w, "could not get new eacl table", reqInfo, err) - return - } - } - if tagSet != nil { tagPrm := &data.PutObjectTaggingParams{ ObjectVersion: &data.ObjectVersion{ @@ -315,19 +294,6 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) { } } - if newEaclTable != nil { - p := &layer.PutBucketACLParams{ - BktInfo: bktInfo, - EACL: newEaclTable, - SessionToken: sessionTokenEACL, - } - - if err = h.obj.PutBucketACL(r.Context(), p); err != nil { - h.logAndSendError(w, "could not put bucket acl", reqInfo, err) - return - } - } - if settings.VersioningEnabled() { w.Header().Set(api.AmzVersionID, objInfo.VersionID()) } @@ -459,13 +425,10 @@ func formEncryptionParamsBase(r *http.Request, isCopySource bool) (enc encryptio func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) { var ( - newEaclTable *eacl.Table - tagSet map[string]string - sessionTokenEACL *session.Container - ctx = r.Context() - reqInfo = middleware.GetReqInfo(ctx) - metadata = make(map[string]string) - cannedACLStatus = aclHeadersStatus(r) + tagSet map[string]string + ctx = r.Context() + reqInfo = middleware.GetReqInfo(ctx) + metadata = make(map[string]string) ) policy, err := checkPostPolicy(r, reqInfo, metadata) @@ -501,20 +464,11 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) { return } - apeEnabled := bktInfo.APEEnabled || settings.CannedACL != "" - if apeEnabled && cannedACLStatus == aclStatusYes { + if acl := auth.MultipartFormValue(r, "acl"); acl != "" && acl != basicACLPrivate { h.logAndSendError(w, "acl not supported for this bucket", reqInfo, errors.GetAPIError(errors.ErrAccessControlListNotSupported)) return } - needUpdateEACLTable := !(apeEnabled || cannedACLStatus == aclStatusNo) - if needUpdateEACLTable { - if sessionTokenEACL, err = getSessionTokenSetEACL(ctx); err != nil { - h.logAndSendError(w, "could not get eacl session token from a box", reqInfo, err) - return - } - } - var contentReader io.Reader var size uint64 if content, ok := r.MultipartForm.Value["file"]; ok { @@ -550,18 +504,6 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) { } objInfo := extendedObjInfo.ObjectInfo - if acl := auth.MultipartFormValue(r, "acl"); acl != "" { - r.Header.Set(api.AmzACL, acl) - r.Header.Set(api.AmzGrantFullControl, "") - r.Header.Set(api.AmzGrantWrite, "") - r.Header.Set(api.AmzGrantRead, "") - - if newEaclTable, err = h.getNewEAclTable(r, bktInfo, objInfo); err != nil { - h.logAndSendError(w, "could not get new eacl table", reqInfo, err) - return - } - } - if tagSet != nil { tagPrm := &data.PutObjectTaggingParams{ ObjectVersion: &data.ObjectVersion{ @@ -578,19 +520,6 @@ func (h *handler) PostObject(w http.ResponseWriter, r *http.Request) { } } - if newEaclTable != nil { - p := &layer.PutBucketACLParams{ - BktInfo: bktInfo, - EACL: newEaclTable, - SessionToken: sessionTokenEACL, - } - - if err = h.obj.PutBucketACL(ctx, p); err != nil { - h.logAndSendError(w, "could not put bucket acl", reqInfo, err) - return - } - } - if settings.VersioningEnabled() { w.Header().Set(api.AmzVersionID, objInfo.VersionID()) } @@ -716,56 +645,6 @@ func aclHeadersStatus(r *http.Request) aclStatus { return aclStatusNo } -func (h *handler) getNewEAclTable(r *http.Request, bktInfo *data.BucketInfo, objInfo *data.ObjectInfo) (*eacl.Table, error) { - var newEaclTable *eacl.Table - key, err := h.bearerTokenIssuerKey(r.Context()) - if err != nil { - return nil, fmt.Errorf("get bearer token issuer: %w", err) - } - objectACL, err := parseACLHeaders(r.Header, key) - if err != nil { - return nil, fmt.Errorf("could not parse object acl: %w", err) - } - - resInfo := &resourceInfo{ - Bucket: objInfo.Bucket, - Object: objInfo.Name, - Version: objInfo.VersionID(), - } - - bktPolicy, err := aclToPolicy(objectACL, resInfo) - if err != nil { - return nil, fmt.Errorf("could not translate object acl to bucket policy: %w", err) - } - - astChild, err := policyToAst(bktPolicy) - if err != nil { - return nil, fmt.Errorf("could not translate policy to ast: %w", err) - } - - bacl, err := h.obj.GetBucketACL(r.Context(), bktInfo) - if err != nil { - return nil, fmt.Errorf("could not get bucket eacl: %w", err) - } - - parentAst := tableToAst(bacl.EACL, objInfo.Bucket) - strCID := bacl.Info.CID.EncodeToString() - - for _, resource := range parentAst.Resources { - if resource.Bucket == strCID { - resource.Bucket = objInfo.Bucket - } - } - - if resAst, updated := mergeAst(parentAst, astChild); updated { - if newEaclTable, err = astToTable(resAst); err != nil { - return nil, fmt.Errorf("could not translate ast to table: %w", err) - } - } - - return newEaclTable, nil -} - func parseTaggingHeader(header http.Header) (map[string]string, error) { var tagSet map[string]string if tagging := header.Get(api.AmzTagging); len(tagging) > 0 { @@ -805,8 +684,7 @@ func parseCannedACL(header http.Header) (string, error) { return basicACLPrivate, nil } - if acl == basicACLPrivate || acl == basicACLPublic || - acl == cannedACLAuthRead || acl == basicACLReadOnly { + if acl == basicACLPrivate || acl == basicACLPublic || acl == basicACLReadOnly { return acl, nil } @@ -873,7 +751,6 @@ func (h *handler) createBucketHandlerPolicy(w http.ResponseWriter, r *http.Reque return } - p.APEEnabled = true bktInfo, err := h.obj.CreateBucket(ctx, p) if err != nil { h.logAndSendError(w, "could not create bucket", reqInfo, err) @@ -990,8 +867,6 @@ func bucketCannedACLToAPERules(cannedACL string, reqInfo *middleware.ReqInfo, cn switch cannedACL { case basicACLPrivate: - case cannedACLAuthRead: - fallthrough case basicACLReadOnly: chains[0].Rules = append(chains[0].Rules, chain.Rule{ Status: chain.Allow, diff --git a/api/handler/util.go b/api/handler/util.go index 41fe57a..dde5274 100644 --- a/api/handler/util.go +++ b/api/handler/util.go @@ -16,7 +16,6 @@ import ( frosterrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "go.opentelemetry.io/otel/trace" "go.uber.org/zap" ) @@ -142,16 +141,3 @@ func parseRange(s string) (*layer.RangeParams, error) { End: values[1], }, nil } - -func getSessionTokenSetEACL(ctx context.Context) (*session.Container, error) { - boxData, err := middleware.GetBoxData(ctx) - if err != nil { - return nil, err - } - sessionToken := boxData.Gate.SessionTokenForSetEACL() - if sessionToken == nil { - return nil, s3errors.GetAPIError(s3errors.ErrAccessDenied) - } - - return sessionToken, nil -} diff --git a/api/layer/container.go b/api/layer/container.go index 9077053..6c45da3 100644 --- a/api/layer/container.go +++ b/api/layer/container.go @@ -12,21 +12,9 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" - cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "go.uber.org/zap" ) -type ( - // BucketACL extends BucketInfo by eacl.Table. - BucketACL struct { - Info *data.BucketInfo - EACL *eacl.Table - } -) - const ( attributeLocationConstraint = ".s3-location-constraint" AttributeLockEnabled = "LockEnabled" @@ -64,7 +52,6 @@ func (n *Layer) containerInfo(ctx context.Context, prm PrmContainer) (*data.Buck info.Created = container.CreatedAt(cnr) info.LocationConstraint = cnr.Attribute(attributeLocationConstraint) info.HomomorphicHashDisabled = container.IsHomomorphicHashingDisabled(cnr) - info.APEEnabled = cnr.BasicACL().Bits() == 0 attrLockEnabled := cnr.Attribute(AttributeLockEnabled) if len(attrLockEnabled) > 0 { @@ -133,7 +120,6 @@ func (n *Layer) createContainer(ctx context.Context, p *CreateBucketParams) (*da Created: TimeNow(ctx), LocationConstraint: p.LocationConstraint, ObjectLockEnabled: p.ObjectLockEnabled, - APEEnabled: p.APEEnabled, } attributes := [][2]string{ @@ -146,11 +132,6 @@ func (n *Layer) createContainer(ctx context.Context, p *CreateBucketParams) (*da }) } - basicACL := acl.PublicRWExtended - if p.APEEnabled { - basicACL = 0 - } - res, err := n.frostFS.CreateContainer(ctx, PrmContainerCreate{ Creator: bktInfo.Owner, Policy: p.Policy, @@ -159,7 +140,7 @@ func (n *Layer) createContainer(ctx context.Context, p *CreateBucketParams) (*da SessionToken: p.SessionContainerCreation, CreationTime: bktInfo.Created, AdditionalAttributes: attributes, - BasicACL: basicACL, + BasicACL: 0, // means APE }) if err != nil { return nil, fmt.Errorf("create container: %w", err) @@ -172,17 +153,3 @@ func (n *Layer) createContainer(ctx context.Context, p *CreateBucketParams) (*da return bktInfo, nil } - -func (n *Layer) setContainerEACLTable(ctx context.Context, idCnr cid.ID, table *eacl.Table, sessionToken *session.Container) error { - table.SetCID(idCnr) - - return n.frostFS.SetContainerEACL(ctx, *table, sessionToken) -} - -func (n *Layer) GetContainerEACL(ctx context.Context, cnrID cid.ID) (*eacl.Table, error) { - prm := PrmContainerEACL{ - ContainerID: cnrID, - SessionToken: n.SessionTokenForRead(ctx), - } - return n.frostFS.ContainerEACL(ctx, prm) -} diff --git a/api/layer/frostfs.go b/api/layer/frostfs.go index ce9fa45..b3c2c87 100644 --- a/api/layer/frostfs.go +++ b/api/layer/frostfs.go @@ -11,7 +11,6 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" @@ -64,15 +63,6 @@ type PrmUserContainers struct { SessionToken *session.Container } -// PrmContainerEACL groups parameters of FrostFS.ContainerEACL operation. -type PrmContainerEACL struct { - // Container identifier. - ContainerID cid.ID - - // Token of the container's creation session. Nil means session absence. - SessionToken *session.Container -} - // ContainerCreateResult is a result parameter of FrostFS.CreateContainer operation. type ContainerCreateResult struct { ContainerID cid.ID @@ -216,18 +206,6 @@ type FrostFS interface { // prevented the containers from being listed. UserContainers(context.Context, PrmUserContainers) ([]cid.ID, error) - // SetContainerEACL saves the eACL table of the container in FrostFS. The - // extended ACL is modified within session if session token is not nil. - // - // It returns any error encountered which prevented the eACL from being saved. - SetContainerEACL(context.Context, eacl.Table, *session.Container) error - - // ContainerEACL reads the container eACL from FrostFS by the container ID. - // - // It returns exactly one non-nil value. It returns any error encountered which - // prevented the eACL from being read. - ContainerEACL(context.Context, PrmContainerEACL) (*eacl.Table, error) - // DeleteContainer marks the container to be removed from FrostFS by ID. // Request is sent within session if the session token is specified. // Successful return does not guarantee actual removal. diff --git a/api/layer/frostfs_mock.go b/api/layer/frostfs_mock.go index ff7f3f9..d360020 100644 --- a/api/layer/frostfs_mock.go +++ b/api/layer/frostfs_mock.go @@ -5,13 +5,11 @@ import ( "context" "crypto/rand" "crypto/sha256" - "errors" "fmt" "io" "strings" "time" - "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" v2container "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/container" objectv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" @@ -20,7 +18,6 @@ import ( apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" @@ -68,7 +65,6 @@ type TestFrostFS struct { objectErrors map[string]error objectPutErrors map[string]error containers map[string]*container.Container - eaclTables map[string]*eacl.Table currentEpoch uint64 key *keys.PrivateKey } @@ -79,7 +75,6 @@ func NewTestFrostFS(key *keys.PrivateKey) *TestFrostFS { objectErrors: make(map[string]error), objectPutErrors: make(map[string]error), containers: make(map[string]*container.Container), - eaclTables: make(map[string]*eacl.Table), key: key, } } @@ -222,7 +217,7 @@ func (t *TestFrostFS) ReadObject(ctx context.Context, prm PrmObjectRead) (*Objec if obj, ok := t.objects[sAddr]; ok { owner := getBearerOwner(ctx) - if !t.checkAccess(prm.Container, owner, eacl.OperationGet, obj) { + if !t.checkAccess(prm.Container, owner) { return nil, ErrAccessDenied } @@ -324,9 +319,9 @@ func (t *TestFrostFS) DeleteObject(ctx context.Context, prm PrmObjectDelete) err return err } - if obj, ok := t.objects[addr.EncodeToString()]; ok { + if _, ok := t.objects[addr.EncodeToString()]; ok { owner := getBearerOwner(ctx) - if !t.checkAccess(prm.Container, owner, eacl.OperationDelete, obj) { + if !t.checkAccess(prm.Container, owner) { return ErrAccessDenied } @@ -354,30 +349,6 @@ func (t *TestFrostFS) AllObjects(cnrID cid.ID) []oid.ID { return result } -func (t *TestFrostFS) SetContainerEACL(_ context.Context, table eacl.Table, _ *session.Container) error { - cnrID, ok := table.CID() - if !ok { - return errors.New("invalid cid") - } - - if _, ok = t.containers[cnrID.EncodeToString()]; !ok { - return errors.New("not found") - } - - t.eaclTables[cnrID.EncodeToString()] = &table - - return nil -} - -func (t *TestFrostFS) ContainerEACL(_ context.Context, prm PrmContainerEACL) (*eacl.Table, error) { - table, ok := t.eaclTables[prm.ContainerID.EncodeToString()] - if !ok { - return nil, errors.New("not found") - } - - return table, nil -} - func (t *TestFrostFS) SearchObjects(_ context.Context, prm PrmObjectSearch) ([]oid.ID, error) { filters := object.NewSearchFilters() filters.AddRootFilter() @@ -415,7 +386,7 @@ func (t *TestFrostFS) SearchObjects(_ context.Context, prm PrmObjectSearch) ([]o return res, nil } -func (t *TestFrostFS) checkAccess(cnrID cid.ID, owner user.ID, op eacl.Operation, obj *object.Object) bool { +func (t *TestFrostFS) checkAccess(cnrID cid.ID, owner user.ID) bool { cnr, ok := t.containers[cnrID.EncodeToString()] if !ok { return false @@ -425,57 +396,6 @@ func (t *TestFrostFS) checkAccess(cnrID cid.ID, owner user.ID, op eacl.Operation return cnr.Owner().Equals(owner) } - table, ok := t.eaclTables[cnrID.EncodeToString()] - if !ok { - return true - } - - for _, rec := range table.Records() { - if rec.Operation() != op { - continue - } - - if !matchTarget(rec, owner) { - continue - } - - if matchFilter(rec.Filters(), obj) { - return rec.Action() == eacl.ActionAllow - } - } - - return true -} - -func matchTarget(rec eacl.Record, owner user.ID) bool { - for _, trgt := range rec.Targets() { - if trgt.Role() == eacl.RoleOthers { - return true - } - var targetOwner user.ID - for _, pk := range eacl.TargetECDSAKeys(&trgt) { - user.IDFromKey(&targetOwner, *pk) - if targetOwner.Equals(owner) { - return true - } - } - } - - return false -} - -func matchFilter(filters []eacl.Filter, obj *object.Object) bool { - objID, _ := obj.ID() - for _, f := range filters { - fv2 := f.ToV2() - if fv2.GetMatchType() != acl.MatchTypeStringEqual || - fv2.GetHeaderType() != acl.HeaderTypeObject || - fv2.GetKey() != acl.FilterObjectID || - fv2.GetValue() != objID.EncodeToString() { - return false - } - } - return true } diff --git a/api/layer/layer.go b/api/layer/layer.go index ece8884..c57d3df 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -22,7 +22,6 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" @@ -159,13 +158,6 @@ type ( SessionContainerCreation *session.Container LocationConstraint string ObjectLockEnabled bool - APEEnabled bool - } - // PutBucketACLParams stores put bucket acl request parameters. - PutBucketACLParams struct { - BktInfo *data.BucketInfo - EACL *eacl.Table - SessionToken *session.Container } // DeleteBucketParams stores delete bucket request parameters. DeleteBucketParams struct { @@ -350,24 +342,6 @@ func (n *Layer) ResolveCID(ctx context.Context, name string) (cid.ID, error) { return n.ResolveBucket(ctx, name) } -// GetBucketACL returns bucket acl info by name. -func (n *Layer) GetBucketACL(ctx context.Context, bktInfo *data.BucketInfo) (*BucketACL, error) { - eACL, err := n.GetContainerEACL(ctx, bktInfo.CID) - if err != nil { - return nil, fmt.Errorf("get container eacl: %w", err) - } - - return &BucketACL{ - Info: bktInfo, - EACL: eACL, - }, nil -} - -// PutBucketACL puts bucket acl by name. -func (n *Layer) PutBucketACL(ctx context.Context, param *PutBucketACLParams) error { - return n.setContainerEACLTable(ctx, param.BktInfo.CID, param.EACL, param.SessionToken) -} - // ListBuckets returns all user containers. The name of the bucket is a container // id. Timestamp is omitted since it is not saved in frostfs container. func (n *Layer) ListBuckets(ctx context.Context) ([]*data.BucketInfo, error) { diff --git a/api/layer/multipart_upload.go b/api/layer/multipart_upload.go index 6c89d90..aeb6759 100644 --- a/api/layer/multipart_upload.go +++ b/api/layer/multipart_upload.go @@ -36,7 +36,6 @@ const ( MultipartObjectSize = "S3-Multipart-Object-Size" metaPrefix = "meta-" - aclPrefix = "acl-" MaxSizeUploadsList = 1000 MaxSizePartsList = 1000 @@ -62,8 +61,7 @@ type ( } UploadData struct { - TagSet map[string]string - ACLHeaders map[string]string + TagSet map[string]string } UploadPartParams struct { @@ -149,7 +147,6 @@ type ( func (n *Layer) CreateMultipartUpload(ctx context.Context, p *CreateMultipartParams) error { metaSize := len(p.Header) if p.Data != nil { - metaSize += len(p.Data.ACLHeaders) metaSize += len(p.Data.TagSet) } @@ -167,10 +164,6 @@ func (n *Layer) CreateMultipartUpload(ctx context.Context, p *CreateMultipartPar } if p.Data != nil { - for key, val := range p.Data.ACLHeaders { - info.Meta[aclPrefix+key] = val - } - for key, val := range p.Data.TagSet { info.Meta[tagPrefix+key] = val } @@ -432,16 +425,13 @@ func (n *Layer) CompleteMultipartUpload(ctx context.Context, p *CompleteMultipar initMetadata[MultipartObjectSize] = strconv.FormatUint(multipartObjetSize, 10) uploadData := &UploadData{ - TagSet: make(map[string]string), - ACLHeaders: make(map[string]string), + TagSet: make(map[string]string), } for key, val := range multipartInfo.Meta { if strings.HasPrefix(key, metaPrefix) { initMetadata[strings.TrimPrefix(key, metaPrefix)] = val } else if strings.HasPrefix(key, tagPrefix) { uploadData.TagSet[strings.TrimPrefix(key, tagPrefix)] = val - } else if strings.HasPrefix(key, aclPrefix) { - uploadData.ACLHeaders[strings.TrimPrefix(key, aclPrefix)] = val } } diff --git a/api/middleware/policy.go b/api/middleware/policy.go index 2786535..eaf9ffe 100644 --- a/api/middleware/policy.go +++ b/api/middleware/policy.go @@ -149,12 +149,7 @@ func policyCheck(r *http.Request, cfg PolicyConfig) error { return apiErr.GetAPIErrorWithError(apiErr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String())) } - isAPE := true - if bktInfo != nil { - isAPE = bktInfo.APEEnabled - } - - if isAPE && cfg.Settings.PolicyDenyByDefault() { + if cfg.Settings.PolicyDenyByDefault() { return apiErr.GetAPIErrorWithError(apiErr.ErrAccessDenied, fmt.Errorf("policy check: %s", st.String())) } diff --git a/api/router_mock_test.go b/api/router_mock_test.go index 9ecd947..c262023 100644 --- a/api/router_mock_test.go +++ b/api/router_mock_test.go @@ -424,8 +424,7 @@ func (h *handlerMock) CreateBucketHandler(w http.ResponseWriter, r *http.Request reqInfo := middleware.GetReqInfo(r.Context()) h.buckets[reqInfo.Namespace+reqInfo.BucketName] = &data.BucketInfo{ - Name: reqInfo.BucketName, - APEEnabled: true, + Name: reqInfo.BucketName, } res := &handlerResult{ diff --git a/api/router_test.go b/api/router_test.go index b341012..48cf459 100644 --- a/api/router_test.go +++ b/api/router_test.go @@ -17,9 +17,7 @@ import ( apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics" - cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" - usertest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user/test" engineiam "git.frostfs.info/TrueCloudLab/policy-engine/iam" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" @@ -305,58 +303,6 @@ func TestDefaultPolicyCheckerWithUserTags(t *testing.T) { createBucket(router, ns, bktName) } -func TestACLAPE(t *testing.T) { - t.Run("acl disabled, ape deny by default", func(t *testing.T) { - router := prepareRouter(t) - - ns, bktName, objName := "", "bucket", "object" - bktNameOld, bktNameNew := "old-bucket", "new-bucket" - createOldBucket(router, bktNameOld) - createNewBucket(router, bktNameNew) - - router.middlewareSettings.denyByDefault = true - - // Allow because of using old bucket - putObject(router, ns, bktNameOld, objName, nil) - // Deny because of deny by default - putObjectErr(router, ns, bktNameNew, objName, nil, apiErrors.ErrAccessDenied) - - // Deny because of deny by default - createBucketErr(router, ns, bktName, nil, apiErrors.ErrAccessDenied) - listBucketsErr(router, ns, apiErrors.ErrAccessDenied) - - // Allow operations and check - allowOperations(router, ns, []string{"s3:CreateBucket", "s3:ListAllMyBuckets"}, nil) - createBucket(router, ns, bktName) - listBuckets(router, ns) - }) - - t.Run("acl disabled, ape allow by default", func(t *testing.T) { - router := prepareRouter(t) - - ns, bktName, objName := "", "bucket", "object" - bktNameOld, bktNameNew := "old-bucket", "new-bucket" - createOldBucket(router, bktNameOld) - createNewBucket(router, bktNameNew) - - router.middlewareSettings.denyByDefault = false - - // Allow because of using old bucket - putObject(router, ns, bktNameOld, objName, nil) - // Allow because of allow by default - putObject(router, ns, bktNameNew, objName, nil) - - // Allow because of deny by default - createBucket(router, ns, bktName) - listBuckets(router, ns) - - // Deny operations and check - denyOperations(router, ns, []string{"s3:CreateBucket", "s3:ListAllMyBuckets"}, nil) - createBucketErr(router, ns, bktName, nil, apiErrors.ErrAccessDenied) - listBucketsErr(router, ns, apiErrors.ErrAccessDenied) - }) -} - func TestRequestParametersCheck(t *testing.T) { t.Run("prefix parameter, allow specific value", func(t *testing.T) { router := prepareRouter(t) @@ -679,28 +625,6 @@ func addPolicy(router *routerMock, ns string, id string, effect engineiam.Effect require.NoError(router.t, err) } -func createOldBucket(router *routerMock, bktName string) { - createSpecificBucket(router, bktName, true) -} - -func createNewBucket(router *routerMock, bktName string) { - createSpecificBucket(router, bktName, false) -} - -func createSpecificBucket(router *routerMock, bktName string, old bool) { - router.handler.buckets[bktName] = &data.BucketInfo{ - Name: bktName, - Zone: "container", - CID: cidtest.ID(), - Owner: usertest.ID(), - Created: time.Now(), - LocationConstraint: "default", - ObjectLockEnabled: false, - HomomorphicHashDisabled: false, - APEEnabled: !old, - } -} - func createBucket(router *routerMock, namespace, bktName string) { w := createBucketBase(router, namespace, bktName, nil) resp := readResponse(router.t, w) @@ -723,24 +647,6 @@ func createBucketBase(router *routerMock, namespace, bktName string, header http return w } -func listBuckets(router *routerMock, namespace string) { - w := listBucketsBase(router, namespace) - resp := readResponse(router.t, w) - require.Equal(router.t, s3middleware.ListBucketsOperation, resp.Method) -} - -func listBucketsErr(router *routerMock, namespace string, errCode apiErrors.ErrorCode) { - w := listBucketsBase(router, namespace) - assertAPIError(router.t, w, errCode) -} - -func listBucketsBase(router *routerMock, namespace string) *httptest.ResponseRecorder { - w, r := httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/", nil) - r.Header.Set(FrostfsNamespaceHeader, namespace) - router.ServeHTTP(w, r) - return w -} - func getBucketErr(router *routerMock, namespace, bktName string, errCode apiErrors.ErrorCode) { w := getBucketBase(router, namespace, bktName) assertAPIError(router.t, w, errCode) diff --git a/creds/accessbox/accessbox.go b/creds/accessbox/accessbox.go index 3c02723..690c171 100644 --- a/creds/accessbox/accessbox.go +++ b/creds/accessbox/accessbox.go @@ -54,11 +54,6 @@ func (g *GateData) SessionTokenForDelete() *session.Container { return g.containerSessionToken(session.VerbContainerDelete) } -// SessionTokenForSetEACL returns the first suitable container session context for SetEACL operation. -func (g *GateData) SessionTokenForSetEACL() *session.Container { - return g.containerSessionToken(session.VerbContainerSetEACL) -} - // SessionToken returns the first container session context. func (g *GateData) SessionToken() *session.Container { if len(g.SessionTokens) != 0 { @@ -78,7 +73,7 @@ func (g *GateData) containerSessionToken(verb session.ContainerVerb) *session.Co func isAppropriateContainerContext(tok *session.Container, verb session.ContainerVerb) bool { switch verb { - case session.VerbContainerSetEACL, session.VerbContainerDelete, session.VerbContainerPut: + case session.VerbContainerDelete, session.VerbContainerPut: return tok.AssertVerb(verb) default: return false diff --git a/creds/accessbox/bearer_token_test.go b/creds/accessbox/bearer_token_test.go index 593332f..b6ee0e0 100644 --- a/creds/accessbox/bearer_token_test.go +++ b/creds/accessbox/bearer_token_test.go @@ -205,18 +205,6 @@ func TestGateDataSessionToken(t *testing.T) { require.Equal(t, sessionTknDelete, sessionTkn) }) - t.Run("session token for set eACL", func(t *testing.T) { - gate.SessionTokens = []*session.Container{} - sessionTkn := gate.SessionTokenForSetEACL() - require.Nil(t, sessionTkn) - - sessionTknSetEACL := new(session.Container) - sessionTknSetEACL.ForVerb(session.VerbContainerSetEACL) - gate.SessionTokens = []*session.Container{sessionTknSetEACL} - sessionTkn = gate.SessionTokenForSetEACL() - require.Equal(t, sessionTknSetEACL, sessionTkn) - }) - t.Run("session token", func(t *testing.T) { gate.SessionTokens = []*session.Container{} sessionTkn := gate.SessionToken() diff --git a/internal/frostfs/frostfs.go b/internal/frostfs/frostfs.go index 3948cbd..a6b3214 100644 --- a/internal/frostfs/frostfs.go +++ b/internal/frostfs/frostfs.go @@ -15,7 +15,6 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" - "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/eacl" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool" @@ -162,29 +161,6 @@ func (x *FrostFS) UserContainers(ctx context.Context, layerPrm layer.PrmUserCont return r, handleObjectError("list user containers via connection pool", err) } -// SetContainerEACL implements frostfs.FrostFS interface method. -func (x *FrostFS) SetContainerEACL(ctx context.Context, table eacl.Table, sessionToken *session.Container) error { - prm := pool.PrmContainerSetEACL{Table: table, Session: sessionToken, WaitParams: &x.await} - - err := x.pool.SetEACL(ctx, prm) - return handleObjectError("save eACL via connection pool", err) -} - -// ContainerEACL implements frostfs.FrostFS interface method. -func (x *FrostFS) ContainerEACL(ctx context.Context, layerPrm layer.PrmContainerEACL) (*eacl.Table, error) { - prm := pool.PrmContainerEACL{ - ContainerID: layerPrm.ContainerID, - Session: layerPrm.SessionToken, - } - - res, err := x.pool.GetEACL(ctx, prm) - if err != nil { - return nil, handleObjectError("read eACL via connection pool", err) - } - - return &res, nil -} - // DeleteContainer implements frostfs.FrostFS interface method. func (x *FrostFS) DeleteContainer(ctx context.Context, id cid.ID, token *session.Container) error { prm := pool.PrmContainerDelete{ContainerID: id, Session: token, WaitParams: &x.await}