Skip to content

Commit

Permalink
Merge pull request #5532 from oasisprotocol/peternose/trivial/refacto…
Browse files Browse the repository at this point in the history
…r-km-acl

go/worker/keymanager: Refactor access control
  • Loading branch information
peternose authored Jan 22, 2024
2 parents f678112 + 08301d8 commit 4104c5e
Show file tree
Hide file tree
Showing 8 changed files with 581 additions and 351 deletions.
Empty file added .changelog/5532.trivial.md
Empty file.
12 changes: 12 additions & 0 deletions go/keymanager/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,12 +79,24 @@ var (
// RPCMethodInit is the name of the `init` method.
RPCMethodInit = "init"

// RPCMethodGetOrCreateKeys is the name of the `get_or_create_keys` method.
RPCMethodGetOrCreateKeys = "get_or_create_keys"

// RPCMethodGetPublicKey is the name of the `get_public_key` method.
RPCMethodGetPublicKey = "get_public_key"

// RPCMethodGetOrCreateEphemeralKeys is the name of the `get_or_create_ephemeral_keys` method.
RPCMethodGetOrCreateEphemeralKeys = "get_or_create_ephemeral_keys"

// RPCMethodGetPublicEphemeralKey is the name of the `get_public_ephemeral_key` method.
RPCMethodGetPublicEphemeralKey = "get_public_ephemeral_key" // #nosec G101

// RPCMethodReplicateMasterSecret is the name of the `replicate_master_secret` method.
RPCMethodReplicateMasterSecret = "replicate_master_secret"

// RPCMethodReplicateEphemeralSecret is the name of the `replicate_ephemeral_secret` method.
RPCMethodReplicateEphemeralSecret = "replicate_ephemeral_secret"

// RPCMethodGenerateMasterSecret is the name of the `generate_master_secret` RPC method.
RPCMethodGenerateMasterSecret = "generate_master_secret"

Expand Down
14 changes: 14 additions & 0 deletions go/runtime/enclaverpc/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,20 @@ const (
KindLocalQuery Kind = 2
)

// String returns a string representation of RPC call kind.
func (k Kind) String() string {
switch k {
case KindNoiseSession:
return "noise session"
case KindInsecureQuery:
return "insecure query"
case KindLocalQuery:
return "local query"
default:
return "[unknown]"
}
}

// Frame is an EnclaveRPC frame.
//
// It is the Go analog of the Rust RPC frame defined in runtime/src/enclave_rpc/types.rs.
Expand Down
176 changes: 176 additions & 0 deletions go/worker/keymanager/acl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package keymanager

import (
"sync"

"github.com/libp2p/go-libp2p/core"

"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/common/logging"
"github.com/oasisprotocol/oasis-core/go/common/node"
p2p "github.com/oasisprotocol/oasis-core/go/p2p/api"
"github.com/oasisprotocol/oasis-core/go/worker/keymanager/api"
)

// RuntimeList is a concurrent-safe collection of unique runtime IDs.
type RuntimeList struct {
mu sync.RWMutex

runtimes map[common.Namespace]struct{} // Guarded by mutex.
}

// NewRuntimeList constructs an empty runtime list.
func NewRuntimeList() *RuntimeList {
return &RuntimeList{
runtimes: make(map[common.Namespace]struct{}),
}
}

// Contains returns true if and only if the list contains the given runtime.
//
// A nil runtime list is considered empty and will always return false.
func (l *RuntimeList) Contains(runtimeID common.Namespace) bool {
if l == nil {
return false
}

l.mu.RLock()
defer l.mu.RUnlock()

_, ok := l.runtimes[runtimeID]
return ok
}

// Add adds the given runtime to the list.
func (l *RuntimeList) Add(runtimeID common.Namespace) {
l.mu.Lock()
defer l.mu.Unlock()

l.runtimes[runtimeID] = struct{}{}
}

// Delete removes the given runtime from the list.
//
// If the runtime list is nil or there is no such element, this function is a no-op.
func (l *RuntimeList) Delete(runtimeID common.Namespace) {
if l == nil {
return
}

l.mu.Lock()
defer l.mu.Unlock()

delete(l.runtimes, runtimeID)
}

// Empty returns true if and only if the list contains no elements.
func (l *RuntimeList) Empty() bool {
if l == nil {
return true
}

l.mu.RLock()
defer l.mu.RUnlock()

return len(l.runtimes) == 0
}

// AccessList is a concurrent-safe data structure for managing access permissions.
type AccessList struct {
mu sync.RWMutex

accessList map[core.PeerID]*RuntimeList // Guarded by mutex.
accessListByRuntime map[common.Namespace][]core.PeerID // Guarded by mutex.

logger *logging.Logger
}

// NewAccessList constructs an empty access list.
func NewAccessList() *AccessList {
logger := logging.GetLogger("worker/keymanager/acl")

return &AccessList{
accessList: make(map[core.PeerID]*RuntimeList),
accessListByRuntime: make(map[common.Namespace][]core.PeerID),
logger: logger,
}
}

// Runtimes returns the IDs of runtimes in which the given peer participates.
func (l *AccessList) Runtimes(peer core.PeerID) *RuntimeList {
l.mu.RLock()
defer l.mu.RUnlock()

return l.accessList[peer]
}

// Update clears the access list for the specified runtime and adds the provided peers.
func (l *AccessList) Update(runtimeID common.Namespace, peers []core.PeerID) {
l.mu.Lock()
defer l.mu.Unlock()

// Clear any old nodes from the access list.
for _, peerID := range l.accessListByRuntime[runtimeID] {
rts := l.accessList[peerID]
rts.Delete(runtimeID)
if rts.Empty() {
delete(l.accessList, peerID)
}
}

// Update the access list.
for _, peer := range peers {
rts, ok := l.accessList[peer]
if !ok {
rts = NewRuntimeList()
l.accessList[peer] = rts
}

rts.Add(runtimeID)
}

// To prevent race conditions when returning runtime access lists, it is essential to always
// replace the peers array and refrain from making any modifications.
l.accessListByRuntime[runtimeID] = peers
}

// UpdateNodes converts node public keys to peer IDs and updates the access list
// for the specified runtime.
func (l *AccessList) UpdateNodes(runtimeID common.Namespace, nodes []*node.Node) {
var peers []core.PeerID
for _, node := range nodes {
peer, err := p2p.PublicKeyToPeerID(node.P2P.ID)
if err != nil {
l.logger.Warn("invalid node P2P ID",
"err", err,
"node_id", node.ID,
)
continue
}
peers = append(peers, peer)
}

l.Update(runtimeID, peers)

l.logger.Debug("new client runtime access policy in effect",
"runtime_id", runtimeID,
"peers", peers,
)
}

// RuntimeAccessLists returns a per-runtime list of allowed peers.
func (l *AccessList) RuntimeAccessLists() []api.RuntimeAccessList {
l.mu.RLock()
defer l.mu.RUnlock()

rals := make([]api.RuntimeAccessList, 0, len(l.accessListByRuntime))
for rt, ps := range l.accessListByRuntime {
ral := api.RuntimeAccessList{
RuntimeID: rt,
Peers: ps,
}
rals = append(rals, ral)
}

return rals
}
39 changes: 20 additions & 19 deletions go/worker/keymanager/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (

"github.com/libp2p/go-libp2p/core"

"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"
Expand Down Expand Up @@ -40,24 +39,22 @@ func New(
ctx, cancelFn := context.WithCancel(context.Background())

w := &Worker{
logger: logging.GetLogger("worker/keymanager"),
ctx: ctx,
cancelCtx: cancelFn,
stopCh: make(chan struct{}),
quitCh: make(chan struct{}),
initCh: make(chan struct{}),
clientRuntimes: make(map[common.Namespace]*clientRuntimeWatcher),
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,
enabled: enabled,
initEnclaveDoneCh: make(chan *api.SignedInitResponse, 1),
genMstSecDoneCh: make(chan bool, 1),
genMstSecEpoch: math.MaxUint64,
genEphSecDoneCh: make(chan bool, 1),
genSecHeight: int64(math.MaxInt64),
logger: logging.GetLogger("worker/keymanager"),
ctx: ctx,
cancelCtx: cancelFn,
stopCh: make(chan struct{}),
quitCh: make(chan struct{}),
initCh: make(chan struct{}),
accessList: NewAccessList(),
privatePeers: make(map[core.PeerID]struct{}),
commonWorker: commonWorker,
backend: backend,
enabled: enabled,
initEnclaveDoneCh: make(chan *api.SignedInitResponse, 1),
genMstSecDoneCh: make(chan bool, 1),
genMstSecEpoch: math.MaxUint64,
genEphSecDoneCh: make(chan bool, 1),
genSecHeight: int64(math.MaxInt64),
}

if !w.enabled {
Expand Down Expand Up @@ -108,6 +105,10 @@ func New(
return nil, fmt.Errorf("worker/keymanager: failed to create runtime host helpers: %w", err)
}

// Prepare watchers.
w.kmNodeWatcher = newKmNodeWatcher(w.runtimeID, commonWorker.Consensus, w.accessList, w.commonWorker.P2P.PeerManager().PeerTagger())
w.kmRuntimeWatcher = newKmRuntimeWatcher(w.runtimeID, commonWorker.Consensus, w.accessList)

// Register keymanager service.
commonWorker.P2P.RegisterProtocolServer(p2p.NewServer(commonWorker.ChainContext, w.runtimeID, w))

Expand Down
18 changes: 3 additions & 15 deletions go/worker/keymanager/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package keymanager
import (
"github.com/libp2p/go-libp2p/core/peer"

"github.com/oasisprotocol/oasis-core/go/common"
"github.com/oasisprotocol/oasis-core/go/worker/keymanager/api"
)

Expand Down Expand Up @@ -38,23 +37,12 @@ func (w *Worker) GetStatus() (*api.Status, error) {
ps = append(ps, p)
}

rts := w.kmRuntimeWatcher.Runtimes()
al := w.accessList.RuntimeAccessLists()

w.RLock()
defer w.RUnlock()

rts := make([]common.Namespace, 0, len(w.clientRuntimes))
for rt := range w.clientRuntimes {
rts = append(rts, rt)
}

al := make([]api.RuntimeAccessList, 0, len(w.accessListByRuntime))
for rt, ps := range w.accessListByRuntime {
ral := api.RuntimeAccessList{
RuntimeID: rt,
Peers: ps,
}
al = append(al, ral)
}

gs := w.globalStatus
ws := api.WorkerStatus{
Status: ss,
Expand Down
Loading

0 comments on commit 4104c5e

Please sign in to comment.