[#357] Add check of request and resource tags
Signed-off-by: Marina Biryukova <m.biryukova@yadro.com>
This commit is contained in:
parent
9f29fcbd52
commit
3ff027587c
24 changed files with 506 additions and 155 deletions
|
@ -3,12 +3,16 @@ package middleware
|
|||
import (
|
||||
"context"
|
||||
"crypto/elliptic"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
||||
apiErr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
||||
frostfsErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/errors"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
|
@ -25,8 +29,22 @@ const (
|
|||
QueryPrefix = "prefix"
|
||||
QueryDelimiter = "delimiter"
|
||||
QueryMaxKeys = "max-keys"
|
||||
amzTagging = "x-amz-tagging"
|
||||
)
|
||||
|
||||
// At the beginning of these operations resources haven't yet been created.
|
||||
var withoutResourceOps = []string{
|
||||
CreateBucketOperation,
|
||||
CreateMultipartUploadOperation,
|
||||
AbortMultipartUploadOperation,
|
||||
CompleteMultipartUploadOperation,
|
||||
UploadPartOperation,
|
||||
UploadPartCopyOperation,
|
||||
ListPartsOperation,
|
||||
PutObjectOperation,
|
||||
CopyObjectOperation,
|
||||
}
|
||||
|
||||
type PolicySettings interface {
|
||||
PolicyDenyByDefault() bool
|
||||
ACLEnabled() bool
|
||||
|
@ -36,6 +54,15 @@ type FrostFSIDInformer interface {
|
|||
GetUserGroupIDs(userHash util.Uint160) ([]string, error)
|
||||
}
|
||||
|
||||
type XMLDecoder interface {
|
||||
NewXMLDecoder(io.Reader) *xml.Decoder
|
||||
}
|
||||
|
||||
type ResourceTagging interface {
|
||||
GetBucketTagging(ctx context.Context, bktInfo *data.BucketInfo) (map[string]string, error)
|
||||
GetObjectTagging(ctx context.Context, p *data.GetObjectTaggingParams) (string, map[string]string, error)
|
||||
}
|
||||
|
||||
// BucketResolveFunc is a func to resolve bucket info by name.
|
||||
type BucketResolveFunc func(ctx context.Context, bucket string) (*data.BucketInfo, error)
|
||||
|
||||
|
@ -46,6 +73,8 @@ type PolicyConfig struct {
|
|||
Domains []string
|
||||
Log *zap.Logger
|
||||
BucketResolver BucketResolveFunc
|
||||
Decoder XMLDecoder
|
||||
Tagging ResourceTagging
|
||||
}
|
||||
|
||||
func PolicyCheck(cfg PolicyConfig) Func {
|
||||
|
@ -54,6 +83,7 @@ func PolicyCheck(cfg PolicyConfig) Func {
|
|||
ctx := r.Context()
|
||||
if err := policyCheck(r, cfg); err != nil {
|
||||
reqLogOrDefault(ctx, cfg.Log).Error(logs.PolicyValidationFailed, zap.Error(err))
|
||||
err = frostfsErrors.UnwrapErr(err)
|
||||
if _, wrErr := WriteErrorResponse(w, GetReqInfo(ctx), err); wrErr != nil {
|
||||
reqLogOrDefault(ctx, cfg.Log).Error(logs.FailedToWriteResponse, zap.Error(wrErr))
|
||||
}
|
||||
|
@ -67,7 +97,7 @@ func PolicyCheck(cfg PolicyConfig) Func {
|
|||
|
||||
func policyCheck(r *http.Request, cfg PolicyConfig) error {
|
||||
reqType, bktName, objName := getBucketObject(r, cfg.Domains)
|
||||
req, err := getPolicyRequest(r, cfg.FrostfsID, reqType, bktName, objName, cfg.Log)
|
||||
req, err := getPolicyRequest(r, cfg, reqType, bktName, objName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -115,7 +145,7 @@ func policyCheck(r *http.Request, cfg PolicyConfig) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func getPolicyRequest(r *http.Request, frostfsid FrostFSIDInformer, reqType ReqType, bktName string, objName string, log *zap.Logger) (*testutil.Request, error) {
|
||||
func getPolicyRequest(r *http.Request, cfg PolicyConfig, reqType ReqType, bktName string, objName string) (*testutil.Request, error) {
|
||||
var (
|
||||
owner string
|
||||
groups []string
|
||||
|
@ -130,7 +160,7 @@ func getPolicyRequest(r *http.Request, frostfsid FrostFSIDInformer, reqType ReqT
|
|||
}
|
||||
owner = pk.Address()
|
||||
|
||||
groups, err = frostfsid.GetUserGroupIDs(pk.GetScriptHash())
|
||||
groups, err = cfg.FrostfsID.GetUserGroupIDs(pk.GetScriptHash())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get group ids: %w", err)
|
||||
}
|
||||
|
@ -145,9 +175,12 @@ func getPolicyRequest(r *http.Request, frostfsid FrostFSIDInformer, reqType ReqT
|
|||
res = fmt.Sprintf(s3.ResourceFormatS3Bucket, bktName)
|
||||
}
|
||||
|
||||
properties := determineProperties(ctx, reqType, op, owner, groups)
|
||||
properties, err := determineProperties(r, cfg.Decoder, cfg.BucketResolver, cfg.Tagging, reqType, op, bktName, objName, owner, groups)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("determine properties: %w", err)
|
||||
}
|
||||
|
||||
reqLogOrDefault(r.Context(), log).Debug(logs.PolicyRequest, zap.String("action", op),
|
||||
reqLogOrDefault(r.Context(), cfg.Log).Debug(logs.PolicyRequest, zap.String("action", op),
|
||||
zap.String("resource", res), zap.Any("properties", properties))
|
||||
|
||||
return testutil.NewRequest(op, testutil.NewResource(res, nil), properties), nil
|
||||
|
@ -376,12 +409,13 @@ func determineGeneralOperation(r *http.Request) string {
|
|||
return "UnmatchedOperation"
|
||||
}
|
||||
|
||||
func determineProperties(ctx context.Context, reqType ReqType, op, owner string, groups []string) map[string]string {
|
||||
func determineProperties(r *http.Request, decoder XMLDecoder, resolver BucketResolveFunc, tagging ResourceTagging, reqType ReqType,
|
||||
op, bktName, objName, owner string, groups []string) (map[string]string, error) {
|
||||
res := map[string]string{
|
||||
s3.PropertyKeyOwner: owner,
|
||||
common.PropertyKeyFrostFSIDGroupID: chain.FormCondSliceContainsValue(groups),
|
||||
}
|
||||
queries := GetReqInfo(ctx).URL.Query()
|
||||
queries := GetReqInfo(r.Context()).URL.Query()
|
||||
|
||||
if reqType == objectType {
|
||||
if versionID := queries.Get(QueryVersionID); len(versionID) > 0 {
|
||||
|
@ -402,5 +436,107 @@ func determineProperties(ctx context.Context, reqType ReqType, op, owner string,
|
|||
}
|
||||
}
|
||||
|
||||
return res
|
||||
tags, err := determineTags(r, decoder, resolver, tagging, reqType, op, bktName, objName, queries.Get(QueryVersionID))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("determine tags: %w", err)
|
||||
}
|
||||
for k, v := range tags {
|
||||
res[k] = v
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func determineTags(r *http.Request, decoder XMLDecoder, resolver BucketResolveFunc, tagging ResourceTagging, reqType ReqType,
|
||||
op, bktName, objName, versionID string) (map[string]string, error) {
|
||||
res, err := determineRequestTags(r, decoder, op)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("determine request tags: %w", err)
|
||||
}
|
||||
|
||||
tags, err := determineResourceTags(r.Context(), reqType, op, bktName, objName, versionID, resolver, tagging)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("determine resource tags: %w", err)
|
||||
}
|
||||
for k, v := range tags {
|
||||
res[k] = v
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func determineRequestTags(r *http.Request, decoder XMLDecoder, op string) (map[string]string, error) {
|
||||
tags := make(map[string]string)
|
||||
|
||||
if strings.HasSuffix(op, PutObjectTaggingOperation) || strings.HasSuffix(op, PutBucketTaggingOperation) {
|
||||
tagging := new(data.Tagging)
|
||||
if err := decoder.NewXMLDecoder(r.Body).Decode(tagging); err != nil {
|
||||
return nil, apiErr.GetAPIError(apiErr.ErrMalformedXML)
|
||||
}
|
||||
GetReqInfo(r.Context()).Tagging = tagging
|
||||
|
||||
for _, tag := range tagging.TagSet {
|
||||
tags[fmt.Sprintf(s3.PropertyKeyFormatRequestTag, tag.Key)] = tag.Value
|
||||
}
|
||||
}
|
||||
|
||||
if tagging := r.Header.Get(amzTagging); len(tagging) > 0 {
|
||||
queries, err := url.ParseQuery(tagging)
|
||||
if err != nil {
|
||||
return nil, apiErr.GetAPIError(apiErr.ErrInvalidArgument)
|
||||
}
|
||||
for key := range queries {
|
||||
tags[fmt.Sprintf(s3.PropertyKeyFormatRequestTag, key)] = queries.Get(key)
|
||||
}
|
||||
}
|
||||
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
func determineResourceTags(ctx context.Context, reqType ReqType, op, bktName, objName, versionID string, resolver BucketResolveFunc,
|
||||
tagging ResourceTagging) (map[string]string, error) {
|
||||
tags := make(map[string]string)
|
||||
|
||||
if reqType != bucketType && reqType != objectType {
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
for _, withoutResOp := range withoutResourceOps {
|
||||
if strings.HasSuffix(op, withoutResOp) {
|
||||
return tags, nil
|
||||
}
|
||||
}
|
||||
|
||||
bktInfo, err := resolver(ctx, bktName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get bucket info: %w", err)
|
||||
}
|
||||
|
||||
if reqType == bucketType {
|
||||
tags, err = tagging.GetBucketTagging(ctx, bktInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get bucket tagging: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if reqType == objectType {
|
||||
tagPrm := &data.GetObjectTaggingParams{
|
||||
ObjectVersion: &data.ObjectVersion{
|
||||
BktInfo: bktInfo,
|
||||
ObjectName: objName,
|
||||
VersionID: versionID,
|
||||
},
|
||||
}
|
||||
_, tags, err = tagging.GetObjectTagging(ctx, tagPrm)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("get object tagging: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
res := make(map[string]string, len(tags))
|
||||
for k, v := range tags {
|
||||
res[fmt.Sprintf(s3.PropertyKeyFormatResourceTag, k)] = v
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue