diff --git a/.changelog/4821.feature.md b/.changelog/4821.feature.md new file mode 100644 index 00000000000..f17c4ffea56 --- /dev/null +++ b/.changelog/4821.feature.md @@ -0,0 +1 @@ +go/oasis-node: allow km to have private peers diff --git a/go/oasis-test-runner/oasis/args.go b/go/oasis-test-runner/oasis/args.go index 6df61c9fae8..49f26b28bdf 100644 --- a/go/oasis-test-runner/oasis/args.go +++ b/go/oasis-test-runner/oasis/args.go @@ -408,6 +408,17 @@ func (args *argBuilder) workerKeymanagerMayGenerate() *argBuilder { return args } +func (args *argBuilder) workerKeymanagerPrivatePeerPubKeys(peerPKs []string) *argBuilder { + for _, pk := range peerPKs { + args.vec = append(args.vec, Argument{ + Name: keymanager.CfgPrivatePeerPubKeys, + Values: []string{pk}, + MultiValued: true, + }) + } + return args +} + func (args *argBuilder) workerSentryEnabled() *argBuilder { args.vec = append(args.vec, Argument{Name: workerSentry.CfgEnabled}) return args diff --git a/go/oasis-test-runner/oasis/client.go b/go/oasis-test-runner/oasis/client.go index 372c46a4614..29f9d92400f 100644 --- a/go/oasis-test-runner/oasis/client.go +++ b/go/oasis-test-runner/oasis/client.go @@ -6,6 +6,10 @@ import ( runtimeRegistry "github.com/oasisprotocol/oasis-core/go/runtime/registry" ) +const ( + clientIdentitySeedTemplate = "ekiden node client %d" +) + // Client is an Oasis client node. type Client struct { *Node @@ -70,6 +74,12 @@ func (net *Network) NewClient(cfg *ClientCfg) (*Client, error) { cfg.RuntimeProvisioner = runtimeRegistry.RuntimeProvisionerUnconfined } + // Pre-provision the node identity so that we can identify the entity. + err = host.setProvisionedIdentity(false, fmt.Sprintf(clientIdentitySeedTemplate, len(net.clients))) + if err != nil { + return nil, fmt.Errorf("oasis/client: failed to provision node identity: %w", err) + } + client := &Client{ Node: host, runtimes: cfg.Runtimes, diff --git a/go/oasis-test-runner/oasis/fixture.go b/go/oasis-test-runner/oasis/fixture.go index 8ae5e326a1b..398a71a0c21 100644 --- a/go/oasis-test-runner/oasis/fixture.go +++ b/go/oasis-test-runner/oasis/fixture.go @@ -318,6 +318,8 @@ type KeymanagerFixture struct { CrashPointsProbability float64 `json:"crash_points_probability,omitempty"` LogWatcherHandlerFactories []log.WatcherHandlerFactory `json:"-"` + + PrivatePeerPubKeys []string `json:"private_peer_pub_keys,omitempty"` } // Create instantiates the key manager described by the fixture. @@ -353,6 +355,7 @@ func (f *KeymanagerFixture) Create(net *Network) (*Keymanager, error) { Runtime: runtime, Policy: policy, SentryIndices: f.Sentries, + PrivatePeerPubKeys: f.PrivatePeerPubKeys, }) } diff --git a/go/oasis-test-runner/oasis/keymanager.go b/go/oasis-test-runner/oasis/keymanager.go index 779308c6671..fe24999903c 100644 --- a/go/oasis-test-runner/oasis/keymanager.go +++ b/go/oasis-test-runner/oasis/keymanager.go @@ -162,6 +162,8 @@ type Keymanager struct { // nolint: maligned p2pPort uint16 mayGenerate bool + + privatePeerPubKeys []string } // KeymanagerCfg is the Oasis key manager provisioning configuration. @@ -173,6 +175,9 @@ type KeymanagerCfg struct { Runtime *Runtime Policy *KeymanagerPolicy RuntimeProvisioner string + + // PrivatePeerPubKeys is a list of base64-encoded libp2p public keys of peers who may call non-public methods. + PrivatePeerPubKeys []string } // IdentityKeyPath returns the paths to the node's identity key. @@ -270,6 +275,7 @@ func (km *Keymanager) AddArgs(args *argBuilder) error { runtimeSGXLoader(km.net.cfg.RuntimeSGXLoaderBinary). runtimePath(km.runtime). workerKeymanagerRuntimeID(km.runtime.ID()). + workerKeymanagerPrivatePeerPubKeys(km.privatePeerPubKeys). configureDebugCrashPoints(km.crashPointsProbability). tendermintSupplementarySanity(km.supplementarySanityInterval). appendNetwork(km.net). @@ -336,6 +342,7 @@ func (net *Network) NewKeymanager(cfg *KeymanagerCfg) (*Keymanager, error) { workerClientPort: host.getProvisionedPort(nodePortClient), p2pPort: host.getProvisionedPort(nodePortP2P), mayGenerate: len(net.keymanagers) == 0, + privatePeerPubKeys: cfg.PrivatePeerPubKeys, } net.keymanagers = append(net.keymanagers, km) diff --git a/go/oasis-test-runner/oasis/oasis.go b/go/oasis-test-runner/oasis/oasis.go index e71826a1ed8..a6966aa27c5 100644 --- a/go/oasis-test-runner/oasis/oasis.go +++ b/go/oasis-test-runner/oasis/oasis.go @@ -334,8 +334,11 @@ func (n *Node) setProvisionedIdentity(persistTLS bool, seed string) error { return err } - if err := n.entity.addNode(nodeSigner); err != nil { - return err + if n.entity != nil { + // Client nodes may need a provisioned identity. They never need an entity, however. + if err := n.entity.addNode(nodeSigner); err != nil { + return err + } } n.nodeSigner = nodeSigner diff --git a/go/worker/keymanager/init.go b/go/worker/keymanager/init.go index 41ecdca2c93..9ee8339706d 100644 --- a/go/worker/keymanager/init.go +++ b/go/worker/keymanager/init.go @@ -3,6 +3,7 @@ package keymanager import ( "context" + "encoding/base64" "fmt" core "github.com/libp2p/go-libp2p-core" @@ -10,6 +11,7 @@ import ( "github.com/spf13/viper" "github.com/oasisprotocol/oasis-core/go/common" + "github.com/oasisprotocol/oasis-core/go/common/crypto/signature" "github.com/oasisprotocol/oasis-core/go/common/logging" "github.com/oasisprotocol/oasis-core/go/common/node" ias "github.com/oasisprotocol/oasis-core/go/ias/api" @@ -17,6 +19,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/runtime/localstorage" runtimeRegistry "github.com/oasisprotocol/oasis-core/go/runtime/registry" workerCommon "github.com/oasisprotocol/oasis-core/go/worker/common" + p2pAPI "github.com/oasisprotocol/oasis-core/go/worker/common/p2p" "github.com/oasisprotocol/oasis-core/go/worker/keymanager/p2p" "github.com/oasisprotocol/oasis-core/go/worker/registration" ) @@ -26,6 +29,8 @@ const ( CfgRuntimeID = "worker.keymanager.runtime.id" // CfgMayGenerate allows the enclave to generate a master secret. CfgMayGenerate = "worker.keymanager.may_generate" + // CfgPrivatePeerPks allows adding manual, unadvertised peers that can call protected methods. + CfgPrivatePeerPubKeys = "worker.keymanager.private_peer_pub_keys" ) // Flags has the configuration flags. @@ -59,6 +64,7 @@ func New( initCh: make(chan struct{}), initTickerCh: nil, accessList: make(map[core.PeerID]map[common.Namespace]struct{}), + privatePeers: make(map[core.PeerID]struct{}), accessListByRuntime: make(map[common.Namespace][]core.PeerID), commonWorker: commonWorker, backend: backend, @@ -70,6 +76,22 @@ func New( return w, nil } + for _, b64pk := range viper.GetStringSlice(CfgPrivatePeerPubKeys) { + pkBytes, err := base64.StdEncoding.DecodeString(b64pk) + if err != nil { + return nil, fmt.Errorf("oasis/keymanager: `%s` is not a base64-encoded public key (%w)", b64pk, err) + } + var pk signature.PublicKey + if err = pk.UnmarshalBinary(pkBytes); err != nil { + return nil, fmt.Errorf("oasis/keymanager: `%s` is not a public key (%w)", b64pk, err) + } + peerID, err := p2pAPI.PublicKeyToPeerID(pk) + if err != nil { + return nil, fmt.Errorf("oasis/keymanager: `%s` can not be converted to a peer id (%w)", b64pk, err) + } + w.privatePeers[peerID] = struct{}{} + } + var runtimeID common.Namespace if err := runtimeID.UnmarshalHex(viper.GetString(CfgRuntimeID)); err != nil { return nil, fmt.Errorf("worker/keymanager: failed to parse runtime ID: %w", err) @@ -115,6 +137,7 @@ func New( func init() { Flags.String(CfgRuntimeID, "", "Key manager Runtime ID") Flags.Bool(CfgMayGenerate, false, "Key manager may generate new master secret") + Flags.StringSlice(CfgPrivatePeerPubKeys, []string{}, "b64-encoded public keys of unadvertised peers that may call protected methods") _ = viper.BindPFlags(Flags) } diff --git a/go/worker/keymanager/worker.go b/go/worker/keymanager/worker.go index 67287b3a93b..dc306abdc06 100644 --- a/go/worker/keymanager/worker.go +++ b/go/worker/keymanager/worker.go @@ -80,6 +80,7 @@ type Worker struct { // nolint: maligned accessList map[core.PeerID]map[common.Namespace]struct{} accessListByRuntime map[common.Namespace][]core.PeerID + privatePeers map[core.PeerID]struct{} commonWorker *workerCommon.Worker roleProvider registration.RoleProvider @@ -174,12 +175,14 @@ func (w *Worker) CallEnclave(ctx context.Context, data []byte) ([]byte, error) { case getPublicKeyRequestMethod: // Anyone can get public keys. default: - // Defer to access control to check the policy. - w.RLock() - _, allowed := w.accessList[peerID] - w.RUnlock() - if !allowed { - return nil, fmt.Errorf("not authorized") + if _, privatePeered := w.privatePeers[peerID]; !privatePeered { + // Defer to access control to check the policy. + w.RLock() + _, allowed := w.accessList[peerID] + w.RUnlock() + if !allowed { + return nil, fmt.Errorf("not authorized") + } } }