forked from TrueCloudLab/frostfs-s3-gw
[#261] Make PutBucketPolicy handler use policy contract
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
6dbb07f0fa
commit
8273af8bf8
16 changed files with 594 additions and 202 deletions
|
@ -8,6 +8,7 @@ import (
|
|||
"encoding/json"
|
||||
stderrors "errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"sort"
|
||||
"strconv"
|
||||
|
@ -24,6 +25,9 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
engineiam "git.frostfs.info/TrueCloudLab/policy-engine/iam"
|
||||
"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"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
@ -485,19 +489,45 @@ func (h *handler) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request)
|
|||
return
|
||||
}
|
||||
|
||||
bucketACL, err := h.obj.GetBucketACL(r.Context(), bktInfo)
|
||||
resolvedNamespace := h.cfg.ResolveNamespaceAlias(reqInfo.Namespace)
|
||||
jsonPolicy, err := h.ape.GetPolicy(resolvedNamespace, bktInfo.CID)
|
||||
if err != nil {
|
||||
h.logAndSendError(w, "could not fetch bucket acl", reqInfo, err)
|
||||
if strings.Contains(err.Error(), "not found") {
|
||||
err = fmt.Errorf("%w: %s", errors.GetAPIError(errors.ErrNoSuchBucketPolicy), err.Error())
|
||||
}
|
||||
h.logAndSendError(w, "failed to get policy from storage", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
ast := tableToAst(bucketACL.EACL, reqInfo.BucketName)
|
||||
bktPolicy := astToPolicy(ast)
|
||||
|
||||
w.Header().Set(api.ContentType, "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
if err = json.NewEncoder(w).Encode(bktPolicy); err != nil {
|
||||
h.logAndSendError(w, "something went wrong", reqInfo, err)
|
||||
if _, err = w.Write(jsonPolicy); err != nil {
|
||||
h.logAndSendError(w, "write json policy to client", reqInfo, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
reqInfo := middleware.GetReqInfo(r.Context())
|
||||
|
||||
bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName)
|
||||
if err != nil {
|
||||
h.logAndSendError(w, "could not get bucket info", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
resolvedNamespace := h.cfg.ResolveNamespaceAlias(reqInfo.Namespace)
|
||||
|
||||
target := engine.NamespaceTarget(resolvedNamespace)
|
||||
chainID := getBucketChainID(bktInfo)
|
||||
if err = h.ape.RemoveChain(target, chainID); err != nil {
|
||||
h.logAndSendError(w, "failed to remove morph rule chain", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.ape.DeletePolicy(resolvedNamespace, bktInfo.CID); err != nil {
|
||||
h.logAndSendError(w, "failed to delete policy from storage", reqInfo, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -523,30 +553,52 @@ func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request)
|
|||
return
|
||||
}
|
||||
|
||||
token, err := getSessionTokenSetEACL(r.Context())
|
||||
jsonPolicy, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
h.logAndSendError(w, "couldn't get eacl token", reqInfo, err)
|
||||
h.logAndSendError(w, "read body", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
bktPolicy := &bucketPolicy{Bucket: reqInfo.BucketName}
|
||||
if err = json.NewDecoder(r.Body).Decode(bktPolicy); err != nil {
|
||||
var bktPolicy engineiam.Policy
|
||||
if err = json.Unmarshal(jsonPolicy, &bktPolicy); err != nil {
|
||||
h.logAndSendError(w, "could not parse bucket policy", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
astPolicy, err := policyToAst(bktPolicy)
|
||||
s3Chain, err := engineiam.ConvertToS3Chain(bktPolicy, h.frostfsid)
|
||||
if err != nil {
|
||||
h.logAndSendError(w, "could not translate policy to ast", reqInfo, err)
|
||||
h.logAndSendError(w, "could not convert s3 policy to chain policy", reqInfo, err)
|
||||
return
|
||||
}
|
||||
s3Chain.ID = getBucketChainID(bktInfo)
|
||||
|
||||
for _, rule := range s3Chain.Rules {
|
||||
for _, resource := range rule.Resources.Names {
|
||||
if reqInfo.BucketName != strings.Split(resource, "/")[0] {
|
||||
h.logAndSendError(w, "policy resource mismatched bucket", reqInfo, errors.GetAPIError(errors.ErrMalformedPolicy))
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resolvedNamespace := h.cfg.ResolveNamespaceAlias(reqInfo.Namespace)
|
||||
|
||||
target := engine.NamespaceTarget(resolvedNamespace)
|
||||
if err = h.ape.AddChain(target, s3Chain); err != nil {
|
||||
h.logAndSendError(w, "failed to add morph rule chain", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
if _, err = h.updateBucketACL(r, astPolicy, bktInfo, token); err != nil {
|
||||
h.logAndSendError(w, "could not update bucket acl", reqInfo, err)
|
||||
if err = h.ape.PutPolicy(resolvedNamespace, bktInfo.CID, jsonPolicy); err != nil {
|
||||
h.logAndSendError(w, "failed to save policy to storage", reqInfo, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getBucketChainID(bktInfo *data.BucketInfo) chain.ID {
|
||||
return chain.ID("bkt" + string(bktInfo.CID[:]))
|
||||
}
|
||||
|
||||
func parseACLHeaders(header http.Header, key *keys.PublicKey) (*AccessControlPolicy, error) {
|
||||
var err error
|
||||
acp := &AccessControlPolicy{Owner: Owner{
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session"
|
||||
engineiam "git.frostfs.info/TrueCloudLab/policy-engine/iam"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -1316,42 +1317,26 @@ func TestBucketPolicy(t *testing.T) {
|
|||
hc := prepareHandlerContext(t)
|
||||
bktName := "bucket-for-policy"
|
||||
|
||||
box, key := createAccessBox(t)
|
||||
createBucket(t, hc, bktName, box)
|
||||
createTestBucket(hc, bktName)
|
||||
|
||||
bktPolicy := getBucketPolicy(hc, bktName)
|
||||
for _, st := range bktPolicy.Statement {
|
||||
if st.Effect == "Allow" {
|
||||
require.Equal(t, hex.EncodeToString(key.PublicKey().Bytes()), st.Principal.CanonicalUser)
|
||||
require.Equal(t, []string{arnAwsPrefix + bktName}, st.Resource)
|
||||
} else {
|
||||
require.Equal(t, allUsersWildcard, st.Principal.AWS)
|
||||
require.Equal(t, "Deny", st.Effect)
|
||||
require.Equal(t, []string{arnAwsPrefix + bktName}, st.Resource)
|
||||
}
|
||||
}
|
||||
getBucketPolicy(hc, bktName, s3errors.ErrNoSuchBucketPolicy)
|
||||
|
||||
newPolicy := &bucketPolicy{
|
||||
Statement: []statement{{
|
||||
Effect: "Allow",
|
||||
Principal: principal{AWS: allUsersWildcard},
|
||||
Action: []string{s3GetObject},
|
||||
Resource: []string{arnAwsPrefix + "dummy"},
|
||||
newPolicy := engineiam.Policy{
|
||||
Statement: []engineiam.Statement{{
|
||||
Principal: map[engineiam.PrincipalType][]string{engineiam.Wildcard: {}},
|
||||
Effect: engineiam.DenyEffect,
|
||||
Action: engineiam.Action{"s3:PutObject"},
|
||||
Resource: engineiam.Resource{"arn:aws:s3:::test/*"},
|
||||
}},
|
||||
}
|
||||
|
||||
putBucketPolicy(hc, bktName, newPolicy, box, http.StatusInternalServerError)
|
||||
putBucketPolicy(hc, bktName, newPolicy, s3errors.ErrMalformedPolicy)
|
||||
|
||||
newPolicy.Statement[0].Resource[0] = arnAwsPrefix + bktName
|
||||
putBucketPolicy(hc, bktName, newPolicy, box, http.StatusOK)
|
||||
newPolicy.Statement[0].Resource[0] = arnAwsPrefix + bktName + "/*"
|
||||
putBucketPolicy(hc, bktName, newPolicy)
|
||||
|
||||
bktPolicy = getBucketPolicy(hc, bktName)
|
||||
for _, st := range bktPolicy.Statement {
|
||||
if st.Effect == "Allow" && st.Principal.AWS == allUsersWildcard {
|
||||
require.Equal(t, []string{arnAwsPrefix + bktName}, st.Resource)
|
||||
require.ElementsMatch(t, []string{s3GetObject, s3ListBucket}, st.Action)
|
||||
}
|
||||
}
|
||||
bktPolicy := getBucketPolicy(hc, bktName)
|
||||
require.Equal(t, newPolicy, bktPolicy)
|
||||
}
|
||||
|
||||
func TestBucketPolicyUnmarshal(t *testing.T) {
|
||||
|
@ -1411,9 +1396,7 @@ func TestPutBucketPolicy(t *testing.T) {
|
|||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [{
|
||||
"Principal": {
|
||||
"AWS": "*"
|
||||
},
|
||||
"Principal": "*",
|
||||
"Effect": "Deny",
|
||||
"Action": "s3:GetObject",
|
||||
"Resource": "arn:aws:s3:::bucket-for-policy/*"
|
||||
|
@ -1423,36 +1406,41 @@ func TestPutBucketPolicy(t *testing.T) {
|
|||
hc := prepareHandlerContext(t)
|
||||
bktName := "bucket-for-policy"
|
||||
|
||||
box, _ := createAccessBox(t)
|
||||
createBucket(t, hc, bktName, box)
|
||||
createTestBucket(hc, bktName)
|
||||
|
||||
w, r := prepareTestPayloadRequest(hc, bktName, "", bytes.NewReader([]byte(bktPolicy)))
|
||||
ctx := middleware.SetBoxData(r.Context(), box)
|
||||
r = r.WithContext(ctx)
|
||||
hc.Handler().PutBucketPolicyHandler(w, r)
|
||||
assertStatus(hc.t, w, http.StatusOK)
|
||||
}
|
||||
|
||||
func getBucketPolicy(hc *handlerContext, bktName string) *bucketPolicy {
|
||||
func getBucketPolicy(hc *handlerContext, bktName string, errCode ...s3errors.ErrorCode) engineiam.Policy {
|
||||
w, r := prepareTestRequest(hc, bktName, "", nil)
|
||||
hc.Handler().GetBucketPolicyHandler(w, r)
|
||||
|
||||
assertStatus(hc.t, w, http.StatusOK)
|
||||
policy := &bucketPolicy{}
|
||||
err := json.NewDecoder(w.Result().Body).Decode(policy)
|
||||
require.NoError(hc.t, err)
|
||||
var policy engineiam.Policy
|
||||
if len(errCode) == 0 {
|
||||
assertStatus(hc.t, w, http.StatusOK)
|
||||
err := json.NewDecoder(w.Result().Body).Decode(&policy)
|
||||
require.NoError(hc.t, err)
|
||||
} else {
|
||||
assertS3Error(hc.t, w, s3errors.GetAPIError(errCode[0]))
|
||||
}
|
||||
|
||||
return policy
|
||||
}
|
||||
|
||||
func putBucketPolicy(hc *handlerContext, bktName string, bktPolicy *bucketPolicy, box *accessbox.Box, status int) {
|
||||
func putBucketPolicy(hc *handlerContext, bktName string, bktPolicy engineiam.Policy, errCode ...s3errors.ErrorCode) {
|
||||
body, err := json.Marshal(bktPolicy)
|
||||
require.NoError(hc.t, err)
|
||||
|
||||
w, r := prepareTestPayloadRequest(hc, bktName, "", bytes.NewReader(body))
|
||||
ctx := middleware.SetBoxData(r.Context(), box)
|
||||
r = r.WithContext(ctx)
|
||||
hc.Handler().PutBucketPolicyHandler(w, r)
|
||||
assertStatus(hc.t, w, status)
|
||||
|
||||
if len(errCode) == 0 {
|
||||
assertStatus(hc.t, w, http.StatusOK)
|
||||
} else {
|
||||
assertS3Error(hc.t, w, s3errors.GetAPIError(errCode[0]))
|
||||
}
|
||||
}
|
||||
|
||||
func checkLastRecords(t *testing.T, tc *handlerContext, bktInfo *data.BucketInfo, action eacl.Action) {
|
||||
|
|
|
@ -12,7 +12,10 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/netmap"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
|
@ -22,6 +25,8 @@ type (
|
|||
obj layer.Client
|
||||
notificator Notificator
|
||||
cfg Config
|
||||
ape APE
|
||||
frostfsid FrostFSID
|
||||
}
|
||||
|
||||
Notificator interface {
|
||||
|
@ -42,18 +47,52 @@ type (
|
|||
IsResolveListAllow() bool
|
||||
BypassContentEncodingInChunks() bool
|
||||
MD5Enabled() bool
|
||||
ResolveNamespaceAlias(namespace string) string
|
||||
}
|
||||
|
||||
FrostFSID interface {
|
||||
GetUserAddress(account, user string) (string, error)
|
||||
}
|
||||
|
||||
// APE is Access Policy Engine that needs to save policy and acl info to different places.
|
||||
APE interface {
|
||||
MorphRuleChainStorage
|
||||
PolicyStorage
|
||||
}
|
||||
|
||||
// MorphRuleChainStorage is a similar to engine.MorphRuleChainStorage
|
||||
// but doesn't know anything about tx.
|
||||
MorphRuleChainStorage interface {
|
||||
AddChain(target engine.Target, c *chain.Chain) error
|
||||
RemoveChain(target engine.Target, chainID chain.ID) error
|
||||
ListChains(target engine.Target) ([]*chain.Chain, error)
|
||||
}
|
||||
|
||||
// PolicyStorage is interface to save intact initial user provided policy.
|
||||
PolicyStorage interface {
|
||||
PutPolicy(namespace string, cnrID cid.ID, policy []byte) error
|
||||
GetPolicy(namespace string, cnrID cid.ID) ([]byte, error)
|
||||
DeletePolicy(namespace string, cnrID cid.ID) error
|
||||
}
|
||||
|
||||
frostfsIDDisabled struct{}
|
||||
)
|
||||
|
||||
var _ api.Handler = (*handler)(nil)
|
||||
|
||||
// New creates new api.Handler using given logger and client.
|
||||
func New(log *zap.Logger, obj layer.Client, notificator Notificator, cfg Config) (api.Handler, error) {
|
||||
func New(log *zap.Logger, obj layer.Client, notificator Notificator, cfg Config, storage APE, ffsid FrostFSID) (api.Handler, error) {
|
||||
switch {
|
||||
case obj == nil:
|
||||
return nil, errors.New("empty FrostFS Object Layer")
|
||||
case log == nil:
|
||||
return nil, errors.New("empty logger")
|
||||
case storage == nil:
|
||||
return nil, errors.New("empty policy storage")
|
||||
}
|
||||
|
||||
if ffsid == nil {
|
||||
ffsid = frostfsIDDisabled{}
|
||||
}
|
||||
|
||||
if !cfg.NotificatorEnabled() {
|
||||
|
@ -66,10 +105,16 @@ func New(log *zap.Logger, obj layer.Client, notificator Notificator, cfg Config)
|
|||
log: log,
|
||||
obj: obj,
|
||||
cfg: cfg,
|
||||
ape: storage,
|
||||
notificator: notificator,
|
||||
frostfsid: ffsid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f frostfsIDDisabled) GetUserAddress(_, _ string) (string, error) {
|
||||
return "", errors.New("frostfsid disabled")
|
||||
}
|
||||
|
||||
// pickCopiesNumbers chooses the return values following this logic:
|
||||
// 1) array of copies numbers sent in request's header has the highest priority.
|
||||
// 2) array of copies numbers with corresponding location constraint provided in the config file.
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/xml"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -26,9 +27,12 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
||||
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"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/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type handlerContext struct {
|
||||
|
@ -116,6 +120,10 @@ func (c *configMock) MD5Enabled() bool {
|
|||
return c.md5Enabled
|
||||
}
|
||||
|
||||
func (c *configMock) ResolveNamespaceAlias(ns string) string {
|
||||
return ns
|
||||
}
|
||||
|
||||
func prepareHandlerContext(t *testing.T) *handlerContext {
|
||||
return prepareHandlerContextBase(t, false)
|
||||
}
|
||||
|
@ -167,6 +175,7 @@ func prepareHandlerContextBase(t *testing.T, minCache bool) *handlerContext {
|
|||
log: l,
|
||||
obj: layer.NewLayer(l, tp, layerCfg),
|
||||
cfg: cfg,
|
||||
ape: newAPEMock(),
|
||||
}
|
||||
|
||||
return &handlerContext{
|
||||
|
@ -199,6 +208,60 @@ func getMinCacheConfig(logger *zap.Logger) *layer.CachesConfig {
|
|||
}
|
||||
}
|
||||
|
||||
type apeMock struct {
|
||||
chainMap map[engine.Target][]*chain.Chain
|
||||
policyMap map[string][]byte
|
||||
}
|
||||
|
||||
func newAPEMock() *apeMock {
|
||||
return &apeMock{
|
||||
chainMap: map[engine.Target][]*chain.Chain{},
|
||||
policyMap: map[string][]byte{},
|
||||
}
|
||||
}
|
||||
|
||||
func (a *apeMock) AddChain(target engine.Target, c *chain.Chain) error {
|
||||
list := a.chainMap[target]
|
||||
|
||||
ind := slices.IndexFunc(list, func(item *chain.Chain) bool { return item.ID == c.ID })
|
||||
if ind != -1 {
|
||||
list[ind] = c
|
||||
} else {
|
||||
list = append(list, c)
|
||||
}
|
||||
|
||||
a.chainMap[target] = list
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *apeMock) RemoveChain(target engine.Target, chainID chain.ID) error {
|
||||
a.chainMap[target] = slices.DeleteFunc(a.chainMap[target], func(item *chain.Chain) bool { return item.ID == chainID })
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *apeMock) ListChains(target engine.Target) ([]*chain.Chain, error) {
|
||||
return a.chainMap[target], nil
|
||||
}
|
||||
|
||||
func (a *apeMock) PutPolicy(namespace string, cnrID cid.ID, policy []byte) error {
|
||||
a.policyMap[namespace+cnrID.EncodeToString()] = policy
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *apeMock) GetPolicy(namespace string, cnrID cid.ID) ([]byte, error) {
|
||||
policy, ok := a.policyMap[namespace+cnrID.EncodeToString()]
|
||||
if !ok {
|
||||
return nil, errors.New("not found")
|
||||
}
|
||||
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
func (a *apeMock) DeletePolicy(namespace string, cnrID cid.ID) error {
|
||||
delete(a.policyMap, namespace+cnrID.EncodeToString())
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewTreeServiceMock(t *testing.T) *tree.Tree {
|
||||
memCli, err := tree.NewTreeServiceClientMemory()
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -7,10 +7,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||
)
|
||||
|
||||
func (h *handler) DeleteBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
h.logAndSendError(w, "not supported", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported))
|
||||
}
|
||||
|
||||
func (h *handler) DeleteBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
h.logAndSendError(w, "not supported", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported))
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ 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"
|
||||
|
@ -52,7 +51,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(policy.S3ChainName, target, req)
|
||||
st, found, err := storage.IsAllowed(chain.S3, target, req)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
|
|
@ -111,7 +111,7 @@ type Config struct {
|
|||
// FrostfsID optional. If nil middleware.FrostfsIDValidation won't be attached.
|
||||
FrostfsID s3middleware.FrostFSID
|
||||
|
||||
PolicyStorage engine.LocalOverrideEngine
|
||||
PolicyChecker engine.ChainRouter
|
||||
}
|
||||
|
||||
func NewRouter(cfg Config) *chi.Mux {
|
||||
|
@ -130,8 +130,8 @@ func NewRouter(cfg Config) *chi.Mux {
|
|||
api.Use(s3middleware.FrostfsIDValidation(cfg.FrostfsID, cfg.Log))
|
||||
}
|
||||
|
||||
if cfg.PolicyStorage != nil {
|
||||
api.Use(s3middleware.PolicyCheck(cfg.PolicyStorage, cfg.MiddlewareSettings, cfg.Domains, cfg.Log))
|
||||
if cfg.PolicyChecker != nil {
|
||||
api.Use(s3middleware.PolicyCheck(cfg.PolicyChecker, cfg.MiddlewareSettings, cfg.Domains, cfg.Log))
|
||||
}
|
||||
|
||||
defaultRouter := chi.NewRouter()
|
||||
|
@ -320,7 +320,7 @@ func bucketRouter(h Handler, log *zap.Logger) chi.Router {
|
|||
Handler(named(s3middleware.DeleteBucketTaggingOperation, h.DeleteBucketTaggingHandler))).
|
||||
Add(NewFilter().
|
||||
Queries(s3middleware.PolicyQuery).
|
||||
Handler(named(s3middleware.PutBucketPolicyOperation, h.PutBucketPolicyHandler))).
|
||||
Handler(named(s3middleware.DeleteBucketPolicyOperation, h.DeleteBucketPolicyHandler))).
|
||||
Add(NewFilter().
|
||||
Queries(s3middleware.LifecycleQuery).
|
||||
Handler(named(s3middleware.PutBucketLifecycleOperation, h.PutBucketLifecycleHandler))).
|
||||
|
|
|
@ -27,6 +27,7 @@ type routerMock struct {
|
|||
router *chi.Mux
|
||||
cfg Config
|
||||
middlewareSettings *middlewareSettingsMock
|
||||
policyChecker engine.LocalOverrideEngine
|
||||
}
|
||||
|
||||
func (m *routerMock) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -35,6 +36,7 @@ func (m *routerMock) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
func prepareRouter(t *testing.T) *routerMock {
|
||||
middlewareSettings := &middlewareSettingsMock{}
|
||||
policyChecker := inmemory.NewInMemoryLocalOverrides()
|
||||
|
||||
cfg := Config{
|
||||
Throttle: middleware.ThrottleOpts{
|
||||
|
@ -46,12 +48,13 @@ func prepareRouter(t *testing.T) *routerMock {
|
|||
Log: zaptest.NewLogger(t),
|
||||
Metrics: &metrics.AppMetrics{},
|
||||
MiddlewareSettings: middlewareSettings,
|
||||
PolicyStorage: inmemory.NewInMemoryLocalOverrides(),
|
||||
PolicyChecker: policyChecker,
|
||||
}
|
||||
return &routerMock{
|
||||
router: NewRouter(cfg),
|
||||
cfg: cfg,
|
||||
middlewareSettings: middlewareSettings,
|
||||
policyChecker: policyChecker,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -164,7 +167,7 @@ func TestPolicyChecker(t *testing.T) {
|
|||
}},
|
||||
}
|
||||
|
||||
_, _, err := chiRouter.cfg.PolicyStorage.MorphRuleChainStorage().AddMorphRuleChain(chain.S3, engine.NamespaceTarget(namespace), ruleChain)
|
||||
_, _, err := chiRouter.policyChecker.MorphRuleChainStorage().AddMorphRuleChain(chain.S3, engine.NamespaceTarget(namespace), ruleChain)
|
||||
require.NoError(t, err)
|
||||
|
||||
// check we can access 'bucket' in default namespace
|
||||
|
|
|
@ -43,8 +43,6 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||
treepool "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -70,7 +68,7 @@ type (
|
|||
|
||||
frostfsid *frostfsid.FrostFSID
|
||||
|
||||
policyStorage engine.LocalOverrideEngine
|
||||
policyStorage *policy.Storage
|
||||
|
||||
servers []Server
|
||||
|
||||
|
@ -144,11 +142,11 @@ 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)
|
||||
a.initPolicyStorage(ctx)
|
||||
a.initAPI(ctx)
|
||||
a.initMetrics()
|
||||
a.initControlAPI()
|
||||
a.initServers(ctx)
|
||||
a.initTracing(ctx)
|
||||
}
|
||||
|
@ -452,27 +450,29 @@ func (a *App) initFrostfsID(ctx context.Context) {
|
|||
}
|
||||
|
||||
func (a *App) initPolicyStorage(ctx context.Context) {
|
||||
if !a.cfg.GetBool(cfgPolicyEnabled) {
|
||||
a.policyStorage = inmemory.NewInMemoryLocalOverrides()
|
||||
return
|
||||
var (
|
||||
err error
|
||||
policyContract policy.Contract
|
||||
)
|
||||
|
||||
if a.cfg.GetBool(cfgPolicyEnabled) {
|
||||
policyContract, 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))
|
||||
}
|
||||
} else {
|
||||
policyContract = contract.NewInMemoryContract()
|
||||
}
|
||||
|
||||
policyClient, err := contract.New(ctx, contract.Config{
|
||||
RPCAddress: a.cfg.GetString(cfgRPCEndpoint),
|
||||
Contract: a.cfg.GetString(cfgPolicyContract),
|
||||
Key: a.key,
|
||||
a.policyStorage = policy.NewStorage(policy.StorageConfig{
|
||||
Contract: policyContract,
|
||||
Cache: cache.NewMorphPolicyCache(getMorphPolicyCacheConfig(a.cfg, a.log)),
|
||||
Log: a.log,
|
||||
})
|
||||
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() {
|
||||
|
@ -672,7 +672,7 @@ func (a *App) Serve(ctx context.Context) {
|
|||
Domains: domains,
|
||||
|
||||
MiddlewareSettings: a.settings,
|
||||
PolicyStorage: a.policyStorage,
|
||||
PolicyChecker: a.policyStorage,
|
||||
}
|
||||
|
||||
// We cannot make direct assignment if frostfsid.FrostFSID is nil
|
||||
|
@ -943,8 +943,16 @@ func getMorphPolicyCacheConfig(v *viper.Viper, l *zap.Logger) *cache.Config {
|
|||
}
|
||||
|
||||
func (a *App) initHandler() {
|
||||
var err error
|
||||
a.api, err = handler.New(a.log, a.obj, a.nc, a.settings)
|
||||
var (
|
||||
err error
|
||||
ffsid handler.FrostFSID
|
||||
)
|
||||
|
||||
if a.frostfsid != nil {
|
||||
ffsid = a.frostfsid
|
||||
}
|
||||
|
||||
a.api, err = handler.New(a.log, a.obj, a.nc, a.settings, a.policyStorage, ffsid)
|
||||
if err != nil {
|
||||
a.log.Fatal(logs.CouldNotInitializeAPIHandler, zap.Error(err))
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@ import (
|
|||
"strings"
|
||||
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/util"
|
||||
frostfsutil "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/wallet"
|
||||
|
@ -33,11 +34,12 @@ type Config struct {
|
|||
var (
|
||||
_ middleware.FrostFSID = (*FrostFSID)(nil)
|
||||
_ authmate.FrostFSID = (*FrostFSID)(nil)
|
||||
_ handler.FrostFSID = (*FrostFSID)(nil)
|
||||
)
|
||||
|
||||
// New creates new FrostfsID contract wrapper that implements auth.FrostFSID interface.
|
||||
func New(ctx context.Context, cfg Config) (*FrostFSID, error) {
|
||||
contractHash, err := util.ResolveContractHash(cfg.Contract, cfg.RPCAddress)
|
||||
contractHash, err := frostfsutil.ResolveContractHash(cfg.Contract, cfg.RPCAddress)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve frostfs contract hash: %w", err)
|
||||
}
|
||||
|
@ -77,3 +79,12 @@ func (f *FrostFSID) RegisterPublicKey(key *keys.PublicKey) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FrostFSID) GetUserAddress(namespace, name string) (string, error) {
|
||||
key, err := f.cli.GetSubjectKeyByName(namespace, name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return key.Address(), nil
|
||||
}
|
||||
|
|
|
@ -1,61 +0,0 @@
|
|||
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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"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) (util.Uint256, uint32, 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) (util.Uint256, uint32, 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
|
||||
}
|
|
@ -7,9 +7,8 @@ import (
|
|||
|
||||
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/policy"
|
||||
frostfsutil "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"
|
||||
|
@ -34,7 +33,7 @@ type Config struct {
|
|||
Key *keys.PrivateKey
|
||||
}
|
||||
|
||||
var _ engine.MorphRuleChainStorage = (*Client)(nil)
|
||||
var _ policy.Contract = (*Client)(nil)
|
||||
|
||||
// New creates new Policy contract wrapper.
|
||||
func New(ctx context.Context, cfg Config) (*Client, error) {
|
||||
|
@ -67,45 +66,36 @@ func New(ctx context.Context, cfg Config) (*Client, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) AddMorphRuleChain(name chain.Name, target engine.Target, policyChain *chain.Chain) (util.Uint256, uint32, error) {
|
||||
chainName := append([]byte(name), []byte(policyChain.ID)...)
|
||||
return c.policyContract.AddChain(getKind(target), target.Name, chainName, policyChain.Bytes())
|
||||
func (c *Client) AddChain(kind policycontract.Kind, entity string, name []byte, chain []byte) (util.Uint256, uint32, error) {
|
||||
return c.policyContract.AddChain(big.NewInt(int64(kind)), entity, name, chain)
|
||||
}
|
||||
|
||||
func (c *Client) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) (util.Uint256, uint32, error) {
|
||||
chainName := append([]byte(name), []byte(chainID)...)
|
||||
return c.policyContract.RemoveChain(getKind(target), target.Name, chainName)
|
||||
func (c *Client) GetChain(kind policycontract.Kind, entity string, name []byte) ([]byte, error) {
|
||||
return c.policyContract.GetChain(big.NewInt(int64(kind)), entity, name)
|
||||
}
|
||||
|
||||
func (c *Client) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) {
|
||||
items, err := c.policyContract.ListChainsByPrefix(getKind(target), target.Name, []byte(name))
|
||||
func (c *Client) RemoveChain(kind policycontract.Kind, entity string, name []byte) (util.Uint256, uint32, error) {
|
||||
return c.policyContract.RemoveChain(big.NewInt(int64(kind)), entity, name)
|
||||
}
|
||||
|
||||
func (c *Client) ListChains(kind policycontract.Kind, entity string, name []byte) ([][]byte, error) {
|
||||
items, err := c.policyContract.ListChainsByPrefix(big.NewInt(int64(kind)), entity, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := make([]*chain.Chain, len(items))
|
||||
res := make([][]byte, len(items))
|
||||
for i, item := range items {
|
||||
data, err := item.TryBytes()
|
||||
res[i], 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)
|
||||
func (c *Client) Wait(tx util.Uint256, vub uint32, err error) error {
|
||||
_, err = c.actor.Wait(tx, vub, err)
|
||||
return err
|
||||
}
|
||||
|
|
97
internal/frostfs/policy/contract/inmemory.go
Normal file
97
internal/frostfs/policy/contract/inmemory.go
Normal file
|
@ -0,0 +1,97 @@
|
|||
package contract
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
policycontract "git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
type InMemoryContract struct {
|
||||
iamChains *syncedMap
|
||||
containerChains *syncedMap
|
||||
namespaceChains *syncedMap
|
||||
}
|
||||
|
||||
type syncedMap struct {
|
||||
mu sync.RWMutex
|
||||
data map[string][]byte
|
||||
}
|
||||
|
||||
var _ policy.Contract = (*InMemoryContract)(nil)
|
||||
|
||||
var ErrChainNotFound = errors.New("chain not found")
|
||||
|
||||
// NewInMemoryContract creates new inmemory Policy contract wrapper.
|
||||
func NewInMemoryContract() *InMemoryContract {
|
||||
return &InMemoryContract{
|
||||
iamChains: &syncedMap{data: map[string][]byte{}},
|
||||
containerChains: &syncedMap{data: map[string][]byte{}},
|
||||
namespaceChains: &syncedMap{data: map[string][]byte{}},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *InMemoryContract) AddChain(kind policycontract.Kind, entity string, name []byte, chain []byte) (util.Uint256, uint32, error) {
|
||||
syncMap := c.getMap(kind)
|
||||
syncMap.mu.Lock()
|
||||
syncMap.data[entity+string(name)] = chain
|
||||
syncMap.mu.Unlock()
|
||||
|
||||
return util.Uint256{}, 0, nil
|
||||
}
|
||||
|
||||
func (c *InMemoryContract) GetChain(kind policycontract.Kind, entity string, name []byte) ([]byte, error) {
|
||||
syncMap := c.getMap(kind)
|
||||
syncMap.mu.RLock()
|
||||
defer syncMap.mu.RUnlock()
|
||||
|
||||
val, ok := syncMap.data[entity+string(name)]
|
||||
if !ok {
|
||||
return nil, ErrChainNotFound
|
||||
}
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c *InMemoryContract) RemoveChain(kind policycontract.Kind, entity string, name []byte) (util.Uint256, uint32, error) {
|
||||
syncMap := c.getMap(kind)
|
||||
syncMap.mu.Lock()
|
||||
delete(syncMap.data, entity+string(name))
|
||||
syncMap.mu.Unlock()
|
||||
|
||||
return util.Uint256{}, 0, nil
|
||||
}
|
||||
|
||||
func (c *InMemoryContract) ListChains(kind policycontract.Kind, entity string, name []byte) ([][]byte, error) {
|
||||
syncMap := c.getMap(kind)
|
||||
syncMap.mu.RLock()
|
||||
defer syncMap.mu.RUnlock()
|
||||
|
||||
var res [][]byte
|
||||
for key, val := range syncMap.data {
|
||||
if strings.HasPrefix(key, entity+string(name)) {
|
||||
res = append(res, val)
|
||||
}
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *InMemoryContract) Wait(_ util.Uint256, _ uint32, err error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *InMemoryContract) getMap(kind policycontract.Kind) *syncedMap {
|
||||
switch kind {
|
||||
case policycontract.IAM:
|
||||
return c.iamChains
|
||||
case policycontract.Container:
|
||||
return c.containerChains
|
||||
case policycontract.Namespace:
|
||||
return c.namespaceChains
|
||||
default:
|
||||
return &syncedMap{data: map[string][]byte{}}
|
||||
}
|
||||
}
|
46
internal/frostfs/policy/morph_policy_storage.go
Normal file
46
internal/frostfs/policy/morph_policy_storage.go
Normal file
|
@ -0,0 +1,46 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
policycontract "git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type MorphPolicyStorage struct {
|
||||
contract Contract
|
||||
}
|
||||
|
||||
type MorphPolicyStorageConfig struct {
|
||||
Contract Contract
|
||||
Log *zap.Logger
|
||||
}
|
||||
|
||||
var _ handler.PolicyStorage = (*MorphPolicyStorage)(nil)
|
||||
|
||||
const policyStoragePrefix = 'b'
|
||||
|
||||
func NewMorphPolicyStorage(config *MorphPolicyStorageConfig) *MorphPolicyStorage {
|
||||
return &MorphPolicyStorage{
|
||||
contract: config.Contract,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MorphPolicyStorage) PutPolicy(namespace string, cnrID cid.ID, policy []byte) error {
|
||||
name := getPolicyStorageName(cnrID)
|
||||
return c.contract.Wait(c.contract.AddChain(policycontract.IAM, namespace, name, policy))
|
||||
}
|
||||
|
||||
func (c *MorphPolicyStorage) GetPolicy(namespace string, cnrID cid.ID) ([]byte, error) {
|
||||
name := getPolicyStorageName(cnrID)
|
||||
return c.contract.GetChain(policycontract.IAM, namespace, name)
|
||||
}
|
||||
|
||||
func (c *MorphPolicyStorage) DeletePolicy(namespace string, cnrID cid.ID) error {
|
||||
name := getPolicyStorageName(cnrID)
|
||||
return c.contract.Wait(c.contract.RemoveChain(policycontract.IAM, namespace, name))
|
||||
}
|
||||
|
||||
func getPolicyStorageName(cnrID cid.ID) []byte {
|
||||
return append([]byte{policyStoragePrefix}, cnrID[:]...)
|
||||
}
|
102
internal/frostfs/policy/morph_rule_chain_storage.go
Normal file
102
internal/frostfs/policy/morph_rule_chain_storage.go
Normal file
|
@ -0,0 +1,102 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
policycontract "git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
|
||||
"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/logs"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type MorphRuleChainStorage struct {
|
||||
contract Contract
|
||||
cache *cache.MorphPolicyCache
|
||||
log *zap.Logger
|
||||
}
|
||||
|
||||
type MorphRuleChainStorageConfig struct {
|
||||
Contract Contract
|
||||
Cache *cache.MorphPolicyCache
|
||||
Log *zap.Logger
|
||||
}
|
||||
|
||||
var (
|
||||
_ engine.MorphRuleChainStorage = (*MorphRuleChainStorage)(nil)
|
||||
_ handler.MorphRuleChainStorage = (*MorphRuleChainStorage)(nil)
|
||||
)
|
||||
|
||||
func NewMorphRuleChainStorage(config *MorphRuleChainStorageConfig) *MorphRuleChainStorage {
|
||||
return &MorphRuleChainStorage{
|
||||
contract: config.Contract,
|
||||
cache: config.Cache,
|
||||
log: config.Log,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *MorphRuleChainStorage) AddChain(target engine.Target, policyChain *chain.Chain) error {
|
||||
return c.contract.Wait(c.AddMorphRuleChain(chain.S3, target, policyChain))
|
||||
}
|
||||
|
||||
func (c *MorphRuleChainStorage) RemoveChain(target engine.Target, chainID chain.ID) error {
|
||||
return c.contract.Wait(c.RemoveMorphRuleChain(chain.S3, target, chainID))
|
||||
}
|
||||
|
||||
func (c *MorphRuleChainStorage) ListChains(target engine.Target) ([]*chain.Chain, error) {
|
||||
return c.ListMorphRuleChains(chain.S3, target)
|
||||
}
|
||||
|
||||
func (c *MorphRuleChainStorage) AddMorphRuleChain(name chain.Name, target engine.Target, policyChain *chain.Chain) (util.Uint256, uint32, error) {
|
||||
c.cache.Delete(cache.MorphPolicyCacheKey{Target: target, Name: name})
|
||||
return c.contract.AddChain(getKind(target), target.Name, getName(name, policyChain.ID), policyChain.Bytes())
|
||||
}
|
||||
|
||||
func (c *MorphRuleChainStorage) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) (util.Uint256, uint32, error) {
|
||||
c.cache.Delete(cache.MorphPolicyCacheKey{Target: target, Name: name})
|
||||
return c.contract.RemoveChain(getKind(target), target.Name, getName(name, chainID))
|
||||
}
|
||||
|
||||
func (c *MorphRuleChainStorage) 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
|
||||
}
|
||||
|
||||
listChains, err := c.contract.ListChains(getKind(target), target.Name, []byte(name))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list = make([]*chain.Chain, len(listChains))
|
||||
for i, listChain := range listChains {
|
||||
var item chain.Chain
|
||||
if err = json.Unmarshal(listChain, &item); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal chain: %w", err)
|
||||
}
|
||||
list[i] = &item
|
||||
}
|
||||
|
||||
if err = c.cache.Put(key, list); err != nil {
|
||||
c.log.Warn(logs.CouldntCacheListPolicyChains)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func getKind(target engine.Target) policycontract.Kind {
|
||||
var kind policycontract.Kind = policycontract.Container
|
||||
if target.Type != engine.Container {
|
||||
kind = policycontract.Namespace
|
||||
}
|
||||
|
||||
return kind
|
||||
}
|
||||
func getName(name chain.Name, chainID chain.ID) []byte {
|
||||
return append([]byte(name), []byte(chainID)...)
|
||||
}
|
|
@ -1,42 +1,95 @@
|
|||
package policy
|
||||
|
||||
import (
|
||||
policycontract "git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler"
|
||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
||||
"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"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const S3ChainName chain.Name = "s3"
|
||||
|
||||
type Storage struct {
|
||||
router engine.ChainRouter
|
||||
|
||||
morph engine.MorphRuleChainStorage
|
||||
morph handler.MorphRuleChainStorage
|
||||
|
||||
local engine.LocalOverrideStorage
|
||||
|
||||
policy handler.PolicyStorage
|
||||
}
|
||||
|
||||
var _ engine.LocalOverrideEngine = (*Storage)(nil)
|
||||
type StorageConfig struct {
|
||||
Contract Contract
|
||||
Cache *cache.MorphPolicyCache
|
||||
Log *zap.Logger
|
||||
}
|
||||
|
||||
func NewStorage(morph engine.MorphRuleChainStorage) *Storage {
|
||||
type Contract interface {
|
||||
AddChain(kind policycontract.Kind, entity string, name []byte, chain []byte) (util.Uint256, uint32, error)
|
||||
GetChain(kind policycontract.Kind, entity string, name []byte) ([]byte, error)
|
||||
RemoveChain(kind policycontract.Kind, entity string, name []byte) (util.Uint256, uint32, error)
|
||||
ListChains(kind policycontract.Kind, entity string, name []byte) ([][]byte, error)
|
||||
Wait(tx util.Uint256, vub uint32, err error) error
|
||||
}
|
||||
|
||||
var _ handler.APE = (*Storage)(nil)
|
||||
|
||||
func NewStorage(cfg StorageConfig) *Storage {
|
||||
// todo use thread safe inmemory https://git.frostfs.info/TrueCloudLab/policy-engine/issues/35
|
||||
local := inmemory.NewInmemoryLocalStorage()
|
||||
|
||||
morph := NewMorphRuleChainStorage(&MorphRuleChainStorageConfig{
|
||||
Contract: cfg.Contract,
|
||||
Cache: cfg.Cache,
|
||||
Log: cfg.Log,
|
||||
})
|
||||
|
||||
policyStorage := NewMorphPolicyStorage(&MorphPolicyStorageConfig{
|
||||
Contract: cfg.Contract,
|
||||
Log: cfg.Log,
|
||||
})
|
||||
|
||||
return &Storage{
|
||||
router: engine.NewDefaultChainRouterWithLocalOverrides(morph, local),
|
||||
morph: morph,
|
||||
local: local,
|
||||
policy: policyStorage,
|
||||
}
|
||||
}
|
||||
|
||||
func (s Storage) IsAllowed(name chain.Name, target engine.RequestTarget, r resource.Request) (status chain.Status, found bool, err error) {
|
||||
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 {
|
||||
func (s *Storage) LocalStorage() engine.LocalOverrideStorage {
|
||||
return s.local
|
||||
}
|
||||
|
||||
func (s *Storage) AddChain(target engine.Target, policyChain *chain.Chain) error {
|
||||
return s.morph.AddChain(target, policyChain)
|
||||
}
|
||||
|
||||
func (s *Storage) RemoveChain(target engine.Target, chainID chain.ID) error {
|
||||
return s.morph.RemoveChain(target, chainID)
|
||||
}
|
||||
|
||||
func (s *Storage) ListChains(target engine.Target) ([]*chain.Chain, error) {
|
||||
return s.morph.ListChains(target)
|
||||
}
|
||||
|
||||
func (s *Storage) PutPolicy(namespace string, cnrID cid.ID, policy []byte) error {
|
||||
return s.policy.PutPolicy(namespace, cnrID, policy)
|
||||
}
|
||||
|
||||
func (s *Storage) GetPolicy(namespace string, cnrID cid.ID) ([]byte, error) {
|
||||
return s.policy.GetPolicy(namespace, cnrID)
|
||||
}
|
||||
|
||||
func (s *Storage) DeletePolicy(namespace string, cnrID cid.ID) error {
|
||||
return s.policy.DeletePolicy(namespace, cnrID)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue