[#365] Include iam user tags in query #365

Merged
dkirillov merged 1 commit from pogpp/frostfs-s3-gw:feature/add_user_tags_in_query into master 2024-04-22 12:18:06 +00:00
4 changed files with 33 additions and 10 deletions

View file

@ -51,7 +51,7 @@ type PolicySettings interface {
} }
type FrostFSIDInformer interface { type FrostFSIDInformer interface {
GetUserGroupIDs(userHash util.Uint160) ([]string, error) GetUserGroupIDsAndClaims(userHash util.Uint160) ([]string, map[string]string, error)
} }
type XMLDecoder interface { type XMLDecoder interface {
@ -149,6 +149,7 @@ func getPolicyRequest(r *http.Request, cfg PolicyConfig, reqType ReqType, bktNam
var ( var (
owner string owner string
groups []string groups []string
tags map[string]string
) )
ctx := r.Context() ctx := r.Context()
@ -160,7 +161,7 @@ func getPolicyRequest(r *http.Request, cfg PolicyConfig, reqType ReqType, bktNam
} }
owner = pk.Address() owner = pk.Address()
groups, err = cfg.FrostfsID.GetUserGroupIDs(pk.GetScriptHash()) groups, tags, err = cfg.FrostfsID.GetUserGroupIDsAndClaims(pk.GetScriptHash())
if err != nil { if err != nil {
return nil, fmt.Errorf("get group ids: %w", err) return nil, fmt.Errorf("get group ids: %w", err)
} }
@ -175,7 +176,7 @@ func getPolicyRequest(r *http.Request, cfg PolicyConfig, reqType ReqType, bktNam
res = fmt.Sprintf(s3.ResourceFormatS3Bucket, bktName) res = fmt.Sprintf(s3.ResourceFormatS3Bucket, bktName)
} }
properties, err := determineProperties(r, cfg.Decoder, cfg.BucketResolver, cfg.Tagging, reqType, op, bktName, objName, owner, groups) properties, err := determineProperties(r, cfg.Decoder, cfg.BucketResolver, cfg.Tagging, reqType, op, bktName, objName, owner, groups, tags)
if err != nil { if err != nil {
return nil, fmt.Errorf("determine properties: %w", err) return nil, fmt.Errorf("determine properties: %w", err)
} }
@ -410,13 +411,17 @@ func determineGeneralOperation(r *http.Request) string {
} }
func determineProperties(r *http.Request, decoder XMLDecoder, resolver BucketResolveFunc, tagging ResourceTagging, reqType ReqType, func determineProperties(r *http.Request, decoder XMLDecoder, resolver BucketResolveFunc, tagging ResourceTagging, reqType ReqType,
op, bktName, objName, owner string, groups []string) (map[string]string, error) { op, bktName, objName, owner string, groups []string, tags map[string]string) (map[string]string, error) {
res := map[string]string{ res := map[string]string{
s3.PropertyKeyOwner: owner, s3.PropertyKeyOwner: owner,
common.PropertyKeyFrostFSIDGroupID: chain.FormCondSliceContainsValue(groups), common.PropertyKeyFrostFSIDGroupID: chain.FormCondSliceContainsValue(groups),
} }
queries := GetReqInfo(r.Context()).URL.Query() queries := GetReqInfo(r.Context()).URL.Query()
for k, v := range tags {
res[fmt.Sprintf(common.PropertyKeyFormatFrostFSIDUserClaim, k)] = v
}
if reqType == objectType { if reqType == objectType {
if versionID := queries.Get(QueryVersionID); len(versionID) > 0 { if versionID := queries.Get(QueryVersionID); len(versionID) > 0 {
res[s3.PropertyKeyVersionID] = versionID res[s3.PropertyKeyVersionID] = versionID

View file

@ -85,14 +85,15 @@ func (r *middlewareSettingsMock) ACLEnabled() bool {
} }
type frostFSIDMock struct { type frostFSIDMock struct {
tags map[string]string
} }
func (f *frostFSIDMock) ValidatePublicKey(*keys.PublicKey) error { func (f *frostFSIDMock) ValidatePublicKey(*keys.PublicKey) error {
return nil return nil
} }
func (f *frostFSIDMock) GetUserGroupIDs(util.Uint160) ([]string, error) { func (f *frostFSIDMock) GetUserGroupIDsAndClaims(util.Uint160) ([]string, map[string]string, error) {
return []string{}, nil return []string{}, f.tags, nil
} }
type xmlMock struct { type xmlMock struct {

View file

@ -22,6 +22,7 @@ import (
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/common"
"git.frostfs.info/TrueCloudLab/policy-engine/schema/s3" "git.frostfs.info/TrueCloudLab/policy-engine/schema/s3"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
@ -256,6 +257,22 @@ func TestDefaultBehaviorPolicyChecker(t *testing.T) {
createBucketErr(chiRouter, ns, bktName, apiErrors.ErrAccessDenied) createBucketErr(chiRouter, ns, bktName, apiErrors.ErrAccessDenied)
} }
dkirillov marked this conversation as resolved Outdated

The logic should be the following:

  • make denyByDefault to true
  • allow bucket creation if and only if user has specific claim
  • try create bucket (error AccessDenied must be got)
  • update FrostFSIDMock so it return appropriate tag for user
  • try create bucket (now it must be successfull)
The logic should be the following: * make denyByDefault to `true` * allow bucket creation if and only if user has specific claim * try create bucket (error `AccessDenied` must be got) * update `FrostFSIDMock` so it return appropriate tag for user * try create bucket (now it must be successfull)
func TestDefaultPolicyCheckerWithUserTags(t *testing.T) {
router := prepareRouter(t)
ns, bktName := "", "bucket"
router.middlewareSettings.denyByDefault = true
allowOperations(router, ns, []string{"s3:CreateBucket"}, engineiam.Conditions{
engineiam.CondStringEquals: engineiam.Condition{fmt.Sprintf(common.PropertyKeyFormatFrostFSIDUserClaim, "tag-test"): []string{"test"}},
})
createBucketErr(router, ns, bktName, apiErrors.ErrAccessDenied)
tags := make(map[string]string)
tags["tag-test"] = "test"
router.cfg.FrostfsID.(*frostFSIDMock).tags = tags
createBucket(router, ns, bktName)
}
func TestACLAPE(t *testing.T) { func TestACLAPE(t *testing.T) {
t.Run("acl disabled, ape deny by default", func(t *testing.T) { t.Run("acl disabled, ape deny by default", func(t *testing.T) {
router := prepareRouter(t) router := prepareRouter(t)

View file

@ -57,14 +57,14 @@ func (f *FrostFSID) ValidatePublicKey(key *keys.PublicKey) error {
return err return err
} }
func (f *FrostFSID) GetUserGroupIDs(userHash util.Uint160) ([]string, error) { func (f *FrostFSID) GetUserGroupIDsAndClaims(userHash util.Uint160) ([]string, map[string]string, error) {
subj, err := f.getSubject(userHash) subj, err := f.getSubject(userHash)
if err != nil { if err != nil {
if strings.Contains(err.Error(), "not found") { if strings.Contains(err.Error(), "not found") {
f.log.Debug(logs.UserGroupsListIsEmpty, zap.Error(err)) f.log.Debug(logs.UserGroupsListIsEmpty, zap.Error(err))
return nil, nil return nil, nil, nil
dkirillov marked this conversation as resolved Outdated

We must not return error

We must not return error
} }
return nil, err return nil, nil, err
} }
res := make([]string, len(subj.Groups)) res := make([]string, len(subj.Groups))
@ -72,7 +72,7 @@ func (f *FrostFSID) GetUserGroupIDs(userHash util.Uint160) ([]string, error) {
res[i] = strconv.FormatInt(group.ID, 10) res[i] = strconv.FormatInt(group.ID, 10)
} }
return res, nil return res, subj.KV, nil
} }
func (f *FrostFSID) getSubject(addr util.Uint160) (*client.SubjectExtended, error) { func (f *FrostFSID) getSubject(addr util.Uint160) (*client.SubjectExtended, error) {