-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ir: Support verified NNS domains of the storage nodes
From now, the Inner Ring checks any incoming node for permission to associate itself with optional private node group (kind of subnet). Access lists are stored in the NeoFS NNS. Closes #2280. Signed-off-by: Leonard Lyubich <[email protected]>
- Loading branch information
1 parent
750e993
commit 6ab5c7c
Showing
15 changed files
with
862 additions
and
17 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
# Verified domains of the NeoFS storage nodes | ||
|
||
Storage nodes declare information flexibly via key-value string attributes when | ||
applying to enter the NeoFS network map. In general, any attributes can be | ||
declared, however, some of them may be subject to restrictions. In particular, | ||
some parties may need to limit the relationship to them of any nodes of their | ||
public network. For example, an organization may need to deploy its storage | ||
nodes as a subnet of a public network to implement specific data storage | ||
strategies. In this example, the organization’s nodes will be “normal” for 3rd | ||
parties, while other nodes will not be able to enter the subnet without special | ||
permission at the system level. | ||
|
||
NeoFS implements solution of the described task through access lists managed | ||
within NeoFS NNS. | ||
|
||
## Access lists | ||
|
||
These lists are stored in the NeoFS NNS. Each party may register any available | ||
NNS domain and set records of `TXT` type with Neo addresses of the storage | ||
nodes. After the domain is registered, it becomes an alias to the subnet composed | ||
only from specified storage nodes. Any storage node trying to associate itself | ||
with this subnet while trying to enter the network must have public key | ||
presented in the access list. The Inner Ring will deny everyone else access to | ||
the network map. | ||
|
||
### Domain record format | ||
|
||
For each public key, a record is created - a structure with at least 3 fields: | ||
1. `ByteString` with name of the corresponding domain | ||
2. `Integer` that is `16` for TXT records (other record types are allowed but left unprocessed) | ||
3. `ByteString` with Neo address of the storage node's public key | ||
|
||
## Private subnet entrance | ||
|
||
By default, storage nodes do not belong to private groups. Any node wishing to | ||
enter the private subnet of storage nodes must first find out the corresponding | ||
domain name. To request a binding to a given subnet, a node needs to set | ||
related domain name in its information about when registering in the network | ||
map. The domain is set via `VerifiedNodesDomain` attribute. To be admitted to | ||
the network, a node must be present in the access list. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,190 @@ | ||
package innerring | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11" | ||
"github.com/nspcc-dev/neo-go/pkg/util" | ||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" | ||
nnsrpc "github.com/nspcc-dev/neofs-contract/rpc/nns" | ||
"github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation/privatedomains" | ||
) | ||
|
||
// provides services of the NeoFS Name Service consumed by the Inner Ring node. | ||
type neoFSNNS struct { | ||
invoker nep11.Invoker | ||
contract *nnsrpc.ContractReader | ||
} | ||
|
||
// creates NeoFS Name Service provider working with the Neo smart contract | ||
// deployed in the Neo network accessed through the specified [nep11.Invoker]. | ||
func newNeoFSNNS(contractAddress util.Uint160, contractCaller nep11.Invoker) *neoFSNNS { | ||
return &neoFSNNS{ | ||
invoker: contractCaller, | ||
contract: nnsrpc.NewReader(contractCaller, contractAddress), | ||
} | ||
} | ||
|
||
var errDomainNotFound = errors.New("domain not found") | ||
|
||
type errWrongStackItemType struct { | ||
expected, got stackitem.Type | ||
} | ||
|
||
func wrongStackItemTypeError(expected, got stackitem.Type) errWrongStackItemType { | ||
return errWrongStackItemType{ | ||
expected: expected, | ||
got: got, | ||
} | ||
} | ||
|
||
func (x errWrongStackItemType) Error() string { | ||
return fmt.Sprintf("wrong type: expected %s, got %s", x.expected, x.got) | ||
} | ||
|
||
type errInvalidNumberOfStructFields struct { | ||
expected, got int | ||
} | ||
|
||
func invalidNumberOfStructFieldsError(expected, got int) errInvalidNumberOfStructFields { | ||
return errInvalidNumberOfStructFields{ | ||
expected: expected, | ||
got: got, | ||
} | ||
} | ||
|
||
func (x errInvalidNumberOfStructFields) Error() string { | ||
return fmt.Sprintf("invalid number of struct fields: expected %d, got %d", x.expected, x.got) | ||
} | ||
|
||
type errInvalidStructField struct { | ||
index uint | ||
cause error | ||
} | ||
|
||
func invalidStructFieldError(index uint, cause error) errInvalidStructField { | ||
return errInvalidStructField{ | ||
index: index, | ||
cause: cause, | ||
} | ||
} | ||
|
||
func (x errInvalidStructField) Error() string { | ||
return fmt.Sprintf("invalid struct field #%d: %v", x.index, x.cause) | ||
} | ||
|
||
func (x errInvalidStructField) Unwrap() error { | ||
return x.cause | ||
} | ||
|
||
type errInvalidNNSDomainRecord struct { | ||
domain string | ||
record string | ||
cause error | ||
} | ||
|
||
func invalidNNSDomainRecordError(domain, record string, cause error) errInvalidNNSDomainRecord { | ||
return errInvalidNNSDomainRecord{ | ||
domain: domain, | ||
record: record, | ||
cause: cause, | ||
} | ||
} | ||
|
||
func (x errInvalidNNSDomainRecord) Error() string { | ||
if x.record != "" { | ||
return fmt.Sprintf("invalid record %q of the NNS domain %q: %v", x.record, x.domain, x.cause) | ||
} | ||
|
||
return fmt.Sprintf("invalid record of the NNS domain %q: %v", x.domain, x.cause) | ||
} | ||
|
||
func (x errInvalidNNSDomainRecord) Unwrap() error { | ||
return x.cause | ||
} | ||
|
||
// CheckDomainRecord calls iterating 'getAllRecords' method of the parameterized | ||
// Neo smart contract passing the given domain name. If contract throws 'token | ||
// not found' exception, CheckDomainRecord returns errDomainNotFound. Each value | ||
// in the resulting iterator is expected to be structure with at least 3 fields. | ||
// If any value has the 2nd field is a number equal to 16 (TXT record type in | ||
// the NNS) and the 3rd one is a string equal to the specified record, | ||
// CheckDomainRecord returns nil. Otherwise, | ||
// [privatedomains.ErrMissingDomainRecord] is returned. | ||
func (x *neoFSNNS) CheckDomainRecord(domain string, record string) error { | ||
sessionID, iter, err := x.contract.GetAllRecords(domain) | ||
if err != nil { | ||
// Track https://github.com/nspcc-dev/neofs-node/issues/2583. | ||
if strings.Contains(err.Error(), "token not found") { | ||
return errDomainNotFound | ||
} | ||
|
||
return fmt.Errorf("get iterator over all records of the NNS domain %q: %w", domain, err) | ||
} | ||
|
||
defer func() { | ||
_ = x.invoker.TerminateSession(sessionID) | ||
}() | ||
|
||
hasRecords := false | ||
|
||
for { | ||
items, err := x.invoker.TraverseIterator(sessionID, &iter, 10) | ||
if err != nil { | ||
return fmt.Errorf("traverse iterator over all records of the NNS domain %q: %w", domain, err) | ||
} | ||
|
||
if len(items) == 0 { | ||
break | ||
} | ||
|
||
hasRecords = true | ||
|
||
for i := range items { | ||
fields, ok := items[i].Value().([]stackitem.Item) | ||
if !ok { | ||
return invalidNNSDomainRecordError(domain, "", | ||
wrongStackItemTypeError(stackitem.StructT, items[i].Type())) | ||
} | ||
|
||
if len(fields) < 3 { | ||
return invalidNNSDomainRecordError(domain, "", | ||
invalidNumberOfStructFieldsError(3, len(fields))) | ||
} | ||
|
||
_, err = fields[0].TryBytes() | ||
if err != nil { | ||
return invalidNNSDomainRecordError(domain, "", | ||
invalidStructFieldError(0, wrongStackItemTypeError(stackitem.ByteArrayT, fields[0].Type()))) | ||
} | ||
|
||
typ, err := fields[1].TryInteger() | ||
if err != nil { | ||
return invalidNNSDomainRecordError(domain, "", | ||
invalidStructFieldError(1, wrongStackItemTypeError(stackitem.IntegerT, fields[1].Type()))) | ||
} | ||
|
||
if typ.Cmp(nnsrpc.TXT) != 0 { | ||
continue | ||
} | ||
|
||
data, err := fields[2].TryBytes() | ||
if err != nil { | ||
return invalidNNSDomainRecordError(domain, "", | ||
invalidStructFieldError(2, wrongStackItemTypeError(stackitem.ByteArrayT, fields[2].Type()))) | ||
} | ||
|
||
if string(data) == record { | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
if hasRecords { | ||
return privatedomains.ErrMissingDomainRecord | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.