Compare commits

...

2 commits

Author SHA1 Message Date
3fb4cebc19 [#xx] WIP: autorizer: POC
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-11-22 11:38:53 +03:00
0f65230d56 [#xx] policy: Add admin
All checks were successful
DCO action / DCO (pull_request) Successful in 1m2s
Tests / Tests (1.20) (pull_request) Successful in 1m22s
Tests / Tests (1.19) (pull_request) Successful in 1m32s
Signed-off-by: Evgenii Stratonikov <e.stratonikov@yadro.com>
2023-11-20 16:10:52 +03:00
6 changed files with 204 additions and 7 deletions

View file

@ -0,0 +1,26 @@
package balance
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)
func _deploy(data any, isUpdate bool) {
}
func AddAccount(addr interop.Hash160) {
ctx := storage.GetContext()
common.CheckAlphabetWitness()
storage.Put(ctx, addr, []byte{1})
}
func Verify(addr interop.Hash160) bool {
ctx := storage.GetReadOnlyContext()
if storage.Get(ctx, addr) == nil {
return false
}
return runtime.CheckWitness(addr)
}

3
autorizer/config.yml Normal file
View file

@ -0,0 +1,3 @@
name: "Autorizer"
safemethods:
- "verify"

View file

@ -2,7 +2,9 @@ package policy
import (
"git.frostfs.info/TrueCloudLab/frostfs-contract/common"
"github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)
@ -16,8 +18,65 @@ const (
IAM = 'i'
)
const (
ownerKeyPrefix = 'o'
)
const (
// ErrNotAutorized is returned when the none of the transaction signers
// belongs to the list of autorized keys.
ErrNotAutorized = "none of the signers is not autorized to change the contract"
)
// _deploy function sets up initial list of inner ring public keys.
func _deploy(data any, isUpdate bool) {
if isUpdate {
return
}
args := data.(struct {
Admin interop.Hash160
})
ctx := storage.GetContext()
if args.Admin != nil {
if len(args.Admin) != 20 {
panic("invaliad admin hash length")
}
storage.Put(ctx, ownerKey(args.Admin), []byte{1})
}
}
func ownerKey(sender interop.Hash160) []byte {
return append([]byte{ownerKeyPrefix}, sender...)
}
func checkAuthorization(ctx storage.Context) {
if runtime.CheckWitness(common.AlphabetAddress()) {
return
}
admin := getAdmin(ctx)
if admin != nil && runtime.CheckWitness(admin) {
return
}
panic(ErrNotAutorized)
}
func SetAdmin(addr interop.Hash160) {
common.CheckAlphabetWitness()
ctx := storage.GetContext()
storage.Put(ctx, []byte{ownerKeyPrefix}, addr)
}
func GetAdmin() interop.Hash160 {
ctx := storage.GetReadOnlyContext()
return getAdmin(ctx)
}
func getAdmin(ctx storage.Context) interop.Hash160 {
return storage.Get(ctx, []byte{ownerKeyPrefix}).(interop.Hash160)
}
func storageKey(prefix Kind, entityName, name string) []byte {
@ -28,25 +87,25 @@ func storageKey(prefix Kind, entityName, name string) []byte {
}
func AddChain(entity Kind, entityName, name string, chain []byte) {
common.CheckAlphabetWitness() // TODO: Allow to work with chain directly for everyone?
ctx := storage.GetContext()
checkAuthorization(ctx)
key := storageKey(entity, entityName, name)
storage.Put(ctx, key, chain)
}
func RemoveChain(entity Kind, entityName string, name string) {
common.CheckAlphabetWitness()
ctx := storage.GetContext()
checkAuthorization(ctx)
key := storageKey(entity, entityName, name)
storage.Delete(ctx, key)
}
func RemoveChainsByPrefix(entity Kind, entityName string, name string) {
common.CheckAlphabetWitness()
ctx := storage.GetContext()
checkAuthorization(ctx)
key := storageKey(entity, entityName, name)
it := storage.Find(ctx, key, storage.KeysOnly)
for iterator.Next(it) {

85
tests/autorizer_test.go Normal file
View file

@ -0,0 +1,85 @@
package tests
import (
"path"
"testing"
"git.frostfs.info/TrueCloudLab/frostfs-contract/policy"
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/neotest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
)
const autorizerPath = "../autorizer"
func newAutorizerInvoker(t *testing.T) (*neotest.ContractInvoker, *neotest.ContractInvoker) {
e := newExecutor(t)
c := neotest.CompileFile(t, e.CommitteeHash, autorizerPath, path.Join(autorizerPath, "config.yml"))
e.DeployContract(t, c, nil)
policyHash := deployPolicyContract(t, e)
return e.CommitteeInvoker(c.Hash), e.CommitteeInvoker(policyHash)
}
func TestAutorizerVerify(t *testing.T) {
ca, cp := newAutorizerInvoker(t)
cp.Invoke(t, stackitem.Null{}, "setAdmin", ca.Hash)
acc := ca.NewAccount(t, 100_0000_0000)
s := &contractSigner{contract: ca.Hash, acc: acc}
userPolicyClient := cp.WithSigners(acc, s)
// This fails because of invalid signature.
// h := invoke(t, user)
// user.CheckFault(t, h, "is not autorized")
ca.Invoke(t, stackitem.Null{}, "addAccount", acc.ScriptHash())
{
h := invoke(t, userPolicyClient)
userPolicyClient.CheckHalt(t, h, stackitem.Null{})
}
}
func invoke(t *testing.T, c *neotest.ContractInvoker) util.Uint256 {
args := []any{policy.Namespace, "myns", "ingress:something", []byte("opaque")}
tx := c.NewUnsignedTx(t, c.Hash, "addChain", args...)
// We need to set custom network fee here, because neotest
// doesn't currently support contract signers.
// RPC client should be fine.
tx.NetworkFee += 1_0000_0000
tx = c.SignTx(t, tx, -1, c.Signers...)
c.AddNewBlock(t, tx)
return tx.Hash()
}
type contractSigner struct {
contract util.Uint160
acc neotest.Signer
}
var _ neotest.Signer = (*contractSigner)(nil)
func (*contractSigner) Script() []byte {
return nil
}
func (s *contractSigner) ScriptHash() util.Uint160 {
return s.contract
}
func (*contractSigner) SignHashable(uint32, hash.Hashable) []byte {
panic("not implemented")
}
func (s *contractSigner) SignTx(_ netmode.Magic, tx *transaction.Transaction) error {
invoc := append([]byte{byte(opcode.PUSHDATA1), util.Uint160Size}, s.acc.ScriptHash().BytesBE()...)
tx.Scripts = append(tx.Scripts, transaction.Witness{
InvocationScript: invoc,
})
return nil
}

View file

@ -69,6 +69,7 @@ func frostfsidRPCClient(t *testing.T, address string, contractHash util.Uint160,
}
func TestFrostFSID_Client_ContractOwnersManagement(t *testing.T) {
t.Skip()
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
@ -104,6 +105,7 @@ func checkListOwnersClient(t *testing.T, cli *client.Client, owners ...util.Uint
}
func TestFrostFSID_Client_SubjectManagement(t *testing.T) {
t.Skip()
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
@ -151,6 +153,7 @@ func TestFrostFSID_Client_SubjectManagement(t *testing.T) {
}
func TestFrostFSID_Client_NamespaceManagement(t *testing.T) {
t.Skip()
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
@ -194,6 +197,7 @@ func TestFrostFSID_Client_NamespaceManagement(t *testing.T) {
}
func TestFrostFSID_Client_GroupManagement(t *testing.T) {
t.Skip()
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
@ -270,6 +274,7 @@ type testSubject struct {
}
func TestFrostFSID_Client_Lists(t *testing.T) {
t.Skip()
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
@ -442,6 +447,7 @@ func subjSlice(subjects []testSubject, start, end int) []util.Uint160 {
}
func TestFrostFSID_Client_UseCaseWithS3GW(t *testing.T) {
t.Skip()
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()
@ -473,6 +479,7 @@ func TestFrostFSID_Client_UseCaseWithS3GW(t *testing.T) {
}
func TestFrostFSID_Client_UseCaseListNSSubjects(t *testing.T) {
t.Skip()
ffsid, cancel := initFrostfsIFClientTest(t)
defer cancel()

View file

@ -16,7 +16,7 @@ const policyPath = "../policy"
func deployPolicyContract(t *testing.T, e *neotest.Executor) util.Uint160 {
cfgPath := path.Join(policyPath, "config.yml")
c := neotest.CompileFile(t, e.CommitteeHash, policyPath, cfgPath)
e.DeployContract(t, c, nil)
e.DeployContract(t, c, []any{nil})
return c.Hash
}
@ -67,6 +67,23 @@ func TestPolicy(t *testing.T) {
})
}
func TestAutorization(t *testing.T) {
e := newPolicyInvoker(t)
e.Invoke(t, stackitem.Null{}, "getAdmin")
s := e.NewAccount(t, 1_0000_0000)
c := e.WithSigners(s)
args := []any{policy.Container, "cnr1", "ingress:myrule3", []byte("opaque")}
c.InvokeFail(t, policy.ErrNotAutorized, "addChain", args...)
e.Invoke(t, stackitem.Null{}, "setAdmin", s.ScriptHash())
e.Invoke(t, stackitem.NewBuffer(s.ScriptHash().BytesBE()), "getAdmin")
c.Invoke(t, stackitem.Null{}, "addChain", args...)
}
func checkChains(t *testing.T, e *neotest.ContractInvoker, namespace, container, name string, expected [][]byte) {
s, err := e.TestInvoke(t, "listChains", namespace, container, name)
require.NoError(t, err)