[#770] cli: Add methods to work with APE rules via control svc

* Add methods to frostfs-cli
* Implement rpc in control service

Signed-off-by: Airat Arifullin <a.arifullin@yadro.com>
This commit is contained in:
Airat Arifullin 2023-10-31 11:55:42 +03:00
parent d04a00800b
commit 624f9deb87
11 changed files with 813 additions and 12 deletions

View file

@ -0,0 +1,90 @@
package control
import (
"bytes"
"crypto/sha256"
"encoding/json"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/modules/util"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
ape "git.frostfs.info/TrueCloudLab/policy-engine"
"github.com/spf13/cobra"
)
const (
ruleFlag = "rule"
)
var addRuleCmd = &cobra.Command{
Use: "add-rule",
Short: "Add local override",
Long: "Add local APE rule to a node with following format:\n<action>[:action_detail] <operation> [<condition1> ...] <resource>",
Example: `allow Object.Get *
deny Object.Get EbxzAdz5LB4uqxuz6crWKAumBNtZyK2rKsqQP7TdZvwr/*
deny:QuotaLimitReached Object.Put Object.Resource:Department=HR *
`,
Run: addRule,
}
func prettyJSONFormat(cmd *cobra.Command, serializedChain []byte) string {
wr := bytes.NewBufferString("")
err := json.Indent(wr, serializedChain, "", " ")
commonCmd.ExitOnErr(cmd, "%w", err)
return wr.String()
}
func addRule(cmd *cobra.Command, _ []string) {
pk := key.Get(cmd)
var cnr cid.ID
cidStr, _ := cmd.Flags().GetString(commonflags.CIDFlag)
commonCmd.ExitOnErr(cmd, "can't decode container ID: %w", cnr.DecodeString(cidStr))
rawCID := make([]byte, sha256.Size)
cnr.Encode(rawCID)
rule, _ := cmd.Flags().GetString(ruleFlag)
chain := new(ape.Chain)
commonCmd.ExitOnErr(cmd, "parser error: %w", util.ParseAPEChain(chain, []string{rule}))
serializedChain := chain.Bytes()
cmd.Println("Container ID: " + cidStr)
cmd.Println("Parsed chain:\n" + prettyJSONFormat(cmd, serializedChain))
req := &control.AddChainLocalOverrideRequest{
Body: &control.AddChainLocalOverrideRequest_Body{
ContainerId: rawCID,
Chain: serializedChain,
},
}
signRequest(cmd, pk, req)
cli := getClient(cmd, pk)
var resp *control.AddChainLocalOverrideResponse
var err error
err = cli.ExecRaw(func(client *client.Client) error {
resp, err = control.AddChainLocalOverride(client, req)
return err
})
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
cmd.Println("Rule has been added. Chain id: ", resp.GetBody().GetChainId())
}
func initControlAddRuleCmd() {
initControlFlags(addRuleCmd)
ff := addRuleCmd.Flags()
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
ff.String(ruleFlag, "", "Rule statement")
}

View file

@ -0,0 +1,69 @@
package control
import (
"crypto/sha256"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
"github.com/spf13/cobra"
)
var getRuleCmd = &cobra.Command{
Use: "get-rule",
Short: "Get local override",
Long: "Get local APE override of the node",
Run: getRule,
}
func getRule(cmd *cobra.Command, _ []string) {
pk := key.Get(cmd)
var cnr cid.ID
cidStr, _ := cmd.Flags().GetString(commonflags.CIDFlag)
commonCmd.ExitOnErr(cmd, "can't decode container ID: %w", cnr.DecodeString(cidStr))
rawCID := make([]byte, sha256.Size)
cnr.Encode(rawCID)
chainID, _ := cmd.Flags().GetString(chainIDFlag)
req := &control.GetChainLocalOverrideRequest{
Body: &control.GetChainLocalOverrideRequest_Body{
ContainerId: rawCID,
ChainId: chainID,
},
}
signRequest(cmd, pk, req)
cli := getClient(cmd, pk)
var resp *control.GetChainLocalOverrideResponse
var err error
err = cli.ExecRaw(func(client *client.Client) error {
resp, err = control.GetChainLocalOverride(client, req)
return err
})
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
var chain policyengine.Chain
commonCmd.ExitOnErr(cmd, "decode error: %w", chain.DecodeBytes(resp.GetBody().GetChain()))
// TODO (aarifullin): make pretty-formatted output for chains.
cmd.Println("Parsed chain:\n" + prettyJSONFormat(cmd, chain.Bytes()))
}
func initControGetRuleCmd() {
initControlFlags(getRuleCmd)
ff := getRuleCmd.Flags()
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
ff.String(chainIDFlag, "", "Chain id")
}

View file

@ -0,0 +1,72 @@
package control
import (
"crypto/sha256"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
"github.com/spf13/cobra"
)
var listRulesCmd = &cobra.Command{
Use: "list-rules",
Short: "List local overrides",
Long: "List local APE overrides of the node",
Run: listRules,
}
func listRules(cmd *cobra.Command, _ []string) {
pk := key.Get(cmd)
var cnr cid.ID
cidStr, _ := cmd.Flags().GetString(commonflags.CIDFlag)
commonCmd.ExitOnErr(cmd, "can't decode container ID: %w", cnr.DecodeString(cidStr))
rawCID := make([]byte, sha256.Size)
cnr.Encode(rawCID)
req := &control.ListChainLocalOverridesRequest{
Body: &control.ListChainLocalOverridesRequest_Body{
ContainerId: rawCID,
},
}
signRequest(cmd, pk, req)
cli := getClient(cmd, pk)
var resp *control.ListChainLocalOverridesResponse
var err error
err = cli.ExecRaw(func(client *client.Client) error {
resp, err = control.ListChainLocalOverrides(client, req)
return err
})
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
chains := resp.GetBody().GetChains()
if len(chains) == 0 {
cmd.Println("Local overrides are not defined for the container.")
return
}
for _, c := range chains {
// TODO (aarifullin): make pretty-formatted output for chains.
var chain policyengine.Chain
commonCmd.ExitOnErr(cmd, "decode error: %w", chain.DecodeBytes(c))
cmd.Println("Parsed chain:\n" + prettyJSONFormat(cmd, chain.Bytes()))
}
}
func initControlListRulesCmd() {
initControlFlags(listRulesCmd)
ff := listRulesCmd.Flags()
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
}

View file

@ -0,0 +1,72 @@
package control
import (
"crypto/sha256"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/rpc/client"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/commonflags"
"git.frostfs.info/TrueCloudLab/frostfs-node/cmd/frostfs-cli/internal/key"
commonCmd "git.frostfs.info/TrueCloudLab/frostfs-node/cmd/internal/common"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"github.com/spf13/cobra"
)
const (
chainIDFlag = "chain-id"
)
var removeRuleCmd = &cobra.Command{
Use: "remove-rule",
Short: "Remove local override",
Long: "Remove local APE override of the node",
Run: removeRule,
}
func removeRule(cmd *cobra.Command, _ []string) {
pk := key.Get(cmd)
var cnr cid.ID
cidStr, _ := cmd.Flags().GetString(commonflags.CIDFlag)
commonCmd.ExitOnErr(cmd, "can't decode container ID: %w", cnr.DecodeString(cidStr))
rawCID := make([]byte, sha256.Size)
cnr.Encode(rawCID)
chainID, _ := cmd.Flags().GetString(chainIDFlag)
req := &control.RemoveChainLocalOverrideRequest{
Body: &control.RemoveChainLocalOverrideRequest_Body{
ContainerId: rawCID,
ChainId: chainID,
},
}
signRequest(cmd, pk, req)
cli := getClient(cmd, pk)
var resp *control.RemoveChainLocalOverrideResponse
var err error
err = cli.ExecRaw(func(client *client.Client) error {
resp, err = control.RemoveChainLocalOverride(client, req)
return err
})
commonCmd.ExitOnErr(cmd, "rpc error: %w", err)
verifyResponse(cmd, resp.GetSignature(), resp.GetBody())
if resp.GetBody().Removed {
cmd.Println("Rule has been removed.")
} else {
cmd.Println("Rule has not been removed.")
}
}
func initControlRemoveRuleCmd() {
initControlFlags(removeRuleCmd)
ff := removeRuleCmd.Flags()
ff.String(commonflags.CIDFlag, "", commonflags.CIDFlagUsage)
ff.String(chainIDFlag, "", "Chain id")
}

View file

@ -34,6 +34,10 @@ func init() {
shardsCmd, shardsCmd,
synchronizeTreeCmd, synchronizeTreeCmd,
irCmd, irCmd,
addRuleCmd,
removeRuleCmd,
listRulesCmd,
getRuleCmd,
) )
initControlHealthCheckCmd() initControlHealthCheckCmd()
@ -42,4 +46,8 @@ func init() {
initControlShardsCmd() initControlShardsCmd()
initControlSynchronizeTreeCmd() initControlSynchronizeTreeCmd()
initControlIRCmd() initControlIRCmd()
initControlAddRuleCmd()
initControlRemoveRuleCmd()
initControlListRulesCmd()
initControGetRuleCmd()
} }

