[#259] Support contract based policies #276

Merged
alexvanin merged 1 commit from dkirillov/frostfs-s3-gw:feature/259-support_policy_from_contract into master 2023-12-13 14:34:39 +00:00
18 changed files with 428 additions and 43 deletions

View file

@ -20,6 +20,7 @@ This document outlines major changes between releases.
- Support control api to manage policies. See `control` config section (#258)
- Add `namespace` label to billing metrics (#271)
- Support policy-engine (#257)
- Support `policy` contract (#259)
### Changed
- Generalise config param `use_default_xmlns_for_complete_multipart` to `use_default_xmlns` so that use default xmlns for all requests (#221)

72
api/cache/policy.go vendored Normal file
View file

@ -0,0 +1,72 @@
package cache
import (
"fmt"
"time"
"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"
"go.uber.org/zap"
)
// MorphPolicyCache provides lru cache for listing policies stored in policy contract.
type MorphPolicyCache struct {
mbiryukova marked this conversation as resolved Outdated

Seems it's not for this cache

Seems it's not for this cache
cache gcache.Cache
logger *zap.Logger
}
type MorphPolicyCacheKey struct {
Target engine.Target
Name chain.Name
}
const (
// DefaultMorphPolicyCacheSize is a default maximum number of entries in cache.
DefaultMorphPolicyCacheSize = 1e4
// DefaultMorphPolicyCacheLifetime is a default lifetime of entries in cache.
DefaultMorphPolicyCacheLifetime = time.Minute
)
// DefaultMorphPolicyConfig returns new default cache expiration values.
func DefaultMorphPolicyConfig(logger *zap.Logger) *Config {
return &Config{
Size: DefaultMorphPolicyCacheSize,
Lifetime: DefaultMorphPolicyCacheLifetime,
Logger: logger,
}
}
// NewMorphPolicyCache creates an object of MorphPolicyCache.
func NewMorphPolicyCache(config *Config) *MorphPolicyCache {
gc := gcache.New(config.Size).LRU().Expiration(config.Lifetime).Build()
return &MorphPolicyCache{cache: gc, logger: config.Logger}
}
// Get returns a cached object. Returns nil if value is missing.
func (o *MorphPolicyCache) Get(key MorphPolicyCacheKey) []*chain.Chain {
entry, err := o.cache.Get(key)
if err != nil {
return nil
}
result, ok := entry.([]*chain.Chain)
if !ok {
o.logger.Warn(logs.InvalidCacheEntryType, zap.String("actual", fmt.Sprintf("%T", entry)),
zap.String("expected", fmt.Sprintf("%T", result)))
return nil
}
return result
}
// Put puts an object to cache.
func (o *MorphPolicyCache) Put(key MorphPolicyCacheKey, list []*chain.Chain) error {
return o.cache.Set(key, list)
}
// Delete deletes an object from cache.
func (o *MorphPolicyCache) Delete(key MorphPolicyCacheKey) bool {
return o.cache.Remove(key)
}

View file

@ -7,6 +7,7 @@ import (
"strings"
apiErr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy"
"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"
@ -51,7 +52,7 @@ func policyCheck(storage engine.ChainRouter, settings PolicySettings, domains []
reqInfo := GetReqInfo(r.Context())
target := engine.NewRequestTargetWithNamespace(settings.ResolveNamespaceAlias(reqInfo.Namespace))
st, found, err := storage.IsAllowed(chain.Ingress, target, req)
st, found, err := storage.IsAllowed(policy.S3ChainName, target, req)
if err != nil {
return 0, err
}

View file

@ -13,6 +13,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/internal/frostfs/policy"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
@ -164,7 +165,7 @@ func TestPolicyChecker(t *testing.T) {
}},
}
err := chiRouter.cfg.PolicyStorage.MorphRuleChainStorage().AddMorphRuleChain(chain.Ingress, engine.NamespaceTarget(namespace), ruleChain)
err := chiRouter.cfg.PolicyStorage.MorphRuleChainStorage().AddMorphRuleChain(policy.S3ChainName, engine.NamespaceTarget(namespace), ruleChain)
require.NoError(t, err)
// check we can access 'bucket' in default namespace

View file

@ -30,6 +30,8 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/frostfsid"
"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"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
@ -144,6 +146,7 @@ func newApp(ctx context.Context, log *Logger, v *viper.Viper) *App {
func (a *App) init(ctx context.Context) {
a.setRuntimeParameters()
a.initAPI(ctx)
a.initPolicyStorage(ctx)
a.initControlAPI()
a.initMetrics()
a.initFrostfsID(ctx)
@ -271,10 +274,10 @@ func (s *appSettings) DefaultPlacementPolicy(namespace string) netmap.PlacementP
func (s *appSettings) PlacementPolicy(namespace, constraint string) (netmap.PlacementPolicy, bool) {
s.mu.RLock()
policy, ok := s.namespaces[namespace].LocationConstraints[constraint]
placementPolicy, ok := s.namespaces[namespace].LocationConstraints[constraint]
s.mu.RUnlock()
return policy, ok
return placementPolicy, ok
}
func (s *appSettings) CopiesNumbers(namespace, constraint string) ([]uint32, bool) {
@ -417,12 +420,10 @@ func (a *App) initAPI(ctx context.Context) {
}
func (a *App) initControlAPI() {
a.policyStorage = inmemory.NewInMemoryLocalOverrides()
svc := controlSvc.New(
controlSvc.WithSettings(a.settings),
controlSvc.WithLogger(a.log),
controlSvc.WithChainStorage(a.policyStorage),
controlSvc.WithChainStorage(a.policyStorage.LocalStorage()),
)
a.controlAPI = grpc.NewServer()
@ -451,6 +452,30 @@ func (a *App) initFrostfsID(ctx context.Context) {
}
}
func (a *App) initPolicyStorage(ctx context.Context) {
if !a.cfg.GetBool(cfgPolicyEnabled) {
a.policyStorage = inmemory.NewInMemoryLocalOverrides()
return
}
policyClient, err := contract.New(ctx, contract.Config{
RPCAddress: a.cfg.GetString(cfgRPCEndpoint),
Contract: a.cfg.GetString(cfgPolicyContract),
Key: a.key,
})
if err != nil {
a.log.Fatal(logs.InitPolicyContractFailed, zap.Error(err))
}
cachedMorph := policy.NewCachedMorph(policy.CachedMorphConfig{
Morph: policyClient,
Cache: cache.NewMorphPolicyCache(getMorphPolicyCacheConfig(a.cfg, a.log)),
Log: a.log,
})
a.policyStorage = policy.NewStorage(cachedMorph)
}
func (a *App) initResolver() {
var err error
a.bucketResolver, err = resolver.NewBucketResolver(a.getResolverOrder(), a.getResolverConfig())
@ -919,6 +944,15 @@ func getAccessBoxCacheConfig(v *viper.Viper, l *zap.Logger) *cache.Config {
return cacheCfg
}
func getMorphPolicyCacheConfig(v *viper.Viper, l *zap.Logger) *cache.Config {
cacheCfg := cache.DefaultMorphPolicyConfig(l)
cacheCfg.Lifetime = fetchCacheLifetime(v, l, cfgMorphPolicyCacheLifetime, cacheCfg.Lifetime)
cacheCfg.Size = fetchCacheSize(v, l, cfgMorphPolicyCacheSize, cacheCfg.Size)
return cacheCfg
}
func (a *App) initHandler() {
var err error
a.api, err = handler.New(a.log, a.obj, a.nc, a.settings)

View file

@ -108,6 +108,8 @@ const ( // Settings.
cfgAccessBoxCacheSize = "cache.accessbox.size"
cfgAccessControlCacheLifetime = "cache.accesscontrol.lifetime"
cfgAccessControlCacheSize = "cache.accesscontrol.size"
cfgMorphPolicyCacheLifetime = "cache.morph_policy.lifetime"
cfgMorphPolicyCacheSize = "cache.morph_policy.size"
// NATS.
cfgEnableNATS = "nats.enabled"
@ -207,6 +209,10 @@ const ( // Settings.
cfgFrostfsIDEnabled = "frostfsid.enabled"
cfgFrostfsIDContract = "frostfsid.contract"
// Policy.
cfgPolicyEnabled = "policy.enabled"
cfgPolicyContract = "policy.contract"
// envPrefix is an environment variables prefix used for configuration.
envPrefix = "S3_GW"
)
@ -689,6 +695,10 @@ func newSettings() *viper.Viper {
v.SetDefault(cfgFrostfsIDContract, "frostfsid.frostfs")
v.SetDefault(cfgFrostfsIDEnabled, true)
// policy
mbiryukova marked this conversation as resolved Outdated

policy

policy
v.SetDefault(cfgPolicyContract, "policy.frostfs")
v.SetDefault(cfgPolicyEnabled, true)
// resolve
v.SetDefault(cfgResolveNamespaceHeader, defaultNamespaceHeader)

View file

@ -97,6 +97,9 @@ S3_GW_CACHE_ACCESSBOX_SIZE=100
# Cache which stores owner to cache operation mapping
S3_GW_CACHE_ACCESSCONTROL_LIFETIME=1m
S3_GW_CACHE_ACCESSCONTROL_SIZE=100000
# Cache which stores list of policy chains
S3_GW_CACHE_MORPH_POLICY_LIFETIME=1m
S3_GW_CACHE_MORPH_POLICY_SIZE=10000
# NATS
S3_GW_NATS_ENABLED=true
@ -195,6 +198,12 @@ S3_GW_FROSTFSID_ENABLED=true
# FrostfsID contract hash (LE) or name in NNS.
S3_GW_FROSTFSID_CONTRACT=frostfsid.frostfs
# Policy contract configuration. To enable this functionality the `rpc_endpoint` param must be also set.
# Enables using policies from Policy contract.
S3_GW_POLICY_ENABLED=true
# Policy contract hash (LE) or name in NNS.
S3_GW_POLICY_CONTRACT=policy.frostfs
# Namespaces configuration
S3_GW_NAMESPACES_CONFIG=namespaces.json

View file

@ -120,6 +120,10 @@ cache:
accesscontrol:
lifetime: 1m
size: 100000
# Cache which stores list of policy chains
morph_policy:
lifetime: 1m
size: 10000
nats:
enabled: true
@ -229,5 +233,12 @@ frostfsid:
# FrostfsID contract hash (LE) or name in NNS.
contract: frostfsid.frostfs
# Policy contract configuration. To enable this functionality the `rpc_endpoint` param must be also set.
policy:
# Enables using policies from Policy contract.
enabled: true
# Policy contract hash (LE) or name in NNS.
contract: policy.frostfs
namespaces:
config: namespaces.json

View file

@ -190,6 +190,7 @@ There are some custom types used for brevity:
| `features` | [Features configuration](#features-section) |
| `web` | [Web server configuration](#web-section) |
| `frostfsid` | [FrostfsID configuration](#frostfsid-section) |
| `policy` | [Policy contract configuration](#policy-section) |
| `namespaces` | [Namespaces configuration](#namespaces-section) |
### General section
@ -409,6 +410,9 @@ cache:
accesscontrol:
lifetime: 1m
size: 100000
morph_policy:
lifetime: 30s
size: 10000
```
| Parameter | Type | Default value | Description |
@ -420,6 +424,7 @@ cache:
| `system` | [Cache config](#cache-subsection) | `lifetime: 5m`<br>`size: 10000` | Cache for system objects in a bucket: bucket settings, notification configuration etc. |
| `accessbox` | [Cache config](#cache-subsection) | `lifetime: 10m`<br>`size: 100` | Cache which stores access box with tokens by its address. |
| `accesscontrol` | [Cache config](#cache-subsection) | `lifetime: 1m`<br>`size: 100000` | Cache which stores owner to cache operation mapping. |
| `morph_policy` | [Cache config](#cache-subsection) | `lifetime: 1m`<br>`size: 10000` | Cache which stores list of policy chains. |
#### `cache` subsection
@ -641,6 +646,21 @@ frostfsid:
| `enabled` | `bool` | no | true | Enables check that allow requests only users that is registered in FrostfsID contract. |
| `contract` | `string` | no | frostfsid.frostfs | FrostfsID contract hash (LE) or name in NNS. |
# `policy` section
Policy contract configuration. To enable this functionality the `rpc_endpoint` param must be also set.
```yaml
policy:
enabled: false
contract: policy.frostfs
```
| Parameter | Type | SIGHUP reload | Default value | Description |
|------------|----------|---------------|----------------|-------------------------------------------------------------------|
| `enabled` | `bool` | no | true | Enables using policies from Policy contract to check permissions. |
| `contract` | `string` | no | policy.frostfs | Policy contract hash (LE) or name in NNS. |
# `namespaces` section
Namespaces configuration.

2
go.mod
View file

@ -4,7 +4,7 @@ go 1.20
require (
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20231121085847-241a9f1ad0a4
git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231109143925-dd5919348da9
git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231129062201-a1b61d394958
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20230531082742-c97d21411eb6
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20231107114540-ab75edd70939
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20231205092054-2d4a9fc6dcb3

4
go.sum
View file

@ -38,8 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20231121085847-241a9f1ad0a4 h1:wjLfZ3WCt7qNGsQv+Jl0TXnmtg0uVk/jToKPFTBc/jo=
git.frostfs.info/TrueCloudLab/frostfs-api-go/v2 v2.16.1-0.20231121085847-241a9f1ad0a4/go.mod h1:uY0AYmCznjZdghDnAk7THFIe1Vlg531IxUcus7ZfUJI=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231109143925-dd5919348da9 h1:o14uxW6CLyweCdptexXn0ox0zGegdXc8lx8XauJ+b24=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231109143925-dd5919348da9/go.mod h1:rQWdsG18NaiFvkJpMguJev913KD/yleHaniRBkUyt0o=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231129062201-a1b61d394958 h1:X9yPizADIhD3K/gdKVCthlAnf9aQ3UJJGnZgIwwixRQ=
git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231129062201-a1b61d394958/go.mod h1:rQWdsG18NaiFvkJpMguJev913KD/yleHaniRBkUyt0o=
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0 h1:FxqFDhQYYgpe41qsIHVOcdzSVCB8JNSfPG7Uk4r2oSk=
git.frostfs.info/TrueCloudLab/frostfs-crypto v0.6.0/go.mod h1:RUIKZATQLJ+TaYQa60X2fTDwfuhMfm8Ar60bQ5fr+vU=
git.frostfs.info/TrueCloudLab/frostfs-observability v0.0.0-20230531082742-c97d21411eb6 h1:aGQ6QaAnTerQ5Dq5b2/f9DUQtSqPkZZ/bkMx/HKuLCo=

View file

@ -8,11 +8,9 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ns"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/util"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
@ -39,7 +37,7 @@ var (
// New creates new FrostfsID contract wrapper that implements auth.FrostFSID interface.
func New(ctx context.Context, cfg Config) (*FrostFSID, error) {
contractHash, err := fetchContractHash(cfg)
contractHash, err := util.ResolveContractHash(cfg.Contract, cfg.RPCAddress)
if err != nil {
return nil, fmt.Errorf("resolve frostfs contract hash: %w", err)
}
@ -79,25 +77,3 @@ func (f *FrostFSID) RegisterPublicKey(key *keys.PublicKey) error {
return nil
}
func fetchContractHash(cfg Config) (util.Uint160, error) {
if hash, err := util.Uint160DecodeStringLE(cfg.Contract); err == nil {
return hash, nil
}
splitName := strings.Split(cfg.Contract, ".")
if len(splitName) != 2 {
return util.Uint160{}, fmt.Errorf("invalid contract name: '%s'", cfg.Contract)
}
var domain container.Domain
domain.SetName(splitName[0])
domain.SetZone(splitName[1])
var nns ns.NNS
if err := nns.Dial(cfg.RPCAddress); err != nil {
return util.Uint160{}, fmt.Errorf("dial nns %s: %w", cfg.RPCAddress, err)
}
return nns.ResolveContractHash(domain)
}

View file

@ -0,0 +1,60 @@
package policy
import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
"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"
"go.uber.org/zap"
)
type CachedMorph struct {
morph engine.MorphRuleChainStorage
cache *cache.MorphPolicyCache
log *zap.Logger
}
type CachedMorphConfig struct {
Morph engine.MorphRuleChainStorage
Cache *cache.MorphPolicyCache
Log *zap.Logger
}
var _ engine.MorphRuleChainStorage = (*CachedMorph)(nil)
func NewCachedMorph(config CachedMorphConfig) *CachedMorph {
return &CachedMorph{
morph: config.Morph,
cache: config.Cache,
log: config.Log,
}
}
func (c *CachedMorph) AddMorphRuleChain(name chain.Name, target engine.Target, policyChain *chain.Chain) error {
c.cache.Delete(cache.MorphPolicyCacheKey{Target: target, Name: name})
return c.morph.AddMorphRuleChain(name, target, policyChain)
}
func (c *CachedMorph) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) error {
c.cache.Delete(cache.MorphPolicyCacheKey{Target: target, Name: name})
return c.morph.RemoveMorphRuleChain(name, target, chainID)
}
func (c *CachedMorph) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
key := cache.MorphPolicyCacheKey{Target: target, Name: name}
list := c.cache.Get(key)
if list != nil {
return list, nil
}
list, err := c.morph.ListMorphRuleChains(name, target)
if err != nil {
return nil, err
}
if err = c.cache.Put(key, list); err != nil {
c.log.Warn(logs.CouldntCacheListPolicyChains)
}
return list, nil
}

View file

@ -0,0 +1,112 @@
package contract
import (
"context"
"fmt"
"math/big"
policycontract "git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
policyclient "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/policy"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/util"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
type Client struct {
actor *actor.Actor
policyContract *policyclient.Contract
}
type Config struct {
// RPCAddress is an endpoint to connect to neo rpc.
RPCAddress string
// Contract is hash of contract or its name in NNS.
Contract string
// Key is used to interact with policy contract.
mbiryukova marked this conversation as resolved Outdated

policy contract

policy contract
// If this is nil than random key will be generated.
Key *keys.PrivateKey
}
var _ engine.MorphRuleChainStorage = (*Client)(nil)
// New creates new Policy contract wrapper.
func New(ctx context.Context, cfg Config) (*Client, error) {
contractHash, err := util.ResolveContractHash(cfg.Contract, cfg.RPCAddress)
if err != nil {
return nil, fmt.Errorf("resolve frostfs contract hash: %w", err)
}
key := cfg.Key
if key == nil {
if key, err = keys.NewPrivateKey(); err != nil {
return nil, fmt.Errorf("generate anon private key for policy: %w", err)
}
}
rpcCli, err := rpcclient.New(ctx, cfg.RPCAddress, rpcclient.Options{})
if err != nil {
return nil, fmt.Errorf("create policy rpc client: %w", err)
}
acc := wallet.NewAccountFromPrivateKey(key)
act, err := actor.NewSimple(rpcCli, acc)
if err != nil {
return nil, fmt.Errorf("create new actor: %w", err)
}
return &Client{
actor: act,
policyContract: policyclient.New(act, contractHash),
}, nil
}
func (c *Client) AddMorphRuleChain(name chain.Name, target engine.Target, policyChain *chain.Chain) error {
chainName := append([]byte(name), []byte(policyChain.ID)...)
_, err := c.actor.Wait(c.policyContract.AddChain(getKind(target), target.Name, chainName, policyChain.Bytes()))
return err
}
func (c *Client) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) error {
chainName := append([]byte(name), []byte(chainID)...)
_, err := c.actor.Wait(c.policyContract.RemoveChain(getKind(target), target.Name, chainName))
return err
}
func (c *Client) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
items, err := c.policyContract.ListChainsByPrefix(getKind(target), target.Name, []byte(name))
if err != nil {
return nil, err
}
res := make([]*chain.Chain, len(items))
for i, item := range items {
data, err := item.TryBytes()
if err != nil {
return nil, err
}
var policyChain chain.Chain
if err = policyChain.DecodeBytes(data); err != nil {
return nil, err
}
res[i] = &policyChain
}
return res, nil
}
func getKind(target engine.Target) *big.Int {
var kind int64 = policycontract.Container
if target.Type != engine.Container {
kind = policycontract.Namespace
}
return big.NewInt(kind)
}

View file

@ -0,0 +1,42 @@
package policy
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/pkg/resource"
)
const S3ChainName chain.Name = "s3"
type Storage struct {
router engine.ChainRouter
morph engine.MorphRuleChainStorage
local engine.LocalOverrideStorage
}
var _ engine.LocalOverrideEngine = (*Storage)(nil)
func NewStorage(morph engine.MorphRuleChainStorage) *Storage {
local := inmemory.NewInmemoryLocalStorage()
return &Storage{
router: engine.NewDefaultChainRouterWithLocalOverrides(morph, local),
morph: morph,
local: local,
}
}
func (s Storage) IsAllowed(name chain.Name, target engine.RequestTarget, r resource.Request) (status chain.Status, found bool, err error) {
return s.router.IsAllowed(name, target, r)
}
func (s Storage) MorphRuleChainStorage() engine.MorphRuleChainStorage {
return s.morph
}
func (s Storage) LocalStorage() engine.LocalOverrideStorage {
return s.local
}

View file

@ -0,0 +1,33 @@
package util
import (
"fmt"
"strings"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/ns"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// ResolveContractHash determine contract hash by resolving NNS name.
func ResolveContractHash(contractHash, rpcAddress string) (util.Uint160, error) {
if hash, err := util.Uint160DecodeStringLE(contractHash); err == nil {
return hash, nil
}
splitName := strings.Split(contractHash, ".")
if len(splitName) != 2 {
return util.Uint160{}, fmt.Errorf("invalid contract name: '%s'", contractHash)
}
var domain container.Domain
domain.SetName(splitName[0])
domain.SetZone(splitName[1])
var nns ns.NNS
if err := nns.Dial(rpcAddress); err != nil {
return util.Uint160{}, fmt.Errorf("dial nns %s: %w", rpcAddress, err)
}
return nns.ResolveContractHash(domain)
}

View file

@ -99,6 +99,7 @@ const (
CouldntCacheBucketSettings = "couldn't cache bucket settings" // Warn in ../../api/layer/cache.go
CouldntCacheCors = "couldn't cache cors" // Warn in ../../api/layer/cache.go
CouldntCacheNotificationConfiguration = "couldn't cache notification configuration" // Warn in ../../api/layer/cache.go
CouldntCacheListPolicyChains = "couldn't cache list policy chains" // Warn in ../../api/layer/cache.go
RequestEnd = "request end" // Info in ../../api/middleware/response.go
CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed = "couldn't receive access box for gate key, random key will be used" // Debug in ../../api/middleware/auth.go
FailedToPassAuthentication = "failed to pass authentication" // Error in ../../api/middleware/auth.go
@ -128,6 +129,7 @@ const (
AnonRequestSkipFrostfsIDValidation = "anon request, skip FrostfsID validation" // Debug in ../../api/middleware/auth.go
FrostfsIDValidationFailed = "FrostfsID validation failed" // Error in ../../api/middleware/auth.go
InitFrostfsIDContractFailed = "init frostfsid contract failed" // Fatal in ../../cmd/s3-gw/app.go
InitPolicyContractFailed = "init policy contract failed" // Fatal in ../../cmd/s3-gw/app.go
ControlAPIHealthcheck = "healthcheck request"
ControlAPIPutPolicies = "put policies request"
ControlAPIRemovePolicies = "remove policies request"

View file

@ -9,6 +9,7 @@ import (
"fmt"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/control"
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto"
@ -43,14 +44,14 @@ type cfg struct {
settings Settings
chainStorage engine.LocalOverrideEngine
chainStorage engine.LocalOverrideStorage
}
func defaultCfg() *cfg {
return &cfg{
log: zap.NewNop(),
settings: defaultSettings{},
chainStorage: inmemory.NewInMemoryLocalOverrides(),
chainStorage: inmemory.NewInmemoryLocalStorage(),
}
}
@ -84,7 +85,7 @@ func WithLogger(log *zap.Logger) Option {
}
// WithChainStorage returns option to set logger.
func WithChainStorage(chainStorage engine.LocalOverrideEngine) Option {
func WithChainStorage(chainStorage engine.LocalOverrideStorage) Option {
return func(c *cfg) {
c.chainStorage = chainStorage
}
@ -141,7 +142,7 @@ func (s *Server) putPolicy(data *control.PutPoliciesRequest_ChainData) error {
}
ns := s.settings.ResolveNamespaceAlias(data.GetNamespace())
if _, err := s.chainStorage.LocalStorage().AddOverride(chain.Ingress, engine.NamespaceTarget(ns), &overrideChain); err != nil {
if _, err := s.chainStorage.AddOverride(policy.S3ChainName, engine.NamespaceTarget(ns), &overrideChain); err != nil {
return status.Error(codes.Internal, err.Error())
}
@ -170,7 +171,7 @@ func (s *Server) RemovePolicies(_ context.Context, req *control.RemovePoliciesRe
func (s *Server) removePolicy(info *control.RemovePoliciesRequest_ChainInfo) error {
ns := s.settings.ResolveNamespaceAlias(info.GetNamespace())
err := s.chainStorage.LocalStorage().RemoveOverride(chain.Ingress, engine.NamespaceTarget(ns), chain.ID(info.GetChainID()))
err := s.chainStorage.RemoveOverride(policy.S3ChainName, engine.NamespaceTarget(ns), chain.ID(info.GetChainID()))
if err != nil {
if isNotFoundError(err) {
return status.Error(codes.NotFound, err.Error())
@ -193,7 +194,7 @@ func (s *Server) GetPolicy(_ context.Context, req *control.GetPolicyRequest) (*c
}
ns := s.settings.ResolveNamespaceAlias(req.GetBody().GetNamespace())
overrideChain, err := s.chainStorage.LocalStorage().GetOverride(chain.Ingress, engine.NamespaceTarget(ns), chain.ID(req.GetBody().GetChainID()))
overrideChain, err := s.chainStorage.GetOverride(policy.S3ChainName, engine.NamespaceTarget(ns), chain.ID(req.GetBody().GetChainID()))
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
@ -214,7 +215,7 @@ func (s *Server) ListPolicies(_ context.Context, req *control.ListPoliciesReques
}
ns := s.settings.ResolveNamespaceAlias(req.GetBody().GetNamespace())
chains, err := s.chainStorage.LocalStorage().ListOverrides(chain.Ingress, engine.NamespaceTarget(ns))
chains, err := s.chainStorage.ListOverrides(policy.S3ChainName, engine.NamespaceTarget(ns))
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}