From 5315f7b733e3f11d34bec4c2f820a7df6ef4210e Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Tue, 12 Mar 2024 11:32:51 +0300 Subject: [PATCH] [#269] Create frostfsid wrapper with cache Signed-off-by: Denis Kirillov --- CHANGELOG.md | 1 + api/cache/frostfsid.go | 7 -- cmd/s3-gw/app.go | 13 ++- internal/frostfs/frostfsid/frostfsid.go | 128 ++++++++++++++++++++++++ internal/logs/logs.go | 3 + 5 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 internal/frostfs/frostfsid/frostfsid.go diff --git a/CHANGELOG.md b/CHANGELOG.md index b15ad65..b014a98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ This document outlines major changes between releases. - Authmate: support custom attributes (#292) - Add new `reconnect_interval` config param (#291) - Support `GetBucketPolicyStatus` (#301) +- Add FrostfsID cache (#269) ### Changed - Generalise config param `use_default_xmlns_for_complete_multipart` to `use_default_xmlns` so that use default xmlns for all requests (#221) diff --git a/api/cache/frostfsid.go b/api/cache/frostfsid.go index da093ec..417a7ed 100644 --- a/api/cache/frostfsid.go +++ b/api/cache/frostfsid.go @@ -6,8 +6,6 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" "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" "github.com/bluele/gcache" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/util" @@ -20,11 +18,6 @@ type FrostfsIDCache struct { logger *zap.Logger } -type FrostfsIDCacheKey struct { - Target engine.Target - Name chain.Name -} - const ( // DefaultFrostfsIDCacheSize is a default maximum number of entries in cache. DefaultFrostfsIDCacheSize = 1e4 diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index 182e719..2a98947 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -30,6 +30,7 @@ import ( "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/frostfsid" + ffidcontract "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/frostfsid/contract" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy/contract" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/services" @@ -457,8 +458,7 @@ func (a *App) initMetrics() { } func (a *App) initFrostfsID(ctx context.Context) { - var err error - a.frostfsid, err = frostfsid.New(ctx, frostfsid.Config{ + cli, err := ffidcontract.New(ctx, ffidcontract.Config{ RPCAddress: a.cfg.GetString(cfgRPCEndpoint), Contract: a.cfg.GetString(cfgFrostfsIDContract), ProxyContract: a.cfg.GetString(cfgProxyContract), @@ -467,6 +467,15 @@ func (a *App) initFrostfsID(ctx context.Context) { if err != nil { a.log.Fatal(logs.InitFrostfsIDContractFailed, zap.Error(err)) } + + a.frostfsid, err = frostfsid.NewFrostFSID(frostfsid.Config{ + Cache: cache.NewFrostfsIDCache(getFrostfsIDCacheConfig(a.cfg, a.log)), + FrostFSID: cli, + Logger: a.log, + }) + if err != nil { + a.log.Fatal(logs.InitFrostfsIDContractFailed, zap.Error(err)) + } } func (a *App) initPolicyStorage(ctx context.Context) { diff --git a/internal/frostfs/frostfsid/frostfsid.go b/internal/frostfs/frostfsid/frostfsid.go new file mode 100644 index 0000000..a7eaf48 --- /dev/null +++ b/internal/frostfs/frostfsid/frostfsid.go @@ -0,0 +1,128 @@ +package frostfsid + +import ( + "encoding/hex" + "errors" + "strconv" + "strings" + + "git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/frostfsid/contract" + "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" + "github.com/nspcc-dev/neo-go/pkg/util" + "go.uber.org/zap" +) + +type FrostFSID struct { + frostfsid *contract.FrostFSID + cache *cache.FrostfsIDCache + log *zap.Logger +} + +type Config struct { + Cache *cache.FrostfsIDCache + FrostFSID *contract.FrostFSID + Logger *zap.Logger +} + +var ( + _ api.FrostFSID = (*FrostFSID)(nil) + _ handler.FrostFSID = (*FrostFSID)(nil) +) + +// NewFrostFSID creates new FrostfsID wrapper. +func NewFrostFSID(cfg Config) (*FrostFSID, error) { + switch { + case cfg.FrostFSID == nil: + return nil, errors.New("missing frostfsid client") + case cfg.Cache == nil: + return nil, errors.New("missing frostfsid cache") + case cfg.Logger == nil: + return nil, errors.New("missing frostfsid logger") + } + + return &FrostFSID{ + frostfsid: cfg.FrostFSID, + cache: cfg.Cache, + log: cfg.Logger, + }, nil +} + +func (f *FrostFSID) ValidatePublicKey(key *keys.PublicKey) error { + _, err := f.getSubject(key.GetScriptHash()) + return err +} + +func (f *FrostFSID) GetUserGroupIDs(userHash util.Uint160) ([]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, err + } + + res := make([]string, len(subj.Groups)) + for i, group := range subj.Groups { + res[i] = strconv.FormatInt(group.ID, 10) + } + + return res, nil +} + +func (f *FrostFSID) getSubject(addr util.Uint160) (*client.SubjectExtended, error) { + if subj := f.cache.GetSubject(addr); subj != nil { + return subj, nil + } + + subj, err := f.frostfsid.GetSubjectExtended(addr) + if err != nil { + return nil, err + } + + if err = f.cache.PutSubject(addr, subj); err != nil { + f.log.Warn(logs.CouldntCacheSubject, zap.Error(err)) + } + + return subj, nil +} + +func (f *FrostFSID) GetUserAddress(namespace, name string) (string, error) { + userKey, err := f.getUserKey(namespace, name) + if err != nil { + return "", err + } + + return userKey.Address(), nil +} + +func (f *FrostFSID) GetUserKey(namespace, name string) (string, error) { + userKey, err := f.getUserKey(namespace, name) + if err != nil { + return "", err + } + + return hex.EncodeToString(userKey.Bytes()), nil +} + +func (f *FrostFSID) getUserKey(namespace, name string) (*keys.PublicKey, error) { + if userKey := f.cache.GetUserKey(namespace, name); userKey != nil { + return userKey, nil + } + + userKey, err := f.frostfsid.GetSubjectKeyByName(namespace, name) + if err != nil { + return nil, err + } + + if err = f.cache.PutUserKey(namespace, name, userKey); err != nil { + f.log.Warn(logs.CouldntCacheUserKey, zap.Error(err)) + } + + return userKey, nil +} diff --git a/internal/logs/logs.go b/internal/logs/logs.go index ec7f8c8..5a113a8 100644 --- a/internal/logs/logs.go +++ b/internal/logs/logs.go @@ -154,4 +154,7 @@ const ( FailedToWriteResponse = "failed to write response" WarnDuplicateAddress = "duplicate address" PolicyCouldntBeConvertedToNativeRules = "policy couldn't be converted to native rules, only s3 rules be applied" + CouldntCacheSubject = "couldn't cache subject info" + UserGroupsListIsEmpty = "user groups list is empty, subject not found" + CouldntCacheUserKey = "couldn't cache user key" )