View file

@ -0,0 +1,179 @@
package util
import (
"errors"
"fmt"
"strings"
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
"github.com/flynn-archive/go-shlex"
)
var (
errInvalidStatementFormat = errors.New("invalid statement format")
errInvalidConditionFormat = errors.New("invalid condition format")
errUnknownAction = errors.New("action is not recognized")
errUnknownOperation = errors.New("operation is not recognized")
errUnknownActionDetail = errors.New("action detail is not recognized")
errUnknownBinaryOperator = errors.New("binary operator is not recognized")
errUnknownCondObjectType = errors.New("condition object type is not recognized")
)
// ParseAPEChain parses APE chain rules.
func ParseAPEChain(chain *policyengine.Chain, rules []string) error {
if len(rules) == 0 {
return errors.New("no APE rules provided")
}
for _, rule := range rules {
r := new(policyengine.Rule)
if err := ParseAPERule(r, rule); err != nil {
return err
}
chain.Rules = append(chain.Rules, *r)
}
return nil
}
// ParseAPERule parses access-policy-engine statement from the following form:
// <action>[:action_detail] <operation> [<condition1> ...] <resource>
//
// Examples:
// deny Object.Put *
// deny:QuotaLimitReached Object.Put *
// allow Object.Put *
// allow Object.Get Object.Resource:Department=HR Object.Request:Actor=ownerA *
//
//nolint:godot
func ParseAPERule(r *policyengine.Rule, rule string) error {
lexemes, err := shlex.Split(rule)
if err != nil {
return fmt.Errorf("can't parse rule '%s': %v", rule, err)
}
return parseRuleLexemes(r, lexemes)
}
func parseRuleLexemes(r *policyengine.Rule, lexemes []string) error {
if len(lexemes) < 2 {
return errInvalidStatementFormat
}
var err error
r.Status, err = parseStatus(lexemes[0])
if err != nil {
return err
}
r.Action, err = parseAction(lexemes[1])
if err != nil {
return err
}
r.Condition, err = parseConditions(lexemes[2 : len(lexemes)-1])
if err != nil {
return err
}
r.Resource, err = parseResource(lexemes[len(lexemes)-1])
return err
}
func parseStatus(lexeme string) (policyengine.Status, error) {
action, expression, found := strings.Cut(lexeme, ":")
switch action = strings.ToLower(action); action {
case "deny":
if !found {
return policyengine.AccessDenied, nil
} else if strings.EqualFold(expression, "QuotaLimitReached") {
return policyengine.QuotaLimitReached, nil
} else {
return 0, fmt.Errorf("%w: %s", errUnknownActionDetail, expression)
}
case "allow":
if found {
return 0, errUnknownActionDetail
}
return policyengine.Allow, nil
default:
return 0, errUnknownAction
}
}
func parseAction(lexeme string) ([]string, error) {
switch strings.ToLower(lexeme) {
case "object.put":
return []string{"native:PutObject"}, nil
case "object.get":
return []string{"native:GetObject"}, nil
case "object.head":
return []string{"native:HeadObject"}, nil
case "object.delete":
return []string{"native:DeleteObject"}, nil
case "object.search":
return []string{"native:SearchObject"}, nil
case "object.range":
return []string{"native:RangeObject"}, nil
case "object.hash":
return []string{"native:HashObject"}, nil
default:
}
return nil, fmt.Errorf("%w: %s", errUnknownOperation, lexeme)
}
func parseResource(lexeme string) ([]string, error) {
return []string{fmt.Sprintf("native:::object/%s", lexeme)}, nil
}
const (
ObjectResource = "object.resource"
ObjectRequest = "object.request"
ObjectActor = "object.actor"
)
var typeToCondObject = map[string]policyengine.ObjectType{
ObjectResource: policyengine.ObjectResource,
ObjectRequest: policyengine.ObjectRequest,
ObjectActor: policyengine.ObjectActor,
}
func parseConditions(lexemes []string) ([]policyengine.Condition, error) {
conds := make([]policyengine.Condition, 0)
for _, lexeme := range lexemes {
typ, expression, found := strings.Cut(lexeme, ":")
typ = strings.ToLower(typ)
objType, ok := typeToCondObject[typ]
if ok {
if !found {
return nil, fmt.Errorf("%w: %s", errInvalidConditionFormat, lexeme)
}
var lhs, rhs string
var binExpFound bool
var cond policyengine.Condition
cond.Object = objType
lhs, rhs, binExpFound = strings.Cut(expression, "!=")
if !binExpFound {
lhs, rhs, binExpFound = strings.Cut(expression, "=")
if !binExpFound {
return nil, fmt.Errorf("%w: %s", errUnknownBinaryOperator, expression)
}
cond.Op = policyengine.CondStringEquals
} else {
cond.Op = policyengine.CondStringNotEquals
}
cond.Key, cond.Value = lhs, rhs
conds = append(conds, cond)
} else {
return nil, fmt.Errorf("%w: %s", errUnknownCondObjectType, typ)
}
}
return conds, nil
}

