Skip to content

Commit 3e2169b

Browse files
committed
innerring: Prepare a platform for private attributes of storage nodes
Private node attribute are coming. The term private means that the system controls access to the declaration of certain attributes by storage nodes. This feature will allow you to prevent unauthorized installation of attributes that semantically require confirmation. Until now, storage nodes were free to set format-valid attributes. Therefore, to prepare for the feature arrival, a new attribute validator (`netmap.NodeValidator`) is introduced: it fakes the real validation and allows all nodes to access all attributes. In the future, verification will be tightened. Refs #2280. Signed-off-by: Leonard Lyubich <[email protected]>
1 parent 3749ce7 commit 3e2169b

File tree

4 files changed

+337
-0
lines changed

4 files changed

+337
-0
lines changed

pkg/innerring/innerring.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"github.com/nspcc-dev/neofs-node/pkg/innerring/processors/neofs"
2424
"github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap"
2525
nodevalidator "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation"
26+
attributevalidator "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation/attributes"
2627
availabilityvalidator "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation/availability"
2728
statevalidation "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation/state"
2829
addrvalidator "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation/structure"
@@ -730,6 +731,8 @@ func New(ctx context.Context, log *zap.Logger, cfg *viper.Viper, errChan chan<-
730731
var netMapCandidateStateValidator statevalidation.NetMapCandidateValidator
731732
netMapCandidateStateValidator.SetNetworkSettings(netSettings)
732733

734+
var attributeAccessCtrl publicNodeAttributesAccessController
735+
733736
// create netmap processor
734737
server.netmapProcessor, err = netmap.New(&netmap.Params{
735738
Log: log,
@@ -755,6 +758,7 @@ func New(ctx context.Context, log *zap.Logger, cfg *viper.Viper, errChan chan<-
755758
&netMapCandidateStateValidator,
756759
addrvalidator.New(),
757760
availabilityvalidator.New(),
761+
attributevalidator.New(attributeAccessCtrl),
758762
locodeValidator,
759763
),
760764
NodeStateSettings: netSettings,

pkg/innerring/netmap.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation/state"
77
netmapclient "github.com/nspcc-dev/neofs-node/pkg/morph/client/netmap"
8+
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
89
)
910

1011
/*
@@ -27,3 +28,11 @@ func (s *networkSettings) MaintenanceModeAllowed() error {
2728

2829
return state.ErrMaintenanceModeDisallowed
2930
}
31+
32+
// [attributevalidator.AttributeAccessController] that allows all nodes to
33+
// freely use any attributes.
34+
type publicNodeAttributesAccessController struct{}
35+
36+
func (x publicNodeAttributesAccessController) CheckNodeAccessToAttribute(_ neofscrypto.PublicKey, _, _ string) error {
37+
return nil
38+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package attributevalidator
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"unicode/utf8"
7+
8+
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
9+
neofsecdsa "github.com/nspcc-dev/neofs-sdk-go/crypto/ecdsa"
10+
"github.com/nspcc-dev/neofs-sdk-go/netmap"
11+
)
12+
13+
// ErrAccessDenied is returned when access is denied.
14+
var ErrAccessDenied = errors.New("node is not allowed to use attribute")
15+
16+
// AttributeAccessController provides access control over the use of attributes
17+
// by storage nodes.
18+
type AttributeAccessController interface {
19+
// CheckNodeAccessToAttribute checks whether storage node with the given key is
20+
// allowed to have specified key-value attribute or not. Returns
21+
// [ErrAccessDenied] when access is denied, or any other error encountered which
22+
// prevented access check.
23+
//
24+
// Key to the attribute is a non-empty UTF-8 string. Value of the attribute is
25+
// any non-empty string.
26+
CheckNodeAccessToAttribute(nodeKey neofscrypto.PublicKey, attrKey, attrValue string) error
27+
}
28+
29+
// Validator validates node attributes declared by the storage nodes on their
30+
// attempts to enter the NeoFS network map.
31+
type Validator struct {
32+
ctrl AttributeAccessController
33+
}
34+
35+
// New returns new Validator that checks storage nodes against provided
36+
// [AttributeAccessController].
37+
func New(ctrl AttributeAccessController) *Validator {
38+
return &Validator{
39+
ctrl: ctrl,
40+
}
41+
}
42+
43+
// various errors useful for testing.
44+
var (
45+
errInvalidNodeBinaryKey = errors.New("invalid node binary key")
46+
errEmptyAttributeKey = errors.New("empty attribute key")
47+
errEmptyAttributeValue = errors.New("empty value of the attribute")
48+
errNonUTF8AttributeKey = errors.New("attribute key is not a valid UTF-8 string")
49+
errDuplicatedAttributeKey = errors.New("duplicated attribute key")
50+
)
51+
52+
// VerifyAndUpdate checks allowance of the storage node represented by the given
53+
// descriptor to use declared attributes. Returns an error if at least one of
54+
// the attributes is forbidden to be used by the storage node or access check
55+
// cannot be done at the moment.
56+
//
57+
// VerifyAndUpdate also pre-checks following NeoFS protocol requirements:
58+
// - public key binary must be a compressed ECDSA public key
59+
// - all keys must be non-empty unique valid UTF-8 string
60+
// - all values must be non-empty strings
61+
//
62+
// VerifyAndUpdate does not mutate the argument.
63+
func (x *Validator) VerifyAndUpdate(info *netmap.NodeInfo) error {
64+
return x.verifyAndUpdate(info)
65+
}
66+
67+
// interface of [netmap.NodeInfo] used by [Validator] to bypass SDK protection
68+
// to compose obviously invalid instance. Needed for testing only.
69+
type nodeInfo interface {
70+
PublicKey() []byte
71+
NumberOfAttributes() int
72+
IterateAttributes(func(string, string))
73+
}
74+
75+
func (x *Validator) verifyAndUpdate(n nodeInfo) error {
76+
var nodePubKey neofsecdsa.PublicKey
77+
78+
err := nodePubKey.Decode(n.PublicKey())
79+
if err != nil {
80+
return fmt.Errorf("%w: %v", errInvalidNodeBinaryKey, err)
81+
}
82+
83+
mAttr := make(map[string]string, n.NumberOfAttributes())
84+
85+
n.IterateAttributes(func(key, value string) {
86+
if err != nil {
87+
return
88+
}
89+
90+
if key == "" {
91+
err = errEmptyAttributeKey
92+
return
93+
}
94+
95+
_, was := mAttr[key]
96+
if was {
97+
err = fmt.Errorf("%w %s", errDuplicatedAttributeKey, key)
98+
return
99+
}
100+
101+
if !utf8.ValidString(key) {
102+
err = fmt.Errorf("%w: %q", errNonUTF8AttributeKey, key)
103+
return
104+
}
105+
106+
if value == "" {
107+
err = fmt.Errorf("%w %q", errEmptyAttributeValue, value)
108+
return
109+
}
110+
111+
mAttr[key] = value
112+
})
113+
if err != nil {
114+
return err
115+
}
116+
117+
for k, v := range mAttr {
118+
err = x.ctrl.CheckNodeAccessToAttribute(&nodePubKey, k, v)
119+
if err != nil {
120+
if errors.Is(err, ErrAccessDenied) {
121+
return fmt.Errorf("%w %q=%q", ErrAccessDenied, k, v)
122+
}
123+
return fmt.Errorf("check access to use attribute %q=%q: %w", k, v, err)
124+
}
125+
}
126+
127+
return nil
128+
}
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
package attributevalidator
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"testing"
7+
"unicode/utf8"
8+
9+
neofscrypto "github.com/nspcc-dev/neofs-sdk-go/crypto"
10+
"github.com/nspcc-dev/neofs-sdk-go/crypto/test"
11+
"github.com/nspcc-dev/neofs-sdk-go/netmap"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
type testController struct {
16+
tb testing.TB
17+
18+
nodeKey neofscrypto.PublicKey
19+
mAttr map[string]string
20+
21+
mProcessedAttr map[string]struct{}
22+
23+
retErr error
24+
}
25+
26+
func newTestController(tb testing.TB, nodeKey neofscrypto.PublicKey, mAttr map[string]string) *testController {
27+
return &testController{
28+
tb: tb,
29+
nodeKey: nodeKey,
30+
mAttr: mAttr,
31+
mProcessedAttr: make(map[string]struct{}),
32+
}
33+
}
34+
35+
func (x *testController) setRetErr(err error) {
36+
x.retErr = err
37+
}
38+
39+
func (x *testController) CheckNodeAccessToAttribute(nodeKey neofscrypto.PublicKey, attrKey, attrValue string) error {
40+
require.Equal(x.tb, x.nodeKey, nodeKey, "node public key must be decoded from the original binary")
41+
require.NotEmpty(x.tb, attrKey, "empty attribute key must not be passed", attrKey)
42+
require.True(x.tb, utf8.ValidString(attrKey), "invalid UTF-8 attribute key must not be passed", attrKey)
43+
require.NotEmptyf(x.tb, attrValue, "empty attribute value must not be passed", attrValue)
44+
45+
_, ok := x.mProcessedAttr[attrKey]
46+
require.False(x.tb, ok, "attribute key must be passed once", attrKey)
47+
48+
x.mProcessedAttr[attrKey] = struct{}{}
49+
50+
val, ok := x.mAttr[attrKey]
51+
require.True(x.tb, ok, "attribute key must be from the original attributes", attrKey)
52+
require.Equal(x.tb, val, attrValue, "attribute value must be from the original attributes", attrValue)
53+
54+
return x.retErr
55+
}
56+
57+
func newNodeInfo(bNodeKey []byte, mAttr map[string]string) netmap.NodeInfo {
58+
var res netmap.NodeInfo
59+
res.SetPublicKey(bNodeKey)
60+
61+
for k, v := range mAttr {
62+
res.SetAttribute(k, v)
63+
}
64+
65+
return res
66+
}
67+
68+
type unprotectedNodeInfo struct {
69+
bNodeKey []byte
70+
attrs [][2]string
71+
}
72+
73+
func (x unprotectedNodeInfo) PublicKey() []byte {
74+
return x.bNodeKey
75+
}
76+
77+
func (x unprotectedNodeInfo) NumberOfAttributes() int {
78+
return len(x.attrs)
79+
}
80+
81+
func (x unprotectedNodeInfo) IterateAttributes(f func(string, string)) {
82+
for _, a := range x.attrs {
83+
f(a[0], a[1])
84+
}
85+
}
86+
87+
func newUnprotectedNodeInfo(bNodeKey []byte, attrs [][2]string) nodeInfo {
88+
return unprotectedNodeInfo{
89+
bNodeKey: bNodeKey,
90+
attrs: attrs,
91+
}
92+
}
93+
94+
func TestValidator_VerifyAndUpdate(t *testing.T) {
95+
nodeKey := test.RandomSigner(t).Public()
96+
bNodeKey := neofscrypto.PublicKeyBytes(nodeKey)
97+
98+
t.Run("invalid node key", func(t *testing.T) {
99+
mAttr := map[string]string{
100+
"any_key": "any_value",
101+
}
102+
103+
node := newNodeInfo([]byte("definitely not a key"), mAttr)
104+
ctrl := newTestController(t, nodeKey, mAttr)
105+
v := New(ctrl)
106+
107+
err := v.VerifyAndUpdate(&node)
108+
require.ErrorIs(t, err, errInvalidNodeBinaryKey)
109+
})
110+
111+
for _, tc := range []struct {
112+
desc string
113+
attrs [][2]string
114+
expectedErr error
115+
}{
116+
{
117+
desc: "empty key",
118+
attrs: [][2]string{{"", "any_value"}},
119+
expectedErr: errEmptyAttributeKey,
120+
},
121+
{
122+
desc: "not a UTF-8 key",
123+
attrs: [][2]string{
124+
{"\xed\xa0\x80", "any_value"}, // U+D800 UTF-16 high surrogate mentioned in RFC 3629
125+
},
126+
expectedErr: errNonUTF8AttributeKey,
127+
},
128+
{
129+
desc: "empty value",
130+
attrs: [][2]string{{"any_key", ""}},
131+
expectedErr: errEmptyAttributeValue,
132+
},
133+
{
134+
desc: "duplicated attribute",
135+
attrs: [][2]string{
136+
{"any_key", "val1"},
137+
{"any_key", "val2"},
138+
},
139+
expectedErr: errDuplicatedAttributeKey,
140+
},
141+
} {
142+
t.Run(fmt.Sprintf("invalid attributes (%s)", tc.desc), func(t *testing.T) {
143+
node := newUnprotectedNodeInfo(bNodeKey, tc.attrs)
144+
145+
mAttr := make(map[string]string)
146+
for _, a := range tc.attrs {
147+
mAttr[a[0]] = a[1]
148+
}
149+
150+
ctrl := newTestController(t, nodeKey, mAttr)
151+
v := New(ctrl)
152+
153+
err := v.verifyAndUpdate(node)
154+
require.ErrorIs(t, err, tc.expectedErr, tc.desc)
155+
})
156+
}
157+
158+
t.Run("failed check", func(t *testing.T) {
159+
checkErr := func(expectedErr error) {
160+
mAttr := map[string]string{
161+
"any_key1": "any_value2",
162+
"any_key2": "any_value2",
163+
}
164+
165+
node := newNodeInfo(bNodeKey, mAttr)
166+
ctrl := newTestController(t, nodeKey, mAttr)
167+
v := New(ctrl)
168+
169+
ctrl.setRetErr(expectedErr)
170+
err := v.VerifyAndUpdate(&node)
171+
require.ErrorIs(t, err, expectedErr)
172+
}
173+
174+
t.Run("access denied", func(t *testing.T) {
175+
checkErr(ErrAccessDenied)
176+
})
177+
178+
t.Run("other reason", func(t *testing.T) {
179+
anyErr := errors.New("any error")
180+
checkErr(anyErr)
181+
})
182+
})
183+
184+
mAttr := map[string]string{
185+
"any_key1": "any_value1",
186+
"any_key2": "any_value2",
187+
}
188+
189+
node := newNodeInfo(bNodeKey, mAttr)
190+
ctrl := newTestController(t, nodeKey, mAttr)
191+
v := New(ctrl)
192+
193+
err := v.VerifyAndUpdate(&node)
194+
require.NoError(t, err)
195+
require.Len(t, ctrl.mProcessedAttr, len(mAttr))
196+
}

0 commit comments

Comments
 (0)