feature/261-update_put-bucket-policy_to_use_contract #279

Merged
19 changed files with 604 additions and 292 deletions

View file

@ -8,6 +8,7 @@ import (
"encoding/json" "encoding/json"
stderrors "errors" stderrors "errors"
"fmt" "fmt"
"io"
"net/http" "net/http"
"sort" "sort"
"strconv" "strconv"
@ -24,6 +25,9 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "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" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -485,19 +489,45 @@ func (h *handler) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request)
return 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 { 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 return
} }
ast := tableToAst(bucketACL.EACL, reqInfo.BucketName) w.Header().Set(api.ContentType, "application/json")
bktPolicy := astToPolicy(ast)
w.WriteHeader(http.StatusOK) w.WriteHeader(http.StatusOK)
if err = json.NewEncoder(w).Encode(bktPolicy); err != nil { if _, err = w.Write(jsonPolicy); err != nil {
h.logAndSendError(w, "something went wrong", reqInfo, err) 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 return
} }
token, err := getSessionTokenSetEACL(r.Context()) jsonPolicy, err := io.ReadAll(r.Body)
if err != nil { if err != nil {
h.logAndSendError(w, "couldn't get eacl token", reqInfo, err) h.logAndSendError(w, "read body", reqInfo, err)
return return
} }
bktPolicy := &bucketPolicy{Bucket: reqInfo.BucketName} var bktPolicy engineiam.Policy
if err = json.NewDecoder(r.Body).Decode(bktPolicy); err != nil { if err = json.Unmarshal(jsonPolicy, &bktPolicy); err != nil {
h.logAndSendError(w, "could not parse bucket policy", reqInfo, err) h.logAndSendError(w, "could not parse bucket policy", reqInfo, err)
return return
} }
astPolicy, err := policyToAst(bktPolicy) s3Chain, err := engineiam.ConvertToS3Chain(bktPolicy, h.frostfsid)
if err != nil { 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 return
} }
if _, err = h.updateBucketACL(r, astPolicy, bktInfo, token); err != nil { if err = h.ape.PutPolicy(resolvedNamespace, bktInfo.CID, jsonPolicy); err != nil {
h.logAndSendError(w, "could not update bucket acl", reqInfo, err) h.logAndSendError(w, "failed to save policy to storage", reqInfo, err)
return 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) { func parseACLHeaders(header http.Header, key *keys.PublicKey) (*AccessControlPolicy, error) {
var err error var err error
acp := &AccessControlPolicy{Owner: Owner{ acp := &AccessControlPolicy{Owner: Owner{
@ -1134,73 +1186,6 @@ func resourceInfoFromName(name, bucketName string) resourceInfo {
return resInfo return resInfo
} }
func astToPolicy(ast *ast) *bucketPolicy {

I will miss this code 😃

I will miss this code 😃
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 { func addTo(list []*astOperation, userID string, op eacl.Operation, groupGrantee bool, action eacl.Action) []*astOperation {
var found *astOperation var found *astOperation
for _, astop := range list { for _, astop := range list {
@ -1387,17 +1372,6 @@ func effectToAction(effect string) eacl.Action {
return eacl.ActionUnknown 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 { func permissionToOperations(permission AWSACL) []eacl.Operation {
switch permission { switch permission {
case aclFullControl: case aclFullControl:

View file

@ -23,6 +23,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/session" "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/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -1316,42 +1317,26 @@ func TestBucketPolicy(t *testing.T) {
hc := prepareHandlerContext(t) hc := prepareHandlerContext(t)
bktName := "bucket-for-policy" bktName := "bucket-for-policy"
box, key := createAccessBox(t) createTestBucket(hc, bktName)
createBucket(t, hc, bktName, box)
bktPolicy := getBucketPolicy(hc, bktName) getBucketPolicy(hc, bktName, s3errors.ErrNoSuchBucketPolicy)
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)
}
}
newPolicy := &bucketPolicy{ newPolicy := engineiam.Policy{
Statement: []statement{{ Statement: []engineiam.Statement{{
Effect: "Allow", Principal: map[engineiam.PrincipalType][]string{engineiam.Wildcard: {}},
Principal: principal{AWS: allUsersWildcard}, Effect: engineiam.DenyEffect,
Action: []string{s3GetObject}, Action: engineiam.Action{"s3:PutObject"},
Resource: []string{arnAwsPrefix + "dummy"}, 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 newPolicy.Statement[0].Resource[0] = arnAwsPrefix + bktName + "/*"
putBucketPolicy(hc, bktName, newPolicy, box, http.StatusOK) putBucketPolicy(hc, bktName, newPolicy)
bktPolicy = getBucketPolicy(hc, bktName) bktPolicy := getBucketPolicy(hc, bktName)
for _, st := range bktPolicy.Statement { require.Equal(t, newPolicy, bktPolicy)
if st.Effect == "Allow" && st.Principal.AWS == allUsersWildcard {
require.Equal(t, []string{arnAwsPrefix + bktName}, st.Resource)
require.ElementsMatch(t, []string{s3GetObject, s3ListBucket}, st.Action)
}
}
} }
func TestBucketPolicyUnmarshal(t *testing.T) { func TestBucketPolicyUnmarshal(t *testing.T) {
@ -1411,9 +1396,7 @@ func TestPutBucketPolicy(t *testing.T) {
{ {
"Version": "2012-10-17", "Version": "2012-10-17",
"Statement": [{ "Statement": [{
"Principal": { "Principal": "*",
"AWS": "*"
},
"Effect": "Deny", "Effect": "Deny",
"Action": "s3:GetObject", "Action": "s3:GetObject",
"Resource": "arn:aws:s3:::bucket-for-policy/*" "Resource": "arn:aws:s3:::bucket-for-policy/*"
@ -1423,36 +1406,41 @@ func TestPutBucketPolicy(t *testing.T) {
hc := prepareHandlerContext(t) hc := prepareHandlerContext(t)
bktName := "bucket-for-policy" bktName := "bucket-for-policy"
box, _ := createAccessBox(t) createTestBucket(hc, bktName)
createBucket(t, hc, bktName, box)
w, r := prepareTestPayloadRequest(hc, bktName, "", bytes.NewReader([]byte(bktPolicy))) w, r := prepareTestPayloadRequest(hc, bktName, "", bytes.NewReader([]byte(bktPolicy)))
ctx := middleware.SetBoxData(r.Context(), box)
r = r.WithContext(ctx)
hc.Handler().PutBucketPolicyHandler(w, r) hc.Handler().PutBucketPolicyHandler(w, r)
assertStatus(hc.t, w, http.StatusOK) 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) w, r := prepareTestRequest(hc, bktName, "", nil)
hc.Handler().GetBucketPolicyHandler(w, r) hc.Handler().GetBucketPolicyHandler(w, r)
var policy engineiam.Policy
if len(errCode) == 0 {
assertStatus(hc.t, w, http.StatusOK) assertStatus(hc.t, w, http.StatusOK)
policy := &bucketPolicy{} err := json.NewDecoder(w.Result().Body).Decode(&policy)
err := json.NewDecoder(w.Result().Body).Decode(policy)
require.NoError(hc.t, err) require.NoError(hc.t, err)
} else {
assertS3Error(hc.t, w, s3errors.GetAPIError(errCode[0]))
}
return policy 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) body, err := json.Marshal(bktPolicy)
require.NoError(hc.t, err) require.NoError(hc.t, err)
w, r := prepareTestPayloadRequest(hc, bktName, "", bytes.NewReader(body)) w, r := prepareTestPayloadRequest(hc, bktName, "", bytes.NewReader(body))
ctx := middleware.SetBoxData(r.Context(), box)
r = r.WithContext(ctx)
hc.Handler().PutBucketPolicyHandler(w, r) 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) { func checkLastRecords(t *testing.T, tc *handlerContext, bktInfo *data.BucketInfo, action eacl.Action) {

View file

@ -12,7 +12,10 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" "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/frostfs-sdk-go/netmap"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -22,6 +25,8 @@ type (
obj layer.Client obj layer.Client
notificator Notificator notificator Notificator
cfg Config cfg Config
ape APE
frostfsid FrostFSID
} }
Notificator interface { Notificator interface {
@ -42,18 +47,52 @@ type (
IsResolveListAllow() bool IsResolveListAllow() bool
BypassContentEncodingInChunks() bool BypassContentEncodingInChunks() bool
MD5Enabled() 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) var _ api.Handler = (*handler)(nil)
// New creates new api.Handler using given logger and client. // 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 { switch {
case obj == nil: case obj == nil:
return nil, errors.New("empty FrostFS Object Layer") return nil, errors.New("empty FrostFS Object Layer")
case log == nil: case log == nil:
return nil, errors.New("empty logger") return nil, errors.New("empty logger")
case storage == nil:
return nil, errors.New("empty policy storage")
}
if ffsid == nil {
ffsid = frostfsIDDisabled{}
} }
if !cfg.NotificatorEnabled() { if !cfg.NotificatorEnabled() {
@ -66,10 +105,16 @@ func New(log *zap.Logger, obj layer.Client, notificator Notificator, cfg Config)
log: log, log: log,
obj: obj, obj: obj,
cfg: cfg, cfg: cfg,
ape: storage,
notificator: notificator, notificator: notificator,
frostfsid: ffsid,
}, nil }, nil
} }
func (f frostfsIDDisabled) GetUserAddress(_, _ string) (string, error) {
return "", errors.New("frostfsid disabled")
}
// pickCopiesNumbers chooses the return values following this logic: // pickCopiesNumbers chooses the return values following this logic:
// 1) array of copies numbers sent in request's header has the highest priority. // 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. // 2) array of copies numbers with corresponding location constraint provided in the config file.

View file

@ -5,6 +5,7 @@ import (
"context" "context"
"crypto/rand" "crypto/rand"
"encoding/xml" "encoding/xml"
"errors"
"io" "io"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -26,9 +27,12 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "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/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"go.uber.org/zap" "go.uber.org/zap"
"golang.org/x/exp/slices"
) )
type handlerContext struct { type handlerContext struct {
@ -116,6 +120,10 @@ func (c *configMock) MD5Enabled() bool {
return c.md5Enabled return c.md5Enabled
} }
func (c *configMock) ResolveNamespaceAlias(ns string) string {
return ns
}
func prepareHandlerContext(t *testing.T) *handlerContext { func prepareHandlerContext(t *testing.T) *handlerContext {
return prepareHandlerContextBase(t, false) return prepareHandlerContextBase(t, false)
} }
@ -167,6 +175,7 @@ func prepareHandlerContextBase(t *testing.T, minCache bool) *handlerContext {
log: l, log: l,
obj: layer.NewLayer(l, tp, layerCfg), obj: layer.NewLayer(l, tp, layerCfg),
cfg: cfg, cfg: cfg,
ape: newAPEMock(),
} }
return &handlerContext{ 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 { func NewTreeServiceMock(t *testing.T) *tree.Tree {
memCli, err := tree.NewTreeServiceClientMemory() memCli, err := tree.NewTreeServiceClientMemory()
require.NoError(t, err) require.NoError(t, err)

View file

@ -7,10 +7,6 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "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) { func (h *handler) DeleteBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
h.logAndSendError(w, "not supported", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported)) h.logAndSendError(w, "not supported", middleware.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotSupported))
} }

View file

@ -7,7 +7,6 @@ import (
"strings" "strings"
apiErr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" 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/frostfs-s3-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
@ -52,7 +51,7 @@ func policyCheck(storage engine.ChainRouter, settings PolicySettings, domains []
reqInfo := GetReqInfo(r.Context()) reqInfo := GetReqInfo(r.Context())
target := engine.NewRequestTargetWithNamespace(settings.ResolveNamespaceAlias(reqInfo.Namespace)) 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)

chain.S3?

`chain.S3`?

Sure

Sure
if err != nil { if err != nil {
return 0, err return 0, err
} }

View file

@ -111,7 +111,7 @@ type Config struct {
// FrostfsID optional. If nil middleware.FrostfsIDValidation won't be attached. // FrostfsID optional. If nil middleware.FrostfsIDValidation won't be attached.
FrostfsID s3middleware.FrostFSID FrostfsID s3middleware.FrostFSID
PolicyStorage engine.LocalOverrideEngine PolicyChecker engine.ChainRouter
} }
func NewRouter(cfg Config) *chi.Mux { func NewRouter(cfg Config) *chi.Mux {
@ -130,8 +130,8 @@ func NewRouter(cfg Config) *chi.Mux {
api.Use(s3middleware.FrostfsIDValidation(cfg.FrostfsID, cfg.Log)) api.Use(s3middleware.FrostfsIDValidation(cfg.FrostfsID, cfg.Log))
} }
if cfg.PolicyStorage != nil { if cfg.PolicyChecker != nil {
api.Use(s3middleware.PolicyCheck(cfg.PolicyStorage, cfg.MiddlewareSettings, cfg.Domains, cfg.Log)) api.Use(s3middleware.PolicyCheck(cfg.PolicyChecker, cfg.MiddlewareSettings, cfg.Domains, cfg.Log))
} }
defaultRouter := chi.NewRouter() defaultRouter := chi.NewRouter()
@ -320,7 +320,7 @@ func bucketRouter(h Handler, log *zap.Logger) chi.Router {
Handler(named(s3middleware.DeleteBucketTaggingOperation, h.DeleteBucketTaggingHandler))). Handler(named(s3middleware.DeleteBucketTaggingOperation, h.DeleteBucketTaggingHandler))).
Add(NewFilter(). Add(NewFilter().
Queries(s3middleware.PolicyQuery). Queries(s3middleware.PolicyQuery).
Handler(named(s3middleware.PutBucketPolicyOperation, h.PutBucketPolicyHandler))). Handler(named(s3middleware.DeleteBucketPolicyOperation, h.DeleteBucketPolicyHandler))).
Add(NewFilter(). Add(NewFilter().
Queries(s3middleware.LifecycleQuery). Queries(s3middleware.LifecycleQuery).
Handler(named(s3middleware.PutBucketLifecycleOperation, h.PutBucketLifecycleHandler))). Handler(named(s3middleware.PutBucketLifecycleOperation, h.PutBucketLifecycleHandler))).

View file

@ -13,7 +13,6 @@ import (
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" 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/frostfs-s3-gw/metrics"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
@ -28,6 +27,7 @@ type routerMock struct {
router *chi.Mux router *chi.Mux
cfg Config cfg Config
middlewareSettings *middlewareSettingsMock middlewareSettings *middlewareSettingsMock
policyChecker engine.LocalOverrideEngine
} }
func (m *routerMock) ServeHTTP(w http.ResponseWriter, r *http.Request) { 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 { func prepareRouter(t *testing.T) *routerMock {
middlewareSettings := &middlewareSettingsMock{} middlewareSettings := &middlewareSettingsMock{}
policyChecker := inmemory.NewInMemoryLocalOverrides()
cfg := Config{ cfg := Config{
Throttle: middleware.ThrottleOpts{ Throttle: middleware.ThrottleOpts{
@ -47,12 +48,13 @@ func prepareRouter(t *testing.T) *routerMock {
Log: zaptest.NewLogger(t), Log: zaptest.NewLogger(t),
Metrics: &metrics.AppMetrics{}, Metrics: &metrics.AppMetrics{},
MiddlewareSettings: middlewareSettings, MiddlewareSettings: middlewareSettings,
PolicyStorage: inmemory.NewInMemoryLocalOverrides(), PolicyChecker: policyChecker,
} }
return &routerMock{ return &routerMock{
router: NewRouter(cfg), router: NewRouter(cfg),
cfg: cfg, cfg: cfg,
middlewareSettings: middlewareSettings, 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) require.NoError(t, err)
// check we can access 'bucket' in default namespace // check we can access 'bucket' in default namespace

View file

@ -43,8 +43,6 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
treepool "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree" treepool "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool/tree"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user" "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/go-chi/chi/v5/middleware"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -70,7 +68,7 @@ type (
frostfsid *frostfsid.FrostFSID frostfsid *frostfsid.FrostFSID
policyStorage engine.LocalOverrideEngine policyStorage *policy.Storage
servers []Server servers []Server
@ -144,11 +142,11 @@ func newApp(ctx context.Context, log *Logger, v *viper.Viper) *App {
func (a *App) init(ctx context.Context) { func (a *App) init(ctx context.Context) {
a.setRuntimeParameters() a.setRuntimeParameters()
a.initAPI(ctx)
a.initPolicyStorage(ctx)
a.initControlAPI()
a.initMetrics()
a.initFrostfsID(ctx) a.initFrostfsID(ctx)
a.initPolicyStorage(ctx)
a.initAPI(ctx)
a.initMetrics()
a.initControlAPI()
a.initServers(ctx) a.initServers(ctx)
a.initTracing(ctx) a.initTracing(ctx)
} }
@ -452,12 +450,13 @@ func (a *App) initFrostfsID(ctx context.Context) {
} }
func (a *App) initPolicyStorage(ctx context.Context) { func (a *App) initPolicyStorage(ctx context.Context) {
if !a.cfg.GetBool(cfgPolicyEnabled) { var (
a.policyStorage = inmemory.NewInMemoryLocalOverrides() err error
return policyContract policy.Contract
} )
policyClient, err := contract.New(ctx, contract.Config{ if a.cfg.GetBool(cfgPolicyEnabled) {
policyContract, err = contract.New(ctx, contract.Config{
RPCAddress: a.cfg.GetString(cfgRPCEndpoint), RPCAddress: a.cfg.GetString(cfgRPCEndpoint),
Contract: a.cfg.GetString(cfgPolicyContract), Contract: a.cfg.GetString(cfgPolicyContract),
Key: a.key, Key: a.key,
@ -465,14 +464,15 @@ func (a *App) initPolicyStorage(ctx context.Context) {
if err != nil { if err != nil {
a.log.Fatal(logs.InitPolicyContractFailed, zap.Error(err)) a.log.Fatal(logs.InitPolicyContractFailed, zap.Error(err))
} }
} else {
policyContract = contract.NewInMemoryContract()
}
cachedMorph := policy.NewCachedMorph(policy.CachedMorphConfig{ a.policyStorage = policy.NewStorage(policy.StorageConfig{
Morph: policyClient, Contract: policyContract,
Cache: cache.NewMorphPolicyCache(getMorphPolicyCacheConfig(a.cfg, a.log)), Cache: cache.NewMorphPolicyCache(getMorphPolicyCacheConfig(a.cfg, a.log)),
Log: a.log, Log: a.log,
}) })
a.policyStorage = policy.NewStorage(cachedMorph)
} }
func (a *App) initResolver() { func (a *App) initResolver() {
@ -672,7 +672,7 @@ func (a *App) Serve(ctx context.Context) {
Domains: domains, Domains: domains,
MiddlewareSettings: a.settings, MiddlewareSettings: a.settings,
PolicyStorage: a.policyStorage, PolicyChecker: a.policyStorage,
} }
// We cannot make direct assignment if frostfsid.FrostFSID is nil // 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() { func (a *App) initHandler() {
var err error var (
a.api, err = handler.New(a.log, a.obj, a.nc, a.settings) 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 { if err != nil {
a.log.Fatal(logs.CouldNotInitializeAPIHandler, zap.Error(err)) a.log.Fatal(logs.CouldNotInitializeAPIHandler, zap.Error(err))
} }

2
go.mod
View file

@ -7,7 +7,7 @@ require (
git.frostfs.info/TrueCloudLab/frostfs-contract v0.18.1-0.20231129062201-a1b61d394958 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-observability v0.0.0-20230531082742-c97d21411eb6
git.frostfs.info/TrueCloudLab/frostfs-sdk-go v0.0.0-20231107114540-ab75edd70939 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 git.frostfs.info/TrueCloudLab/zapjournald v0.0.0-20231018083019-2b6d84de9a3d
github.com/aws/aws-sdk-go v1.44.6 github.com/aws/aws-sdk-go v1.44.6
github.com/bluele/gcache v0.0.2 github.com/bluele/gcache v0.0.2

4
go.sum
View file

@ -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/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 h1:ccBRK21rFvY5R1WotI6LNoPlizk7qSvdfD8lNIRudVc=
git.frostfs.info/TrueCloudLab/hrw v1.2.1/go.mod h1:C1Ygde2n843yTZEQ0FP69jYiuaYV0kriLvP4zm8JuvM= 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-20231213132038-1d07331f5df5 h1:vNDlTalmXHL4jVbDfquBdXeoevglOOFImOM/yanH14A=
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/go.mod h1:iJMX6qk9aIHIu3WVSd4puF5CHsNk5eOi++MaJJfNbXM=
git.frostfs.info/TrueCloudLab/rfc6979 v0.4.0 h1:M2KR3iBj7WpY3hP10IevfIB9MURr4O9mwVfJ+SjT3HA= 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/rfc6979 v0.4.0/go.mod h1:okpbKfVYf/BpejtfFTfhZqFP+sZ8rsHrP8Rr/jYPNRc=
git.frostfs.info/TrueCloudLab/tzhash v1.8.0 h1:UFMnUIk0Zh17m8rjGHJMqku2hCgaXDqjqZzS4gsb4UA= git.frostfs.info/TrueCloudLab/tzhash v1.8.0 h1:UFMnUIk0Zh17m8rjGHJMqku2hCgaXDqjqZzS4gsb4UA=

View file

@ -6,9 +6,10 @@ import (
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-contract/frostfsid/client" "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/api/middleware"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate" "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/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/rpcclient" "github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
@ -33,11 +34,12 @@ type Config struct {
var ( var (
_ middleware.FrostFSID = (*FrostFSID)(nil) _ middleware.FrostFSID = (*FrostFSID)(nil)
_ authmate.FrostFSID = (*FrostFSID)(nil) _ authmate.FrostFSID = (*FrostFSID)(nil)
_ handler.FrostFSID = (*FrostFSID)(nil)
) )
// New creates new FrostfsID contract wrapper that implements auth.FrostFSID interface. // New creates new FrostfsID contract wrapper that implements auth.FrostFSID interface.
func New(ctx context.Context, cfg Config) (*FrostFSID, error) { 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 { if err != nil {
return nil, fmt.Errorf("resolve frostfs contract hash: %w", err) return nil, fmt.Errorf("resolve frostfs contract hash: %w", err)
} }
@ -77,3 +79,12 @@ func (f *FrostFSID) RegisterPublicKey(key *keys.PublicKey) error {
return nil 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
}

View file

@ -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
}

View file

@ -7,12 +7,12 @@ import (
policycontract "git.frostfs.info/TrueCloudLab/frostfs-contract/policy" policycontract "git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
policyclient "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/policy" policyclient "git.frostfs.info/TrueCloudLab/frostfs-contract/rpcclient/policy"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/util" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/policy"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" frostfsutil "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/util"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "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"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor" "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" "github.com/nspcc-dev/neo-go/pkg/wallet"
) )
@ -33,11 +33,11 @@ type Config struct {
Key *keys.PrivateKey Key *keys.PrivateKey
} }
var _ engine.MorphRuleChainStorage = (*Client)(nil) var _ policy.Contract = (*Client)(nil)
// New creates new Policy contract wrapper. // New creates new Policy contract wrapper.
func New(ctx context.Context, cfg Config) (*Client, error) { 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 { if err != nil {
return nil, fmt.Errorf("resolve frostfs contract hash: %w", err) return nil, fmt.Errorf("resolve frostfs contract hash: %w", err)
} }
@ -66,47 +66,36 @@ func New(ctx context.Context, cfg Config) (*Client, error) {
}, nil }, nil
} }
func (c *Client) AddMorphRuleChain(name chain.Name, target engine.Target, policyChain *chain.Chain) error { func (c *Client) AddChain(kind policycontract.Kind, entity string, name []byte, chain []byte) (util.Uint256, uint32, error) {
chainName := append([]byte(name), []byte(policyChain.ID)...) return c.policyContract.AddChain(big.NewInt(int64(kind)), entity, name, chain)
_, err := c.actor.Wait(c.policyContract.AddChain(getKind(target), target.Name, chainName, policyChain.Bytes()))
return err
} }
func (c *Client) RemoveMorphRuleChain(name chain.Name, target engine.Target, chainID chain.ID) error { func (c *Client) GetChain(kind policycontract.Kind, entity string, name []byte) ([]byte, error) {
chainName := append([]byte(name), []byte(chainID)...) return c.policyContract.GetChain(big.NewInt(int64(kind)), entity, name)
_, err := c.actor.Wait(c.policyContract.RemoveChain(getKind(target), target.Name, chainName))
return err
} }
func (c *Client) ListMorphRuleChains(name chain.Name, target engine.Target) ([]*chain.Chain, error) { func (c *Client) RemoveChain(kind policycontract.Kind, entity string, name []byte) (util.Uint256, uint32, error) {
items, err := c.policyContract.ListChainsByPrefix(getKind(target), target.Name, []byte(name)) 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 { if err != nil {
return nil, err return nil, err
} }
res := make([]*chain.Chain, len(items)) res := make([][]byte, len(items))
for i, item := range items { for i, item := range items {
data, err := item.TryBytes() res[i], err = item.TryBytes()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var policyChain chain.Chain
if err = policyChain.DecodeBytes(data); err != nil {
return nil, err
}
res[i] = &policyChain
} }
return res, nil return res, nil
} }
func getKind(target engine.Target) *big.Int { func (c *Client) Wait(tx util.Uint256, vub uint32, err error) error {
var kind int64 = policycontract.Container _, err = c.actor.Wait(tx, vub, err)
if target.Type != engine.Container { return err
kind = policycontract.Namespace
}
return big.NewInt(kind)
} }

View 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{}}
}
}

View 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[:]...)
}

View 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)...)
}

View file

@ -1,42 +1,95 @@
package policy package policy
import ( 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/chain"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine/inmemory"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/resource" "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 { type Storage struct {
Review

Isn't it completely replaced by chain.S3?

Isn't it completely replaced by `chain.S3`?
router engine.ChainRouter router engine.ChainRouter
morph engine.MorphRuleChainStorage morph handler.MorphRuleChainStorage
local engine.LocalOverrideStorage 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() local := inmemory.NewInmemoryLocalStorage()

Maybe link to the issue in policy engine repo?

Maybe link to the issue in policy engine repo?

I wanted to implement thread same in memory storage in s3-gw repo but forgot

I wanted to implement thread same in memory storage in s3-gw repo but forgot

But let's create issue for policy engine TrueCloudLab/policy-engine#35

But let's create issue for policy engine https://git.frostfs.info/TrueCloudLab/policy-engine/issues/35
morph := NewMorphRuleChainStorage(&MorphRuleChainStorageConfig{
Contract: cfg.Contract,
Cache: cfg.Cache,
Log: cfg.Log,
})
policyStorage := NewMorphPolicyStorage(&MorphPolicyStorageConfig{
Contract: cfg.Contract,
Log: cfg.Log,
})
return &Storage{ return &Storage{
router: engine.NewDefaultChainRouterWithLocalOverrides(morph, local), router: engine.NewDefaultChainRouterWithLocalOverrides(morph, local),
morph: morph, morph: morph,
local: local, 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) return s.router.IsAllowed(name, target, r)
} }
func (s Storage) MorphRuleChainStorage() engine.MorphRuleChainStorage { func (s *Storage) LocalStorage() engine.LocalOverrideStorage {
return s.morph
}
func (s Storage) LocalStorage() engine.LocalOverrideStorage {
return s.local 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)
}

View file

@ -9,7 +9,6 @@ import (
"fmt" "fmt"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/refs" "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/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/control" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/service/control"
frostfscrypto "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/crypto" 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()) 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()) 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 { func (s *Server) removePolicy(info *control.RemovePoliciesRequest_ChainInfo) error {
ns := s.settings.ResolveNamespaceAlias(info.GetNamespace()) 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 err != nil {
if isNotFoundError(err) { if isNotFoundError(err) {
return status.Error(codes.NotFound, err.Error()) 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()) 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 { if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error()) 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()) 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 { if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error()) return nil, status.Error(codes.InvalidArgument, err.Error())
} }