feature/261-update_put-bucket-policy_to_use_contract #279
19 changed files with 604 additions and 292 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{
|
||||
|
@ -1134,73 +1186,6 @@ func resourceInfoFromName(name, bucketName string) resourceInfo {
|
|||
return resInfo
|
||||
}
|
||||
|
||||
func astToPolicy(ast *ast) *bucketPolicy {
|
||||
bktPolicy := &bucketPolicy{}
|
||||
|
||||
for _, resource := range ast.Resources {
|
||||
allowed, denied := triageOperations(resource.Operations)
|
||||
handleResourceOperations(bktPolicy, allowed, eacl.ActionAllow, resource.Name())
|
||||
handleResourceOperations(bktPolicy, denied, eacl.ActionDeny, resource.Name())
|
||||
}
|
||||
|
||||
return bktPolicy
|
||||
}
|
||||
|
||||
func handleResourceOperations(bktPolicy *bucketPolicy, list []*astOperation, eaclAction eacl.Action, resourceName string) {
|
||||
userOpsMap := make(map[string][]eacl.Operation)
|
||||
|
||||
for _, op := range list {
|
||||
if !op.IsGroupGrantee() {
|
||||
for _, user := range op.Users {
|
||||
userOps := userOpsMap[user]
|
||||
userOps = append(userOps, op.Op)
|
||||
userOpsMap[user] = userOps
|
||||
}
|
||||
} else {
|
||||
userOps := userOpsMap[allUsersGroup]
|
||||
userOps = append(userOps, op.Op)
|
||||
userOpsMap[allUsersGroup] = userOps
|
||||
}
|
||||
}
|
||||
|
||||
for user, userOps := range userOpsMap {
|
||||
var actions []string
|
||||
LOOP:
|
||||
for action, ops := range actionToOpMap {
|
||||
for _, op := range ops {
|
||||
if !contains(userOps, op) {
|
||||
continue LOOP
|
||||
}
|
||||
}
|
||||
actions = append(actions, action)
|
||||
}
|
||||
if len(actions) != 0 {
|
||||
state := statement{
|
||||
Effect: actionToEffect(eaclAction),
|
||||
Principal: principal{CanonicalUser: user},
|
||||
Action: actions,
|
||||
Resource: []string{arnAwsPrefix + resourceName},
|
||||
}
|
||||
if user == allUsersGroup {
|
||||
state.Principal = principal{AWS: allUsersWildcard}
|
||||
}
|
||||
bktPolicy.Statement = append(bktPolicy.Statement, state)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func triageOperations(operations []*astOperation) ([]*astOperation, []*astOperation) {
|
||||
var allowed, denied []*astOperation
|
||||
for _, op := range operations {
|
||||
if op.Action == eacl.ActionAllow {
|
||||
allowed = append(allowed, op)
|
||||
} else {
|
||||
denied = append(denied, op)
|
||||
}
|
||||
}
|
||||
return allowed, denied
|
||||
}
|
||||
|
||||
func addTo(list []*astOperation, userID string, op eacl.Operation, groupGrantee bool, action eacl.Action) []*astOperation {
|
||||
var found *astOperation
|
||||
for _, astop := range list {
|
||||
|
@ -1387,17 +1372,6 @@ func effectToAction(effect string) eacl.Action {
|
|||
return eacl.ActionUnknown
|
||||
}
|
||||
|
||||
func actionToEffect(action eacl.Action) string {
|
||||
switch action {
|
||||
case eacl.ActionAllow:
|
||||
return "Allow"
|
||||
case eacl.ActionDeny:
|
||||
return "Deny"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func permissionToOperations(permission AWSACL) []eacl.Operation {
|
||||
switch permission {
|
||||
case aclFullControl:
|
||||
|
|
|
@ -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))).
|
||||
|
|
|
@ -13,7 +13,6 @@ 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"
|
||||
|
@ -28,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) {
|
||||
|
@ -36,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{
|
||||
|
@ -47,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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -165,7 +167,7 @@ func TestPolicyChecker(t *testing.T) {
|
|||
}},
|
||||
}
|
||||
|
||||
err := chiRouter.cfg.PolicyStorage.MorphRuleChainStorage().AddMorphRuleChain(policy.S3ChainName, 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))
|
||||
}
|
||||
|
|
2
go.mod
2
go.mod
|
@ -7,7 +7,7 @@ require (
|
|||
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
|
||||
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20231213132038-1d07331f5df5
|
||||
git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20231018083019-2b6d84de9a3d
|
||||
github.com/aws/aws-sdk-go v1.44.6
|
||||
github.com/bluele/gcache v0.0.2
|
||||
|
|
4
go.sum
4
go.sum
|
@ -48,8 +48,8 @@ git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20231107114540-ab75edd70939
|
|||
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20231107114540-ab75edd70939/go.mod h1:t1akKcUH7iBrFHX8rSXScYMP17k2kYQXMbZooiL5Juw=
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc=
|
||||
git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM=
|
||||
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20231205092054-2d4a9fc6dcb3 h1:d4cCtg6vgQ101Qni9FqYaGPkmSJP1ZnEyHYMI+JaTIo=
|
||||
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20231205092054-2d4a9fc6dcb3/go.mod h1:ekrDiIySdYhji5rBNAkxYMztFWMXyC9Q8LVz6gGVDu0=
|
||||
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20231213132038-1d07331f5df5 h1:vNDlTalmXHL4jVbDfquBdXeoevglOOFImOM/yanH14A=
|
||||
git.frostfs.info/TrueCloudLab/policy-engine v0.0.0-20231213132038-1d07331f5df5/go.mod h1:iJMX6qk9aIHIu3WVSd4puF5CHsNk5eOi++MaJJfNbXM=
|
||||
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9mwVfJ+SjT3HA=
|
||||
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0/go.mod h1:okpbKfVYf/BpejtfFTfhZqFP+sZ8rsHrP8Rr/jYPNRc=
|
||||
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 h1:UFMnUIk0Zh17m8rjGHJMqku2hCgaXDqjqZzS4gsb4UA=
|
||||
|
|
|
@ -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,60 +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"
|
||||
"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
|
||||
}
|
|
@ -7,12 +7,12 @@ 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/util"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
||||
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy"
|
||||
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/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
)
|
||||
|
||||
|
@ -33,11 +33,11 @@ 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) {
|
||||
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)
|
||||
}
|
||||
|
@ -66,47 +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) 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) 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) error {
|
||||
chainName := append([]byte(name), []byte(chainID)...)
|
||||
_, err := c.actor.Wait(c.policyContract.RemoveChain(getKind(target), target.Name, chainName))
|
||||
return err
|
||||
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)
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ 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"
|
||||
|
@ -142,7 +141,7 @@ func (s *Server) putPolicy(data *control.PutPoliciesRequest_ChainData) error {
|
|||
}
|
||||
|
||||
ns := s.settings.ResolveNamespaceAlias(data.GetNamespace())
|
||||
if _, err := s.chainStorage.AddOverride(policy.S3ChainName, engine.NamespaceTarget(ns), &overrideChain); err != nil {
|
||||
if _, err := s.chainStorage.AddOverride(chain.S3, engine.NamespaceTarget(ns), &overrideChain); err != nil {
|
||||
return status.Error(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
|
@ -171,7 +170,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.RemoveOverride(policy.S3ChainName, engine.NamespaceTarget(ns), chain.ID(info.GetChainID()))
|
||||
err := s.chainStorage.RemoveOverride(chain.S3, engine.NamespaceTarget(ns), chain.ID(info.GetChainID()))
|
||||
if err != nil {
|
||||
if isNotFoundError(err) {
|
||||
return status.Error(codes.NotFound, err.Error())
|
||||
|
@ -194,7 +193,7 @@ func (s *Server) GetPolicy(_ context.Context, req *control.GetPolicyRequest) (*c
|
|||
}
|
||||
|
||||
ns := s.settings.ResolveNamespaceAlias(req.GetBody().GetNamespace())
|
||||
overrideChain, err := s.chainStorage.GetOverride(policy.S3ChainName, engine.NamespaceTarget(ns), chain.ID(req.GetBody().GetChainID()))
|
||||
overrideChain, err := s.chainStorage.GetOverride(chain.S3, engine.NamespaceTarget(ns), chain.ID(req.GetBody().GetChainID()))
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
@ -215,7 +214,7 @@ func (s *Server) ListPolicies(_ context.Context, req *control.ListPoliciesReques
|
|||
}
|
||||
|
||||
ns := s.settings.ResolveNamespaceAlias(req.GetBody().GetNamespace())
|
||||
chains, err := s.chainStorage.ListOverrides(policy.S3ChainName, engine.NamespaceTarget(ns))
|
||||
chains, err := s.chainStorage.ListOverrides(chain.S3, engine.NamespaceTarget(ns))
|
||||
if err != nil {
|
||||
return nil, status.Error(codes.InvalidArgument, err.Error())
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue
Isn't it completely replaced by
chain.S3
?