-
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 private attributes of the storage nodes
Previously, storage nodes could declare (almost) any key-value attributes while entering the network map. Sometimes there is a need to restrict access to a specific attribute value. To do this, the concept of a private node attribute is introduced. Access lists with public key are stored in the NeoFS NNS: for each private attribute there is a domain, and only nodes recorded in this domain are able to use this attribute. From now, the Inner Ring checks any incoming node for permission to use private attributes (if any). Closes #2280. Signed-off-by: Leonard Lyubich <leonard@morphbits.io>
- Loading branch information
1 parent
9207437
commit 93a5475
Showing
13 changed files
with
791 additions
and
158 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Private attributes of the NeoFS storage nodes | ||
|
||
Storage nodes declare key-value string attributes when applying to enter the | ||
network map. In general, any attributes can be declared, however, some of them | ||
may be subject to restrictions. In particular, to specify so-called private | ||
attributes, the node must be in the corresponding access list. | ||
|
||
## Access lists | ||
|
||
These lists are stored in the NeoFS NNS. For each private attribute value | ||
`KEY=VALUE`, there is a domain with name `MD5(VALUE).MD5(KEY).private-node-attributes.neofs` | ||
and records of `TXT` type with HEX-encoded public keys. If the domain exists | ||
and has non-empty list of public key records, only storage nodes with keys from | ||
this list will be able to set this attribute value. The Inner Ring will deny | ||
everyone else access to the network map. | ||
|
||
Note that if domain for the attribute exists but has no records, attribute is | ||
non-private. | ||
|
||
### 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 should be `16` (TXT records) | ||
3. `ByteString` with HEX-encoded public key |
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,114 @@ | ||
package innerring | ||
|
||
import ( | ||
"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" | ||
attributevalidator "github.com/nspcc-dev/neofs-node/pkg/innerring/processors/netmap/nodevalidation/attributes" | ||
) | ||
|
||
// 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), | ||
} | ||
} | ||
|
||
// 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 | ||
// [attributevalidator.ErrMissingDomain]. 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, [attributevalidator.ErrMissingDomainRecord] is returned. | ||
func (x *neoFSNNS) CheckDomainRecord(domainPath []string, record string) error { | ||
domain := strings.Join(domainPath, ".") | ||
|
||
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 attributevalidator.ErrMissingDomain | ||
} | ||
|
||
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 fmt.Errorf("invalid element returned by iterator over all record of the NNS domain %q: unexpected type %s instead of %s", | ||
domain, stackitem.StructT, items[i].Type()) | ||
} | ||
|
||
if len(fields) < 3 { | ||
return fmt.Errorf("invalid element returned by iterator over all record of the NNS domain %q: unsupported number of struct fields: expected at least 3, got %d", | ||
domain, len(fields)) | ||
} | ||
|
||
_, err = fields[0].TryBytes() | ||
if err != nil { | ||
return fmt.Errorf("invalid element returned by iterator over all record of the NNS domain %q: convert 1st field to byte array: %w", | ||
domain, err) | ||
} | ||
|
||
typ, err := fields[1].TryInteger() | ||
if err != nil { | ||
return fmt.Errorf("invalid element returned by iterator over all record of the NNS domain %q: convert 2nd field to integer: %w", | ||
domain, err) | ||
} | ||
|
||
if typ.Cmp(nnsrpc.TXT) != 0 { | ||
return fmt.Errorf("invalid element returned by iterator over all record of the NNS domain %q: expected TXT (%v) record, got %v", | ||
domain, nnsrpc.TXT, typ) | ||
} | ||
|
||
data, err := fields[2].TryBytes() | ||
if err != nil { | ||
return fmt.Errorf("invalid element returned by iterator over all record of the NNS domain %q: convert 3rd field to byte array: %w", | ||
domain, err) | ||
} | ||
|
||
if string(data) == record { | ||
return nil | ||
} | ||
} | ||
} | ||
|
||
if hasRecords { | ||
return attributevalidator.ErrMissingDomainRecord | ||
} | ||
|
||
return nil | ||
} |
Oops, something went wrong.