View file

@ -0,0 +1,129 @@
package util
import (
"testing"
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
"github.com/stretchr/testify/require"
)
func TestParseAPERule(t *testing.T) {
tests := [...]struct {
name string
rule string
expectErr error
expectRule policyengine.Rule
}{
{
name: "Valid allow rule",
rule: "allow Object.Put *",
expectRule: policyengine.Rule{
Status: policyengine.Allow,
Action: []string{"native:PutObject"},
Resource: []string{"native:::object/*"},
Condition: []policyengine.Condition{},
},
},
{
name: "Valid deny rule",
rule: "deny Object.Put *",
expectRule: policyengine.Rule{
Status: policyengine.AccessDenied,
Action: []string{"native:PutObject"},
Resource: []string{"native:::object/*"},
Condition: []policyengine.Condition{},
},
},
{
name: "Valid deny rule with action detail",
rule: "deny:QuotaLimitReached Object.Put *",
expectRule: policyengine.Rule{
Status: policyengine.QuotaLimitReached,
Action: []string{"native:PutObject"},
Resource: []string{"native:::object/*"},
Condition: []policyengine.Condition{},
},
},
{
name: "Valid allow rule with conditions",
rule: "allow Object.Get Object.Resource:Department=HR Object.Request:Actor!=ownerA *",
expectRule: policyengine.Rule{
Status: policyengine.Allow,
Action: []string{"native:GetObject"},
Resource: []string{"native:::object/*"},
Condition: []policyengine.Condition{
{
Op: policyengine.CondStringEquals,
Object: policyengine.ObjectResource,
Key: "Department",
Value: "HR",
},
{
Op: policyengine.CondStringNotEquals,
Object: policyengine.ObjectRequest,
Key: "Actor",
Value: "ownerA",
},
},
},
},
{
name: "Valid rule with conditions with action detail",
rule: "deny:QuotaLimitReached Object.Get Object.Resource:Department=HR Object.Request:Actor!=ownerA *",
expectRule: policyengine.Rule{
Status: policyengine.QuotaLimitReached,
Action: []string{"native:GetObject"},
Resource: []string{"native:::object/*"},
Condition: []policyengine.Condition{
{
Op: policyengine.CondStringEquals,
Object: policyengine.ObjectResource,
Key: "Department",
Value: "HR",
},
{
Op: policyengine.CondStringNotEquals,
Object: policyengine.ObjectRequest,
Key: "Actor",
Value: "ownerA",
},
},
},
},
{
name: "Invalid rule with unknown action",
rule: "permit Object.Put *",
expectErr: errUnknownAction,
},
{
name: "Invalid rule with unknown operation",
rule: "allow Object.PutOut *",
expectErr: errUnknownOperation,
},
{
name: "Invalid rule with unknown action detail",
rule: "deny:UnknownActionDetail Object.Put *",
expectErr: errUnknownActionDetail,
},
{
name: "Invalid rule with unknown condition binary operator",
rule: "deny Object.Put Object.Resource:Department<HR *",
expectErr: errUnknownBinaryOperator,
},
{
name: "Invalid rule with unknown condition object type",
rule: "deny Object.Put Object.ResourZe:Department=HR *",
expectErr: errUnknownCondObjectType,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
r := new(policyengine.Rule)
err := ParseAPERule(r, test.rule)
require.ErrorIs(t, err, test.expectErr)
if test.expectErr == nil {
require.Equal(t, test.expectRule, *r)
}
})
}
}

