diff --git a/api/middleware/policy.go b/api/middleware/policy.go index 3af3fd58..615e3235 100644 --- a/api/middleware/policy.go +++ b/api/middleware/policy.go @@ -51,7 +51,7 @@ type PolicySettings interface { } type FrostFSIDInformer interface { - GetUserGroupIDs(userHash util.Uint160) ([]string, error) + GetUserGroupIDsAndClaims(userHash util.Uint160) ([]string, map[string]string, error) } type XMLDecoder interface { @@ -149,6 +149,7 @@ func getPolicyRequest(r *http.Request, cfg PolicyConfig, reqType ReqType, bktNam var ( owner string groups []string + tags map[string]string ) ctx := r.Context() @@ -160,7 +161,7 @@ func getPolicyRequest(r *http.Request, cfg PolicyConfig, reqType ReqType, bktNam } owner = pk.Address() - groups, err = cfg.FrostfsID.GetUserGroupIDs(pk.GetScriptHash()) + groups, tags, err = cfg.FrostfsID.GetUserGroupIDsAndClaims(pk.GetScriptHash()) if err != nil { 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) } - 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 { 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, - 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{ s3.PropertyKeyOwner: owner, common.PropertyKeyFrostFSIDGroupID: chain.FormCondSliceContainsValue(groups), } queries := GetReqInfo(r.Context()).URL.Query() + for k, v := range tags { + res[fmt.Sprintf(common.PropertyKeyFormatFrostFSIDUserClaim, k)] = v + } + if reqType == objectType { if versionID := queries.Get(QueryVersionID); len(versionID) > 0 { res[s3.PropertyKeyVersionID] = versionID diff --git a/api/router_mock_test.go b/api/router_mock_test.go index 8c996da7..6b410020 100644 --- a/api/router_mock_test.go +++ b/api/router_mock_test.go @@ -85,14 +85,15 @@ func (r *middlewareSettingsMock) ACLEnabled() bool { } type frostFSIDMock struct { + tags map[string]string } func (f *frostFSIDMock) ValidatePublicKey(*keys.PublicKey) error { return nil } -func (f *frostFSIDMock) GetUserGroupIDs(util.Uint160) ([]string, error) { - return []string{}, nil +func (f *frostFSIDMock) GetUserGroupIDsAndClaims(util.Uint160) ([]string, map[string]string, error) { + return []string{}, f.tags, nil } type xmlMock struct { diff --git a/api/router_test.go b/api/router_test.go index fdeb851d..ed895a81 100644 --- a/api/router_test.go +++ b/api/router_test.go @@ -22,6 +22,7 @@ import ( "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "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" "github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5/middleware" @@ -256,6 +257,22 @@ func TestDefaultBehaviorPolicyChecker(t *testing.T) { createBucketErr(chiRouter, ns, bktName, apiErrors.ErrAccessDenied) } +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) { t.Run("acl disabled, ape deny by default", func(t *testing.T) { router := prepareRouter(t) diff --git a/internal/frostfs/frostfsid/frostfsid.go b/internal/frostfs/frostfsid/frostfsid.go index a7eaf48b..26e4f624 100644 --- a/internal/frostfs/frostfsid/frostfsid.go +++ b/internal/frostfs/frostfsid/frostfsid.go @@ -57,14 +57,14 @@ func (f *FrostFSID) ValidatePublicKey(key *keys.PublicKey) error { 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) if err != nil { if strings.Contains(err.Error(), "not found") { f.log.Debug(logs.UserGroupsListIsEmpty, zap.Error(err)) - return nil, nil + return nil, nil, nil } - return nil, err + return nil, nil, err } 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) } - return res, nil + return res, subj.KV, nil } func (f *FrostFSID) getSubject(addr util.Uint160) (*client.SubjectExtended, error) {