forked from TrueCloudLab/frostfs-node
[#2012] Add commands neofs-cli acl basic/extended print
to show ACL table in human readable format
Signed-off-by: Anton Nikiforov <an.nikiforov@yadro.com>
This commit is contained in:
parent
59db66cdb6
commit
8a77b4638a
12 changed files with 428 additions and 166 deletions
|
@ -11,10 +11,12 @@ Changelog for NeoFS Node
|
|||
- Background workers and object service now use separate client caches (#2048)
|
||||
- `replicator.pool_size` config field to tune replicator pool size (#2049)
|
||||
- Fix NNS hash parsing in morph client (#2063)
|
||||
- `neofs-cli neofs-cli acl basic/extended print` commands (#2012)
|
||||
|
||||
### Changed
|
||||
- `object lock` command reads CID and OID the same way other commands do (#1971)
|
||||
- `LOCK` object are stored on every container node (#1502)
|
||||
- `neofs-cli container get-eacl` print ACL table in json format only with arg `--json' (#2012)
|
||||
|
||||
### Fixed
|
||||
- Open FSTree in sync mode by default (#1992)
|
||||
|
|
28
cmd/neofs-cli/modules/acl/basic/print.go
Normal file
28
cmd/neofs-cli/modules/acl/basic/print.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package basic
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common"
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/util"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var printACLCmd = &cobra.Command{
|
||||
Use: "print",
|
||||
Short: "Pretty print basic ACL from the HEX representation",
|
||||
Example: `neofs-cli acl basic print 0x1C8C8CCC`,
|
||||
Long: `Pretty print basic ACL from the HEX representation.
|
||||
Few roles have exclusive default access to set of operation, even if particular bit deny it.
|
||||
Container have access to the operations of the data replication mechanism:
|
||||
Get, Head, Put, Search, Hash.
|
||||
InnerRing members are allowed to data audit ops only:
|
||||
Get, Head, Hash, Search.`,
|
||||
Run: printACL,
|
||||
Args: cobra.ExactArgs(1),
|
||||
}
|
||||
|
||||
func printACL(cmd *cobra.Command, args []string) {
|
||||
var bacl acl.Basic
|
||||
common.ExitOnErr(cmd, "unable to parse basic acl: %w", bacl.DecodeString(args[0]))
|
||||
util.PrettyPrintTableBACL(cmd, &bacl)
|
||||
}
|
14
cmd/neofs-cli/modules/acl/basic/root.go
Normal file
14
cmd/neofs-cli/modules/acl/basic/root.go
Normal file
|
@ -0,0 +1,14 @@
|
|||
package basic
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "basic",
|
||||
Short: "Operations with Basic Access Control Lists",
|
||||
}
|
||||
|
||||
func init() {
|
||||
Cmd.AddCommand(printACLCmd)
|
||||
}
|
|
@ -2,16 +2,13 @@ package extended
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/flynn-archive/go-shlex"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common"
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags"
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/util"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
"github.com/spf13/cobra"
|
||||
|
@ -52,7 +49,7 @@ neofs-cli acl extended create --cid EutHBsdT1YCzHxjCfQHnLPL1vFrkSyLSio4vkphfnEk
|
|||
|
||||
func init() {
|
||||
createCmd.Flags().StringArrayP("rule", "r", nil, "Extended ACL table record to apply")
|
||||
createCmd.Flags().StringP("file", "f", "", "Read list of extended ACL table records from from text file")
|
||||
createCmd.Flags().StringP("file", "f", "", "Read list of extended ACL table records from text file")
|
||||
createCmd.Flags().StringP("out", "o", "", "Save JSON formatted extended ACL table in file")
|
||||
createCmd.Flags().StringP(commonflags.CIDFlag, "", "", commonflags.CIDFlagUsage)
|
||||
|
||||
|
@ -87,20 +84,7 @@ func createEACL(cmd *cobra.Command, _ []string) {
|
|||
}
|
||||
|
||||
tb := eacl.NewTable()
|
||||
|
||||
for _, ruleStr := range rules {
|
||||
r, err := shlex.Split(ruleStr)
|
||||
if err != nil {
|
||||
cmd.PrintErrf("can't parse rule '%s': %v\n", ruleStr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
err = parseTable(tb, r)
|
||||
if err != nil {
|
||||
cmd.PrintErrf("can't create extended ACL record from rule '%s': %v\n", ruleStr, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
common.ExitOnErr(cmd, "unable to parse provided rules: %w", util.ParseEACLRules(tb, rules))
|
||||
|
||||
tb.SetCID(containerID)
|
||||
|
||||
|
@ -141,144 +125,3 @@ func getRulesFromFile(filename string) ([]string, error) {
|
|||
|
||||
return strings.Split(strings.TrimSpace(string(data)), "\n"), nil
|
||||
}
|
||||
|
||||
// parseTable parses eACL table from the following form:
|
||||
// <action> <operation> [<filter1> ...] [<target1> ...]
|
||||
//
|
||||
// Examples:
|
||||
// allow get req:X-Header=123 obj:Attr=value others:0xkey1,key2 system:key3 user:key4
|
||||
//
|
||||
//nolint:godot
|
||||
func parseTable(tb *eacl.Table, args []string) error {
|
||||
if len(args) < 2 {
|
||||
return errors.New("at least 2 arguments must be provided")
|
||||
}
|
||||
|
||||
var action eacl.Action
|
||||
if !action.FromString(strings.ToUpper(args[0])) {
|
||||
return errors.New("invalid action (expected 'allow' or 'deny')")
|
||||
}
|
||||
|
||||
ops, err := parseOperations(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := parseRecord(args[2:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.SetAction(action)
|
||||
|
||||
for _, op := range ops {
|
||||
r := *r
|
||||
r.SetOperation(op)
|
||||
tb.AddRecord(&r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseRecord(args []string) (*eacl.Record, error) {
|
||||
r := new(eacl.Record)
|
||||
for i := range args {
|
||||
ss := strings.SplitN(args[i], ":", 2)
|
||||
|
||||
switch prefix := strings.ToLower(ss[0]); prefix {
|
||||
case "req", "obj": // filters
|
||||
if len(ss) != 2 {
|
||||
return nil, fmt.Errorf("invalid filter or target: %s", args[i])
|
||||
}
|
||||
|
||||
i := strings.Index(ss[1], "=")
|
||||
if i < 0 {
|
||||
return nil, fmt.Errorf("invalid filter key-value pair: %s", ss[1])
|
||||
}
|
||||
|
||||
var key, value string
|
||||
var op eacl.Match
|
||||
|
||||
if 0 < i && ss[1][i-1] == '!' {
|
||||
key = ss[1][:i-1]
|
||||
op = eacl.MatchStringNotEqual
|
||||
} else {
|
||||
key = ss[1][:i]
|
||||
op = eacl.MatchStringEqual
|
||||
}
|
||||
|
||||
value = ss[1][i+1:]
|
||||
|
||||
typ := eacl.HeaderFromRequest
|
||||
if ss[0] == "obj" {
|
||||
typ = eacl.HeaderFromObject
|
||||
}
|
||||
|
||||
r.AddFilter(typ, op, key, value)
|
||||
case "others", "system", "user", "pubkey": // targets
|
||||
var err error
|
||||
|
||||
var pubs []ecdsa.PublicKey
|
||||
if len(ss) == 2 {
|
||||
pubs, err = parseKeyList(ss[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var role eacl.Role
|
||||
if prefix != "pubkey" {
|
||||
role, err = roleFromString(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
eacl.AddFormedTarget(r, role, pubs...)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid prefix: %s", ss[0])
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func roleFromString(s string) (eacl.Role, error) {
|
||||
var r eacl.Role
|
||||
if !r.FromString(strings.ToUpper(s)) {
|
||||
return r, fmt.Errorf("unexpected role %s", s)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// parseKeyList parses list of hex-encoded public keys separated by comma.
|
||||
func parseKeyList(s string) ([]ecdsa.PublicKey, error) {
|
||||
ss := strings.Split(s, ",")
|
||||
pubs := make([]ecdsa.PublicKey, len(ss))
|
||||
for i := range ss {
|
||||
st := strings.TrimPrefix(ss[i], "0x")
|
||||
pub, err := keys.NewPublicKeyFromString(st)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid public key '%s': %w", ss[i], err)
|
||||
}
|
||||
|
||||
pubs[i] = ecdsa.PublicKey(*pub)
|
||||
}
|
||||
|
||||
return pubs, nil
|
||||
}
|
||||
|
||||
func parseOperations(s string) ([]eacl.Operation, error) {
|
||||
ss := strings.Split(s, ",")
|
||||
ops := make([]eacl.Operation, len(ss))
|
||||
|
||||
for i := range ss {
|
||||
if !ops[i].FromString(strings.ToUpper(ss[i])) {
|
||||
return nil, fmt.Errorf("invalid operation: %s", ss[i])
|
||||
}
|
||||
}
|
||||
|
||||
return ops, nil
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package extended
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/util"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
@ -63,8 +63,7 @@ func TestParseTable(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ss := strings.Split(test.rule, " ")
|
||||
err := parseTable(eaclTable, ss)
|
||||
err := util.ParseEACLRule(eaclTable, test.rule)
|
||||
ok := len(test.jsonRecord) > 0
|
||||
require.Equal(t, ok, err == nil, err)
|
||||
if ok {
|
||||
|
|
38
cmd/neofs-cli/modules/acl/extended/print.go
Normal file
38
cmd/neofs-cli/modules/acl/extended/print.go
Normal file
|
@ -0,0 +1,38 @@
|
|||
package extended
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common"
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/util"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var printEACLCmd = &cobra.Command{
|
||||
Use: "print",
|
||||
Short: "Pretty print extended ACL from the file(in text or json format) or for given container.",
|
||||
Run: printEACL,
|
||||
}
|
||||
|
||||
func init() {
|
||||
flags := printEACLCmd.Flags()
|
||||
flags.StringP("file", "f", "",
|
||||
"Read list of extended ACL table records from text or json file")
|
||||
_ = printEACLCmd.MarkFlagRequired("file")
|
||||
}
|
||||
|
||||
func printEACL(cmd *cobra.Command, _ []string) {
|
||||
file, _ := cmd.Flags().GetString("file")
|
||||
eaclTable := new(eacl.Table)
|
||||
data, err := os.ReadFile(file)
|
||||
common.ExitOnErr(cmd, "can't read file with EACL: %w", err)
|
||||
if strings.HasSuffix(file, ".json") {
|
||||
common.ExitOnErr(cmd, "unable to parse json: %w", eaclTable.UnmarshalJSON(data))
|
||||
} else {
|
||||
rules := strings.Split(strings.TrimSpace(string(data)), "\n")
|
||||
common.ExitOnErr(cmd, "can't parse file with EACL: %w", util.ParseEACLRules(eaclTable, rules))
|
||||
}
|
||||
util.PrettyPrintTableEACL(cmd, eaclTable)
|
||||
}
|
|
@ -1,6 +1,8 @@
|
|||
package extended
|
||||
|
||||
import "github.com/spf13/cobra"
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var Cmd = &cobra.Command{
|
||||
Use: "extended",
|
||||
|
@ -9,4 +11,5 @@ var Cmd = &cobra.Command{
|
|||
|
||||
func init() {
|
||||
Cmd.AddCommand(createCmd)
|
||||
Cmd.AddCommand(printEACLCmd)
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package acl
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/acl/basic"
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/acl/extended"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
@ -12,4 +13,5 @@ var Cmd = &cobra.Command{
|
|||
|
||||
func init() {
|
||||
Cmd.AddCommand(extended.Cmd)
|
||||
Cmd.AddCommand(basic.Cmd)
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/common"
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/commonflags"
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/internal/key"
|
||||
"github.com/nspcc-dev/neofs-node/cmd/neofs-cli/modules/util"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
|
@ -141,6 +142,7 @@ func prettyPrintBasicACL(cmd *cobra.Command, basicACL acl.Basic) {
|
|||
}
|
||||
|
||||
cmd.Println()
|
||||
util.PrettyPrintTableBACL(cmd, &basicACL)
|
||||
}
|
||||
|
||||
func getContainer(cmd *cobra.Command) (container.Container, *ecdsa.PrivateKey) {
|
||||
|
|
330
cmd/neofs-cli/modules/util/acl.go
Normal file
330
cmd/neofs-cli/modules/util/acl.go
Normal file
|
@ -0,0 +1,330 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/flynn-archive/go-shlex"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container/acl"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/eacl"
|
||||
"github.com/olekukonko/tablewriter"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// PrettyPrintTableBACL print basic ACL in table format.
|
||||
func PrettyPrintTableBACL(cmd *cobra.Command, bacl *acl.Basic) {
|
||||
// Header
|
||||
w := tabwriter.NewWriter(cmd.OutOrStdout(), 1, 4, 4, ' ', 0)
|
||||
fmt.Fprintln(w, "\tRangeHASH\tRange\tSearch\tDelete\tPut\tHead\tGet")
|
||||
// Bits
|
||||
bits := []string{
|
||||
boolToString(bacl.Sticky()) + " " + boolToString(bacl.Extendable()),
|
||||
getRoleBitsForOperation(bacl, acl.OpObjectHash), getRoleBitsForOperation(bacl, acl.OpObjectRange),
|
||||
getRoleBitsForOperation(bacl, acl.OpObjectSearch), getRoleBitsForOperation(bacl, acl.OpObjectDelete),
|
||||
getRoleBitsForOperation(bacl, acl.OpObjectPut), getRoleBitsForOperation(bacl, acl.OpObjectHead),
|
||||
getRoleBitsForOperation(bacl, acl.OpObjectGet),
|
||||
}
|
||||
fmt.Fprintln(w, strings.Join(bits, "\t"))
|
||||
// Footer
|
||||
footer := []string{"X F"}
|
||||
for i := 0; i < 7; i++ {
|
||||
footer = append(footer, "U S O B")
|
||||
}
|
||||
fmt.Fprintln(w, strings.Join(footer, "\t"))
|
||||
|
||||
w.Flush()
|
||||
|
||||
cmd.Println(" X-Sticky F-Final U-User S-System O-Others B-Bearer")
|
||||
}
|
||||
|
||||
func getRoleBitsForOperation(bacl *acl.Basic, op acl.Op) string {
|
||||
return boolToString(bacl.IsOpAllowed(op, acl.RoleOwner)) + " " +
|
||||
boolToString(bacl.IsOpAllowed(op, acl.RoleContainer)) + " " +
|
||||
boolToString(bacl.IsOpAllowed(op, acl.RoleOthers)) + " " +
|
||||
boolToString(bacl.IsOpAllowed(op, acl.RoleInnerRing))
|
||||
}
|
||||
|
||||
func boolToString(b bool) string {
|
||||
if b {
|
||||
return "1"
|
||||
}
|
||||
return "0"
|
||||
}
|
||||
|
||||
// PrettyPrintTableEACL print extended ACL in table format.
|
||||
func PrettyPrintTableEACL(cmd *cobra.Command, table *eacl.Table) {
|
||||
out := tablewriter.NewWriter(cmd.OutOrStdout())
|
||||
out.SetHeader([]string{"Operation", "Action", "Filters", "Targets"})
|
||||
out.SetAlignment(tablewriter.ALIGN_CENTER)
|
||||
out.SetRowLine(true)
|
||||
|
||||
out.SetAutoWrapText(false)
|
||||
|
||||
for _, r := range table.Records() {
|
||||
out.Append([]string{
|
||||
r.Operation().String(),
|
||||
r.Action().String(),
|
||||
eaclFiltersToString(r.Filters()),
|
||||
eaclTargetsToString(r.Targets()),
|
||||
})
|
||||
}
|
||||
|
||||
out.Render()
|
||||
}
|
||||
|
||||
func eaclTargetsToString(ts []eacl.Target) string {
|
||||
b := bytes.NewBuffer(nil)
|
||||
for _, t := range ts {
|
||||
keysExists := len(t.BinaryKeys()) > 0
|
||||
switch t.Role() {
|
||||
case eacl.RoleUser:
|
||||
b.WriteString("User")
|
||||
if keysExists {
|
||||
b.WriteString(": ")
|
||||
}
|
||||
case eacl.RoleSystem:
|
||||
b.WriteString("System")
|
||||
if keysExists {
|
||||
b.WriteString(": ")
|
||||
}
|
||||
case eacl.RoleOthers:
|
||||
b.WriteString("Others")
|
||||
if keysExists {
|
||||
b.WriteString(": ")
|
||||
}
|
||||
default:
|
||||
b.WriteString("Unknown")
|
||||
if keysExists {
|
||||
b.WriteString(": ")
|
||||
}
|
||||
}
|
||||
|
||||
for i, pub := range t.BinaryKeys() {
|
||||
if i != 0 {
|
||||
b.WriteString(" ")
|
||||
}
|
||||
b.WriteString(hex.EncodeToString(pub))
|
||||
b.WriteString("\n")
|
||||
}
|
||||
}
|
||||
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func eaclFiltersToString(fs []eacl.Filter) string {
|
||||
b := bytes.NewBuffer(nil)
|
||||
tw := tabwriter.NewWriter(b, 0, 0, 1, ' ', 0)
|
||||
|
||||
for _, f := range fs {
|
||||
switch f.From() {
|
||||
case eacl.HeaderFromObject:
|
||||
_, _ = tw.Write([]byte("O:\t"))
|
||||
case eacl.HeaderFromRequest:
|
||||
_, _ = tw.Write([]byte("R:\t"))
|
||||
case eacl.HeaderFromService:
|
||||
_, _ = tw.Write([]byte("S:\t"))
|
||||
default:
|
||||
_, _ = tw.Write([]byte(" \t"))
|
||||
}
|
||||
|
||||
_, _ = tw.Write([]byte(f.Key()))
|
||||
|
||||
switch f.Matcher() {
|
||||
case eacl.MatchStringEqual:
|
||||
_, _ = tw.Write([]byte("\t==\t"))
|
||||
case eacl.MatchStringNotEqual:
|
||||
_, _ = tw.Write([]byte("\t!=\t"))
|
||||
case eacl.MatchUnknown:
|
||||
}
|
||||
|
||||
_, _ = tw.Write([]byte(f.Value() + "\t"))
|
||||
_, _ = tw.Write([]byte("\n"))
|
||||
}
|
||||
|
||||
_ = tw.Flush()
|
||||
|
||||
// To have nice output with tabwriter, we must append newline
|
||||
// after the last line. Here we strip it to delete empty line
|
||||
// in the final output.
|
||||
s := b.String()
|
||||
if len(s) > 0 {
|
||||
s = s[:len(s)-1]
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// ParseEACLRules parses eACL table.
|
||||
// Uses ParseEACLRule.
|
||||
//
|
||||
//nolint:godot
|
||||
func ParseEACLRules(table *eacl.Table, rules []string) error {
|
||||
if len(rules) == 0 {
|
||||
return errors.New("no extended ACL rules has been provided")
|
||||
}
|
||||
|
||||
for _, ruleStr := range rules {
|
||||
err := ParseEACLRule(table, ruleStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't create extended acl record from rule '%s': %v", ruleStr, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ParseEACLRule parses eACL table from the following form:
|
||||
// <action> <operation> [<filter1> ...] [<target1> ...]
|
||||
//
|
||||
// Examples:
|
||||
// allow get req:X-Header=123 obj:Attr=value others:0xkey1,key2 system:key3 user:key4
|
||||
//
|
||||
//nolint:godot
|
||||
func ParseEACLRule(table *eacl.Table, rule string) error {
|
||||
r, err := shlex.Split(rule)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't parse rule '%s': %v", rule, err)
|
||||
}
|
||||
return parseEACLTable(table, r)
|
||||
}
|
||||
|
||||
func parseEACLTable(tb *eacl.Table, args []string) error {
|
||||
if len(args) < 2 {
|
||||
return errors.New("at least 2 arguments must be provided")
|
||||
}
|
||||
|
||||
var action eacl.Action
|
||||
if !action.FromString(strings.ToUpper(args[0])) {
|
||||
return errors.New("invalid action (expected 'allow' or 'deny')")
|
||||
}
|
||||
|
||||
ops, err := eaclOperationsFromString(args[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r, err := parseEACLRecord(args[2:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
r.SetAction(action)
|
||||
|
||||
for _, op := range ops {
|
||||
r := *r
|
||||
r.SetOperation(op)
|
||||
tb.AddRecord(&r)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseEACLRecord(args []string) (*eacl.Record, error) {
|
||||
r := new(eacl.Record)
|
||||
for i := range args {
|
||||
ss := strings.SplitN(args[i], ":", 2)
|
||||
|
||||
switch prefix := strings.ToLower(ss[0]); prefix {
|
||||
case "req", "obj": // filters
|
||||
if len(ss) != 2 {
|
||||
return nil, fmt.Errorf("invalid filter or target: %s", args[i])
|
||||
}
|
||||
|
||||
i := strings.Index(ss[1], "=")
|
||||
if i < 0 {
|
||||
return nil, fmt.Errorf("invalid filter key-value pair: %s", ss[1])
|
||||
}
|
||||
|
||||
var key, value string
|
||||
var op eacl.Match
|
||||
|
||||
if 0 < i && ss[1][i-1] == '!' {
|
||||
key = ss[1][:i-1]
|
||||
op = eacl.MatchStringNotEqual
|
||||
} else {
|
||||
key = ss[1][:i]
|
||||
op = eacl.MatchStringEqual
|
||||
}
|
||||
|
||||
value = ss[1][i+1:]
|
||||
|
||||
typ := eacl.HeaderFromRequest
|
||||
if ss[0] == "obj" {
|
||||
typ = eacl.HeaderFromObject
|
||||
}
|
||||
|
||||
r.AddFilter(typ, op, key, value)
|
||||
case "others", "system", "user", "pubkey": // targets
|
||||
var err error
|
||||
|
||||
var pubs []ecdsa.PublicKey
|
||||
if len(ss) == 2 {
|
||||
pubs, err = parseKeyList(ss[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
var role eacl.Role
|
||||
if prefix != "pubkey" {
|
||||
role, err = eaclRoleFromString(prefix)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
eacl.AddFormedTarget(r, role, pubs...)
|
||||
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid prefix: %s", ss[0])
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// eaclRoleFromString parses eacl.Role from string.
|
||||
func eaclRoleFromString(s string) (eacl.Role, error) {
|
||||
var r eacl.Role
|
||||
if !r.FromString(strings.ToUpper(s)) {
|
||||
return r, fmt.Errorf("unexpected role %s", s)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// parseKeyList parses list of hex-encoded public keys separated by comma.
|
||||
func parseKeyList(s string) ([]ecdsa.PublicKey, error) {
|
||||
ss := strings.Split(s, ",")
|
||||
pubs := make([]ecdsa.PublicKey, len(ss))
|
||||
for i := range ss {
|
||||
st := strings.TrimPrefix(ss[i], "0x")
|
||||
pub, err := keys.NewPublicKeyFromString(st)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid public key '%s': %w", ss[i], err)
|
||||
}
|
||||
|
||||
pubs[i] = ecdsa.PublicKey(*pub)
|
||||
}
|
||||
|
||||
return pubs, nil
|
||||
}
|
||||
|
||||
// eaclOperationsFromString parses list of eacl.Operation separated by comma.
|
||||
func eaclOperationsFromString(s string) ([]eacl.Operation, error) {
|
||||
ss := strings.Split(s, ",")
|
||||
ops := make([]eacl.Operation, len(ss))
|
||||
|
||||
for i := range ss {
|
||||
if !ops[i].FromString(strings.ToUpper(ss[i])) {
|
||||
return nil, fmt.Errorf("invalid operation: %s", ss[i])
|
||||
}
|
||||
}
|
||||
|
||||
return ops, nil
|
||||
}
|
3
go.mod
3
go.mod
|
@ -21,6 +21,7 @@ require (
|
|||
github.com/nspcc-dev/neofs-contract v0.16.0
|
||||
github.com/nspcc-dev/neofs-sdk-go v1.0.0-rc.7
|
||||
github.com/nspcc-dev/tzhash v1.6.1
|
||||
github.com/olekukonko/tablewriter v0.0.5
|
||||
github.com/panjf2000/ants/v2 v2.4.0
|
||||
github.com/paulmach/orb v0.2.2
|
||||
github.com/prometheus/client_golang v1.13.0
|
||||
|
@ -57,7 +58,7 @@ require (
|
|||
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||
github.com/ipfs/go-cid v0.0.7 // indirect
|
||||
github.com/magiconair/properties v1.8.6 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.4 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.9 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
|
||||
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1 // indirect
|
||||
github.com/minio/sha256-simd v0.1.1-0.20190913151208-6de447530771 // indirect
|
||||
|
|
BIN
go.sum
BIN
go.sum
Binary file not shown.
Loading…
Reference in a new issue