View file

@ -20,6 +20,10 @@ const (
rpcStopShardEvacuation = "StopShardEvacuation" rpcStopShardEvacuation = "StopShardEvacuation"
rpcFlushCache = "FlushCache" rpcFlushCache = "FlushCache"
rpcDoctor = "Doctor" rpcDoctor = "Doctor"
rpcAddChainLocalOverride = "AddChainLocalOverride"
rpcGetChainLocalOverride = "GetChainLocalOverride"
rpcListChainLocalOverrides = "ListChainLocalOverrides"
rpcRemoveChainLocalOverride = "RemoveChainLocalOverride"
) )
// HealthCheck executes ControlService.HealthCheck RPC. // HealthCheck executes ControlService.HealthCheck RPC.
@ -208,3 +212,55 @@ func Doctor(cli *client.Client, req *DoctorRequest, opts ...client.CallOption) (
return wResp.message, nil return wResp.message, nil
} }
// AddChainLocalOverride executes ControlService.AddChainLocalOverride RPC.
func AddChainLocalOverride(cli *client.Client, req *AddChainLocalOverrideRequest, opts ...client.CallOption) (*AddChainLocalOverrideResponse, error) {
wResp := newResponseWrapper[AddChainLocalOverrideResponse]()
wReq := &requestWrapper{m: req}
err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceName, rpcAddChainLocalOverride), wReq, wResp, opts...)
if err != nil {
return nil, err
}
return wResp.message, nil
}
// ListChainLocalOverrides executes ControlService.ListChainLocalOverrides RPC.
func ListChainLocalOverrides(cli *client.Client, req *ListChainLocalOverridesRequest, opts ...client.CallOption) (*ListChainLocalOverridesResponse, error) {
wResp := newResponseWrapper[ListChainLocalOverridesResponse]()
wReq := &requestWrapper{m: req}
err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceName, rpcListChainLocalOverrides), wReq, wResp, opts...)
if err != nil {
return nil, err
}
return wResp.message, nil
}
// RemoveChainLocalOverride executes ControlService.RemoveChainLocalOverride RPC.
func GetChainLocalOverride(cli *client.Client, req *GetChainLocalOverrideRequest, opts ...client.CallOption) (*GetChainLocalOverrideResponse, error) {
wResp := newResponseWrapper[GetChainLocalOverrideResponse]()
wReq := &requestWrapper{m: req}
err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceName, rpcGetChainLocalOverride), wReq, wResp, opts...)
if err != nil {
return nil, err
}
return wResp.message, nil
}
// RemoveChainLocalOverride executes ControlService.RemoveChainLocalOverride RPC.
func RemoveChainLocalOverride(cli *client.Client, req *RemoveChainLocalOverrideRequest, opts ...client.CallOption) (*RemoveChainLocalOverrideResponse, error) {
wResp := newResponseWrapper[RemoveChainLocalOverrideResponse]()
wReq := &requestWrapper{m: req}
err := client.SendUnary(cli, common.CallMethodInfoUnary(serviceName, rpcRemoveChainLocalOverride), wReq, wResp, opts...)
if err != nil {
return nil, err
}
return wResp.message, nil
}

View file

@ -5,20 +5,144 @@ import (
"fmt" "fmt"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/services/control"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
policyengine "git.frostfs.info/TrueCloudLab/policy-engine"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
) )
func (s *Server) AddChainLocalOverride(ctx context.Context, req *control.AddChainLocalOverrideRequest) (*control.AddChainLocalOverrideResponse, error) { func (s *Server) AddChainLocalOverride(_ context.Context, req *control.AddChainLocalOverrideRequest) (*control.AddChainLocalOverrideResponse, error) {
return nil, fmt.Errorf("not implemented yet") if err := s.isValidRequest(req); err != nil {
return nil, status.Error(codes.PermissionDenied, err.Error())
}
var cid cid.ID
err := cid.Decode(req.GetBody().GetContainerId())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
var chain policyengine.Chain
if err = chain.DecodeBytes(req.GetBody().GetChain()); err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
src, err := s.apeChainSrc.GetChainSource(cid)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
s.apeChainCounter.Add(1)
// TODO (aarifullin): the such chain id is not well-designed yet.
chain.ID = policyengine.ChainID(fmt.Sprintf("%s:%d", policyengine.Ingress, s.apeChainCounter.Load()))
src.AddOverride(policyengine.Ingress, &chain)
resp := &control.AddChainLocalOverrideResponse{
Body: &control.AddChainLocalOverrideResponse_Body{
ChainId: string(chain.ID),
},
}
err = SignMessage(s.key, resp)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return resp, nil
} }
func (s *Server) GetChainLocalOverride(ctx context.Context, req *control.GetChainLocalOverrideRequest) (*control.GetChainLocalOverrideResponse, error) { func (s *Server) GetChainLocalOverride(_ context.Context, req *control.GetChainLocalOverrideRequest) (*control.GetChainLocalOverrideResponse, error) {
return nil, fmt.Errorf("not implemented yet") if err := s.isValidRequest(req); err != nil {
return nil, status.Error(codes.PermissionDenied, err.Error())
}
var cid cid.ID
err := cid.Decode(req.GetBody().GetContainerId())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
src, err := s.apeChainSrc.GetChainSource(cid)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
chain, found := src.GetOverride(policyengine.Ingress, policyengine.ChainID(req.GetBody().GetChainId()))
if !found {
err = fmt.Errorf("local override has not been found")
return nil, status.Error(codes.NotFound, err.Error())
}
resp := &control.GetChainLocalOverrideResponse{
Body: &control.GetChainLocalOverrideResponse_Body{
Chain: chain.Bytes(),
},
}
err = SignMessage(s.key, resp)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return resp, nil
} }
func (s *Server) ListChainLocalOverrides(ctx context.Context, req *control.ListChainLocalOverridesRequest) (*control.ListChainLocalOverridesResponse, error) { func (s *Server) ListChainLocalOverrides(_ context.Context, req *control.ListChainLocalOverridesRequest) (*control.ListChainLocalOverridesResponse, error) {
return nil, fmt.Errorf("not implemented yet") if err := s.isValidRequest(req); err != nil {
return nil, status.Error(codes.PermissionDenied, err.Error())
}
var cid cid.ID
err := cid.Decode(req.GetBody().GetContainerId())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
src, err := s.apeChainSrc.GetChainSource(cid)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
chains := src.ListOverrides(policyengine.Ingress)
serializedChains := make([][]byte, 0, len(chains))
for _, chain := range chains {
serializedChains = append(serializedChains, chain.Bytes())
}
resp := &control.ListChainLocalOverridesResponse{
Body: &control.ListChainLocalOverridesResponse_Body{
Chains: serializedChains,
},
}
err = SignMessage(s.key, resp)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return resp, nil
} }
func (s *Server) RemoveChainLocalOverride(ctx context.Context, req *control.RemoveChainLocalOverrideRequest) (*control.RemoveChainLocalOverrideResponse, error) { func (s *Server) RemoveChainLocalOverride(_ context.Context, req *control.RemoveChainLocalOverrideRequest) (*control.RemoveChainLocalOverrideResponse, error) {
return nil, fmt.Errorf("not implemented yet") if err := s.isValidRequest(req); err != nil {
return nil, status.Error(codes.PermissionDenied, err.Error())
}
var cid cid.ID
err := cid.Decode(req.GetBody().GetContainerId())
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}
src, err := s.apeChainSrc.GetChainSource(cid)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
removed := src.RemoveOverride(policyengine.Ingress, policyengine.ChainID(req.GetBody().GetChainId()))
resp := &control.RemoveChainLocalOverrideResponse{
Body: &control.RemoveChainLocalOverrideResponse_Body{
Removed: removed,
},
}
err = SignMessage(s.key, resp)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return resp, nil
} }

View file

@ -2,6 +2,7 @@ package control
import ( import (
"crypto/ecdsa" "crypto/ecdsa"
"sync/atomic"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/container"
"git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap" "git.frostfs.info/TrueCloudLab/frostfs-node/pkg/core/netmap"
@ -14,6 +15,11 @@ import (
// Control service on storage node. // Control service on storage node.
type Server struct { type Server struct {
*cfg *cfg
// TODO (aarifullin): this counter is used to assign id for rule chains
// added as local overrides and will be removed as soon as in-memory
// implementation will be replaced.
apeChainCounter atomic.Uint32
} }
// HealthChecker is component interface for calculating // HealthChecker is component interface for calculating

View file

@ -218,10 +218,6 @@ func (b Service) Head(
reqInfo.obj = obj reqInfo.obj = obj
if err := b.apeChecker.CheckIfRequestPermitted(reqInfo); err != nil {
return nil, err
}
resp, err := b.next.Head(ctx, request) resp, err := b.next.Head(ctx, request)
if err == nil { if err == nil {
if err = b.checker.CheckEACL(resp, reqInfo); err != nil { if err = b.checker.CheckEACL(resp, reqInfo); err != nil {