metropolis: reduce usage of identity.NodeID
Eventually, we want to be able to rotate node keypairs. To allow this,
the node ID needs to become independent of the public key. This change
is a refactoring which starts this work by reducing the usage of
identity.NodeID, the function which derives a node ID from a public key.
Change-Id: I5231ed0a7be37c23327fec93481b00c74374af07
Reviewed-on: https://review.monogon.dev/c/monogon/+/3445
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/metropolis/node/core/cluster/cluster_bootstrap.go b/metropolis/node/core/cluster/cluster_bootstrap.go
index fdfb44a..820dffc 100644
--- a/metropolis/node/core/cluster/cluster_bootstrap.go
+++ b/metropolis/node/core/cluster/cluster_bootstrap.go
@@ -93,6 +93,7 @@
if err != nil {
return fmt.Errorf("could not generate node keypair: %w", err)
}
+ id := identity.NodeID(pub)
supervisor.Logger(ctx).Infof("Bootstrapping: node public key: %s", hex.EncodeToString(pub))
jpub, jpriv, err := ed25519.GenerateKey(rand.Reader)
@@ -104,7 +105,7 @@
directory := &cpb.ClusterDirectory{
Nodes: []*cpb.ClusterDirectory_Node{
{
- Id: identity.NodeID(pub),
+ Id: id,
Addresses: []*cpb.ClusterDirectory_Node_Address{
{
Host: "127.0.0.1",
@@ -157,6 +158,7 @@
}
bd := roleserve.BootstrapData{}
+ bd.Node.ID = id
bd.Node.PrivateKey = priv
bd.Node.ClusterUnlockKey = cuk
bd.Node.NodeUnlockKey = nuk
diff --git a/metropolis/node/core/consensus/BUILD.bazel b/metropolis/node/core/consensus/BUILD.bazel
index ac048d6..5c58c2a 100644
--- a/metropolis/node/core/consensus/BUILD.bazel
+++ b/metropolis/node/core/consensus/BUILD.bazel
@@ -15,7 +15,6 @@
"//go/logging",
"//metropolis/node",
"//metropolis/node/core/consensus/client",
- "//metropolis/node/core/identity",
"//metropolis/node/core/localstorage",
"//osbase/event",
"//osbase/event/memory",
diff --git a/metropolis/node/core/consensus/configuration.go b/metropolis/node/core/consensus/configuration.go
index bb3db82..071f5f5 100644
--- a/metropolis/node/core/consensus/configuration.go
+++ b/metropolis/node/core/consensus/configuration.go
@@ -13,7 +13,6 @@
"go.etcd.io/etcd/server/v3/embed"
"source.monogon.dev/metropolis/node"
- "source.monogon.dev/metropolis/node/core/identity"
"source.monogon.dev/metropolis/node/core/localstorage"
"source.monogon.dev/osbase/pki"
)
@@ -31,8 +30,11 @@
// If that data is not present, a new cluster will be bootstrapped.
JoinCluster *JoinCluster
+ // NodeID is the node ID, which is also used to identify consensus nodes.
+ NodeID string
+
// NodePrivateKey is the node's main private key which is also used for
- // Metropolis PKI. The same key will be used to identify consensus nodes, but
+ // Metropolis PKI. The same key will be used for consensus nodes, but
// different certificates will be used.
NodePrivateKey ed25519.PrivateKey
@@ -85,12 +87,11 @@
// over TLS. This requires TLS credentials to be present on disk, and will be
// disabled for bootstrapping the instance.
func (c *Config) build(enablePeers bool) *embed.Config {
- nodeID := identity.NodeID(c.nodePublicKey())
port := int(node.ConsensusPort)
if p := c.testOverrides.externalPort; p != 0 {
port = p
}
- host := nodeID
+ host := c.NodeID
if c.testOverrides.externalAddress != "" {
host = c.testOverrides.externalAddress
}
@@ -101,7 +102,7 @@
cfg := embed.NewConfig()
- cfg.Name = nodeID
+ cfg.Name = c.NodeID
cfg.ClusterState = "existing"
cfg.InitialClusterToken = "METROPOLIS"
cfg.Logger = "zap"
@@ -147,7 +148,7 @@
}}
}
- cfg.InitialCluster = cfg.InitialClusterFromName(nodeID)
+ cfg.InitialCluster = cfg.InitialClusterFromName(c.NodeID)
if c.JoinCluster != nil {
for _, n := range c.JoinCluster.ExistingNodes {
cfg.InitialCluster += "," + n.connectionString()
diff --git a/metropolis/node/core/consensus/consensus.go b/metropolis/node/core/consensus/consensus.go
index 8da53d6..f8861a7 100644
--- a/metropolis/node/core/consensus/consensus.go
+++ b/metropolis/node/core/consensus/consensus.go
@@ -87,7 +87,6 @@
import (
"context"
- "crypto/ed25519"
"crypto/x509"
"crypto/x509/pkix"
"errors"
@@ -102,7 +101,6 @@
"go.etcd.io/etcd/server/v3/embed"
"source.monogon.dev/metropolis/node/core/consensus/client"
- "source.monogon.dev/metropolis/node/core/identity"
"source.monogon.dev/osbase/event"
"source.monogon.dev/osbase/event/memory"
"source.monogon.dev/osbase/logtree/unraw"
@@ -131,16 +129,16 @@
}
}
-func pkiPeerCertificate(pubkey ed25519.PublicKey, extraNames []string) x509.Certificate {
+func pkiPeerCertificate(nodeID string, extraNames []string) x509.Certificate {
return x509.Certificate{
Subject: pkix.Name{
- CommonName: identity.NodeID(pubkey),
+ CommonName: nodeID,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{
x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth,
},
- DNSNames: append(extraNames, identity.NodeID(pubkey)),
+ DNSNames: append(extraNames, nodeID),
}
}
@@ -451,10 +449,10 @@
extraNames = []string{external}
}
memberTemplate := pki.Certificate{
- Name: identity.NodeID(s.config.nodePublicKey()),
+ Name: s.config.NodeID,
Namespace: &pkiNamespace,
Issuer: s.ca,
- Template: pkiPeerCertificate(s.config.nodePublicKey(), extraNames),
+ Template: pkiPeerCertificate(s.config.NodeID, extraNames),
Mode: pki.CertificateExternal,
PublicKey: s.config.nodePublicKey(),
}
diff --git a/metropolis/node/core/consensus/consensus_test.go b/metropolis/node/core/consensus/consensus_test.go
index 85df62e..64dd0b3 100644
--- a/metropolis/node/core/consensus/consensus_test.go
+++ b/metropolis/node/core/consensus/consensus_test.go
@@ -84,6 +84,7 @@
etcd := New(Config{
Data: &b.root.Data.Etcd,
Ephemeral: &b.root.Ephemeral.Consensus,
+ NodeID: "node1",
NodePrivateKey: b.privkey,
testOverrides: testOverrides{
externalPort: 2345,
@@ -132,6 +133,7 @@
etcd := New(Config{
Data: &b.root.Data.Etcd,
Ephemeral: &b.root.Ephemeral.Consensus,
+ NodeID: "node1",
NodePrivateKey: b.privkey,
testOverrides: testOverrides{
externalPort: 1234,
@@ -168,6 +170,7 @@
etcd := New(Config{
Data: &b.root.Data.Etcd,
Ephemeral: &b.root.Ephemeral.Consensus,
+ NodeID: "node1",
NodePrivateKey: b.privkey,
testOverrides: testOverrides{
externalPort: 1235,
@@ -216,6 +219,7 @@
etcd = New(Config{
Data: &b.root.Data.Etcd,
Ephemeral: &b.root.Ephemeral.Consensus,
+ NodeID: "node1",
NodePrivateKey: b.privkey,
testOverrides: testOverrides{
externalPort: 1235,
@@ -262,6 +266,7 @@
etcd := New(Config{
Data: &b.root.Data.Etcd,
Ephemeral: &b.root.Ephemeral.Consensus,
+ NodeID: "node1",
NodePrivateKey: b.privkey,
testOverrides: testOverrides{
externalPort: 3000,
@@ -290,7 +295,7 @@
b2 := prep(t)
defer b2.close()
- join, err := st.AddNode(b.ctx, b2.privkey.Public().(ed25519.PublicKey), &AddNodeOption{
+ join, err := st.AddNode(b.ctx, "node2", b2.privkey.Public().(ed25519.PublicKey), &AddNodeOption{
externalAddress: "localhost",
externalPort: 3001,
})
@@ -301,6 +306,7 @@
etcd2 := New(Config{
Data: &b2.root.Data.Etcd,
Ephemeral: &b2.root.Ephemeral.Consensus,
+ NodeID: "node2",
NodePrivateKey: b2.privkey,
JoinCluster: join,
testOverrides: testOverrides{
diff --git a/metropolis/node/core/consensus/status.go b/metropolis/node/core/consensus/status.go
index ee3efbc..40988e5 100644
--- a/metropolis/node/core/consensus/status.go
+++ b/metropolis/node/core/consensus/status.go
@@ -12,7 +12,6 @@
"source.monogon.dev/metropolis/node"
"source.monogon.dev/metropolis/node/core/consensus/client"
- "source.monogon.dev/metropolis/node/core/identity"
"source.monogon.dev/osbase/event"
"source.monogon.dev/osbase/pki"
)
@@ -87,19 +86,18 @@
return s.cl
}
-// AddNode creates a new consensus member corresponding to a given Ed25519 node
-// public key if one does not yet exist. The member will at first be marked as a
+// AddNode creates a new consensus member corresponding to a given node ID
+// if one does not yet exist. The member will at first be marked as a
// Learner, ensuring it does not take part in quorum until it has finished
// catching up to the state of the etcd store. As it does, the autopromoter will
// turn it into a 'full' node and it will start taking part in the quorum and be
// able to perform all etcd operations.
-func (s *Status) AddNode(ctx context.Context, pk ed25519.PublicKey, opts ...*AddNodeOption) (*JoinCluster, error) {
+func (s *Status) AddNode(ctx context.Context, nodeID string, pk ed25519.PublicKey, opts ...*AddNodeOption) (*JoinCluster, error) {
clPKI, err := s.pkiClient()
if err != nil {
return nil, err
}
- nodeID := identity.NodeID(pk)
var extraNames []string
name := nodeID
port := int(node.ConsensusPort)
@@ -117,7 +115,7 @@
Name: nodeID,
Namespace: &pkiNamespace,
Issuer: s.ca,
- Template: pkiPeerCertificate(pk, extraNames),
+ Template: pkiPeerCertificate(nodeID, extraNames),
Mode: pki.CertificateExternal,
PublicKey: pk,
}
diff --git a/metropolis/node/core/curator/bootstrap.go b/metropolis/node/core/curator/bootstrap.go
index e092c63..bdc7f28 100644
--- a/metropolis/node/core/curator/bootstrap.go
+++ b/metropolis/node/core/curator/bootstrap.go
@@ -52,7 +52,7 @@
nodeCert := &pki.Certificate{
Namespace: &pkiNamespace,
Issuer: pkiCA,
- Template: identity.NodeCertificate(node.pubkey),
+ Template: identity.NodeCertificate(node.ID()),
Mode: pki.CertificateExternal,
PublicKey: node.pubkey,
Name: fmt.Sprintf("node-%s", node.ID()),
diff --git a/metropolis/node/core/curator/impl_leader_background.go b/metropolis/node/core/curator/impl_leader_background.go
index 46f78ec..1b07607 100644
--- a/metropolis/node/core/curator/impl_leader_background.go
+++ b/metropolis/node/core/curator/impl_leader_background.go
@@ -133,7 +133,7 @@
supervisor.Logger(ctx).Infof("Adding ConsensusMember role to node which is etcd member: %s...", nodeID)
// The node is already etcd member. We only call AddNode to obtain the
// join parameters.
- join, err := l.consensusStatus.AddNode(ctx, node.pubkey)
+ join, err := l.consensusStatus.AddNode(ctx, nodeID, node.pubkey)
if err != nil {
return fmt.Errorf("failed to obtain consensus join parameters: %w", err)
}
diff --git a/metropolis/node/core/curator/impl_leader_certificates.go b/metropolis/node/core/curator/impl_leader_certificates.go
index 73e28a9..40211fd 100644
--- a/metropolis/node/core/curator/impl_leader_certificates.go
+++ b/metropolis/node/core/curator/impl_leader_certificates.go
@@ -8,7 +8,6 @@
"google.golang.org/grpc/status"
ipb "source.monogon.dev/metropolis/node/core/curator/proto/api"
- "source.monogon.dev/metropolis/node/core/identity"
"source.monogon.dev/metropolis/node/core/rpc"
kpki "source.monogon.dev/metropolis/node/kubernetes/pki"
)
@@ -82,7 +81,7 @@
if pi == nil || pi.Node == nil {
return nil, status.Error(codes.PermissionDenied, "only nodes can request certificates")
}
- id := identity.NodeID(pi.Node.PublicKey)
+ id := pi.Node.ID
node, err := nodeLoad(ctx, l.leadership, id)
if err != nil {
return nil, status.Errorf(codes.Unavailable, "could not load node info: %v", err)
diff --git a/metropolis/node/core/curator/impl_leader_cluster_networking.go b/metropolis/node/core/curator/impl_leader_cluster_networking.go
index 5dab657..b2cc8ba 100644
--- a/metropolis/node/core/curator/impl_leader_cluster_networking.go
+++ b/metropolis/node/core/curator/impl_leader_cluster_networking.go
@@ -10,7 +10,6 @@
"google.golang.org/grpc/status"
ipb "source.monogon.dev/metropolis/node/core/curator/proto/api"
- "source.monogon.dev/metropolis/node/core/identity"
"source.monogon.dev/metropolis/node/core/rpc"
"source.monogon.dev/osbase/event"
"source.monogon.dev/osbase/event/etcd"
@@ -69,7 +68,7 @@
if pi == nil || pi.Node == nil {
return nil, status.Error(codes.PermissionDenied, "only nodes can update node cluster networking")
}
- id := identity.NodeID(pi.Node.PublicKey)
+ id := pi.Node.ID
if req.Clusternet == nil {
return nil, status.Error(codes.InvalidArgument, "clusternet must be set")
diff --git a/metropolis/node/core/curator/impl_leader_curator.go b/metropolis/node/core/curator/impl_leader_curator.go
index ec18688..d668dc4 100644
--- a/metropolis/node/core/curator/impl_leader_curator.go
+++ b/metropolis/node/core/curator/impl_leader_curator.go
@@ -227,7 +227,7 @@
if pi == nil || pi.Node == nil {
return nil, status.Error(codes.PermissionDenied, "only nodes can update node status")
}
- id := identity.NodeID(pi.Node.PublicKey)
+ id := pi.Node.ID
if id != req.NodeId {
return nil, status.Errorf(codes.PermissionDenied, "node %q cannot update the status of node %q", id, req.NodeId)
}
@@ -273,7 +273,7 @@
if pi == nil || pi.Node == nil {
return status.Error(codes.PermissionDenied, "only nodes can send heartbeats")
}
- id := identity.NodeID(pi.Node.PublicKey)
+ id := pi.Node.ID
for {
_, err := stream.Recv()
@@ -395,6 +395,7 @@
// No node exists, create one.
node = &Node{
+ id: id,
pubkey: pubkey,
jkey: req.JoinKey,
state: cpb.NodeState_NODE_STATE_NEW,
@@ -495,10 +496,10 @@
nodeCert := &pki.Certificate{
Namespace: &pkiNamespace,
Issuer: pkiCA,
- Template: identity.NodeCertificate(node.pubkey),
+ Template: identity.NodeCertificate(node.ID()),
Mode: pki.CertificateExternal,
PublicKey: node.pubkey,
- Name: fmt.Sprintf("node-%s", identity.NodeID(pubkey)),
+ Name: fmt.Sprintf("node-%s", node.ID()),
}
nodeCertBytes, err := nodeCert.Ensure(ctx, l.etcd)
if err != nil {
diff --git a/metropolis/node/core/curator/impl_leader_management.go b/metropolis/node/core/curator/impl_leader_management.go
index 338cc17..a7eaf5f 100644
--- a/metropolis/node/core/curator/impl_leader_management.go
+++ b/metropolis/node/core/curator/impl_leader_management.go
@@ -145,7 +145,7 @@
// heartbeat was received, given a current timestamp.
func (l *leaderManagement) nodeHealth(node *Node, now time.Time) (apb.Node_Health, time.Duration) {
// Get the last received node heartbeat's timestamp.
- nid := identity.NodeID(node.pubkey)
+ nid := node.ID()
nts := l.nodeHeartbeatTimestamp(nid)
// lhb is the duration since the last heartbeat was received.
lhb := now.Sub(nts)
@@ -225,7 +225,7 @@
entry := apb.Node{
Pubkey: node.pubkey,
- Id: identity.NodeID(node.pubkey),
+ Id: node.ID(),
State: node.state,
Status: node.status,
Roles: roles,
@@ -343,7 +343,7 @@
if req.ConsensusMember != nil {
if *req.ConsensusMember {
// Add a new etcd learner node.
- join, err := l.consensusStatus.AddNode(ctx, node.pubkey)
+ join, err := l.consensusStatus.AddNode(ctx, id, node.pubkey)
if err != nil {
return nil, status.Errorf(codes.Unavailable, "could not add node: %v", err)
}
diff --git a/metropolis/node/core/curator/impl_leader_test.go b/metropolis/node/core/curator/impl_leader_test.go
index a6ad457..bbdf936 100644
--- a/metropolis/node/core/curator/impl_leader_test.go
+++ b/metropolis/node/core/curator/impl_leader_test.go
@@ -113,6 +113,7 @@
nodeID := identity.NodeID(nodePub)
cNode := NewNodeForBootstrap(&NewNodeData{
CUK: nil,
+ ID: nodeID,
Pubkey: nodePub,
JPub: nodeJoinPub,
TPMUsage: cpb.NodeTPMUsage_NODE_TPM_PRESENT_AND_USED,
@@ -357,6 +358,7 @@
cuk := []byte("fakefakefakefakefakefakefakefake")
node := &Node{
clusterUnlockKey: cuk,
+ id: identity.NodeID(npub),
pubkey: npub,
jkey: jpub,
}
@@ -768,7 +770,7 @@
if err != nil {
t.Fatalf("Recv failed: %v", err)
}
- if identity.NodeID(node.Pubkey) != cl.otherNodeID {
+ if node.Id != cl.otherNodeID {
continue
}
if node.State != state {
@@ -833,6 +835,7 @@
cuk := []byte("fakefakefakefakefakefakefakefake")
node := Node{
clusterUnlockKey: cuk,
+ id: identity.NodeID(npub),
pubkey: npub,
jkey: jpub,
state: cpb.NodeState_NODE_STATE_UP,
@@ -965,7 +968,7 @@
if err != nil {
t.Fatalf("Recv failed: %v", err)
}
- if id != identity.NodeID(node.Pubkey) {
+ if id != node.Id {
continue
}
if node.Health != health {
@@ -1027,8 +1030,10 @@
t.Fatalf("could not generate join keypair: %v", err)
}
cuk := []byte("fakefakefakefakefakefakefakefake")
+ nodeID := identity.NodeID(npub)
node := Node{
clusterUnlockKey: cuk,
+ id: nodeID,
pubkey: npub,
jkey: jpub,
state: cpb.NodeState_NODE_STATE_NEW,
@@ -1036,7 +1041,7 @@
if err := nodeSave(ctx, cl.l, &node); err != nil {
t.Fatalf("nodeSave failed: %v", err)
}
- expectNode(identity.NodeID(npub), apb.Node_UNKNOWN)
+ expectNode(nodeID, apb.Node_UNKNOWN)
}
// TestManagementClusterInfo exercises GetClusterInfo after setting a status.
@@ -1159,9 +1164,8 @@
// Exercise duration-based filtering. Start with setting up node and
// leadership timestamps much like in TestClusterHeartbeat.
tsn := putNode(t, ctx, cl.l, func(n *Node) { n.state = cpb.NodeState_NODE_STATE_UP })
- nid := identity.NodeID(tsn.pubkey)
// Last of node's tsn heartbeats were received 5 seconds ago,
- cl.l.ls.heartbeatTimestamps.Store(nid, time.Now().Add(-5*time.Second))
+ cl.l.ls.heartbeatTimestamps.Store(tsn.id, time.Now().Add(-5*time.Second))
// ...while the current leader's tenure started 15 seconds ago.
cl.l.ls.startTs = time.Now().Add(-15 * time.Second)
@@ -1989,7 +1993,7 @@
// Add a cluster node with ConsensusMember role, and an etcd member with a
// different name.
newNode := putNode(t, ctx, cl.l, func(n *Node) {
- join, err := cl.l.consensusStatus.AddNode(ctx, n.pubkey)
+ join, err := cl.l.consensusStatus.AddNode(ctx, "foo", n.pubkey)
if err != nil {
t.Fatalf("failed to obtain consensus join parameters: %v", err)
}
diff --git a/metropolis/node/core/curator/state_node.go b/metropolis/node/core/curator/state_node.go
index 0cfc87e..e574f9d 100644
--- a/metropolis/node/core/curator/state_node.go
+++ b/metropolis/node/core/curator/state_node.go
@@ -60,6 +60,9 @@
// node's ESP partition.
clusterUnlockKey []byte
+ // id is the Node's ID.
+ id string
+
// pubkey is the ED25519 public key corresponding to the node's private key
// which it stores on its local data partition. The private part of the key
// never leaves the node.
@@ -108,6 +111,7 @@
type NewNodeData struct {
CUK []byte
+ ID string
Pubkey []byte
JPub []byte
TPMUsage cpb.NodeTPMUsage
@@ -121,6 +125,7 @@
func NewNodeForBootstrap(n *NewNodeData) Node {
return Node{
clusterUnlockKey: n.CUK,
+ id: n.ID,
pubkey: n.Pubkey,
jkey: n.JPub,
state: cpb.NodeState_NODE_STATE_UP,
@@ -170,13 +175,13 @@
URL string
}
-// ID returns the name of this node. See NodeID for more information.
+// ID returns the name of this node.
func (n *Node) ID() string {
- return identity.NodeID(n.pubkey)
+ return n.id
}
func (n *Node) String() string {
- return n.ID()
+ return n.id
}
// KubernetesWorker returns a copy of the NodeRoleKubernetesWorker struct if
@@ -315,6 +320,7 @@
}
n := &Node{
clusterUnlockKey: msg.ClusterUnlockKey,
+ id: identity.NodeID(msg.PublicKey),
pubkey: msg.PublicKey,
jkey: msg.JoinKey,
state: msg.FsmState,
diff --git a/metropolis/node/core/identity/certificates.go b/metropolis/node/core/identity/certificates.go
index fca55b7..7735ae6 100644
--- a/metropolis/node/core/identity/certificates.go
+++ b/metropolis/node/core/identity/certificates.go
@@ -22,10 +22,10 @@
}
// NodeCertificate makes a Metropolis-compatible node certificate template.
-func NodeCertificate(pubkey ed25519.PublicKey) x509.Certificate {
+func NodeCertificate(nodeID string) x509.Certificate {
return x509.Certificate{
Subject: pkix.Name{
- CommonName: NodeID(pubkey),
+ CommonName: nodeID,
},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment,
ExtKeyUsage: []x509.ExtKeyUsage{
@@ -39,7 +39,7 @@
// certificate for ease of use within Metropolis, where the local DNS setup
// allows each node's IP address to be resolvable through the Node's ID.
DNSNames: []string{
- NodeID(pubkey),
+ nodeID,
},
}
}
@@ -123,12 +123,12 @@
// VerifyNodeInCluster ensures that a given certificate is a Metropolis node
// certificate emitted by a given Metropolis CA.
//
-// The node's public key is returned if verification is successful, and error is
+// The node's ID is returned if verification is successful, and error is
// returned otherwise.
-func VerifyNodeInCluster(node, ca *x509.Certificate) (ed25519.PublicKey, error) {
+func VerifyNodeInCluster(node, ca *x509.Certificate) (string, error) {
pk, err := VerifyInCluster(node, ca)
if err != nil {
- return nil, err
+ return "", err
}
// Ensure certificate has ServerAuth bit, thereby marking it as a node certificate.
@@ -140,14 +140,14 @@
}
}
if !found {
- return nil, fmt.Errorf("not a node certificate (missing ServerAuth key usage)")
+ return "", fmt.Errorf("not a node certificate (missing ServerAuth key usage)")
}
id := NodeID(pk)
// Ensure node ID is present in Subject.CommonName and at least one DNS name.
if node.Subject.CommonName != id {
- return nil, fmt.Errorf("node ID not found in CommonName")
+ return "", fmt.Errorf("node ID not found in CommonName")
}
found = false
@@ -158,10 +158,10 @@
}
}
if !found {
- return nil, fmt.Errorf("node ID not found in DNSNames")
+ return "", fmt.Errorf("node ID not found in DNSNames")
}
- return pk, nil
+ return id, nil
}
// VerifyUserInCluster ensures that a given certificate is a Metropolis user
diff --git a/metropolis/node/core/identity/certificates_test.go b/metropolis/node/core/identity/certificates_test.go
index f96f517..d2dd0cd 100644
--- a/metropolis/node/core/identity/certificates_test.go
+++ b/metropolis/node/core/identity/certificates_test.go
@@ -59,7 +59,7 @@
t.Fatalf("ParseCertificate (CA): %v", err)
}
- nodeTemplate := NodeCertificate(nodePub)
+ nodeTemplate := NodeCertificate(NodeID(nodePub))
basic(&nodeTemplate)
fnode(&nodeTemplate)
nodeCertBytes, err = x509.CreateCertificate(rand.Reader, &nodeTemplate, caCert, nodePub, caPriv)
diff --git a/metropolis/node/core/roleserve/roleserve.go b/metropolis/node/core/roleserve/roleserve.go
index 3bcc48d..e5e0d74 100644
--- a/metropolis/node/core/roleserve/roleserve.go
+++ b/metropolis/node/core/roleserve/roleserve.go
@@ -183,6 +183,7 @@
type BootstrapData struct {
// Data about the bootstrapping node.
Node struct {
+ ID string
PrivateKey ed25519.PrivateKey
// CUK/NUK for storage, if storage encryption is enabled.
@@ -209,12 +210,9 @@
}
func (s *Service) ProvideBootstrapData(data *BootstrapData) {
- pubkey := data.Node.PrivateKey.Public().(ed25519.PublicKey)
- nid := identity.NodeID(pubkey)
-
// This is the first time we have the node ID, tell the resolver that it's
// available on the loopback interface.
- s.Resolver.AddOverride(nid, resolver.NodeByHostPort("127.0.0.1", uint16(common.CuratorServicePort)))
+ s.Resolver.AddOverride(data.Node.ID, resolver.NodeByHostPort("127.0.0.1", uint16(common.CuratorServicePort)))
s.Resolver.AddEndpoint(resolver.NodeByHostPort("127.0.0.1", uint16(common.CuratorServicePort)))
s.bootstrapData.Set(data)
diff --git a/metropolis/node/core/roleserve/values.go b/metropolis/node/core/roleserve/values.go
index 34c743b..aa0c227 100644
--- a/metropolis/node/core/roleserve/values.go
+++ b/metropolis/node/core/roleserve/values.go
@@ -63,7 +63,7 @@
}
func (c *CuratorConnection) nodeID() string {
- return identity.NodeID(c.Credentials.PublicKey())
+ return c.Credentials.ID()
}
// KubernetesStatus is an Event Value structure populated by a running
diff --git a/metropolis/node/core/roleserve/worker_controlplane.go b/metropolis/node/core/roleserve/worker_controlplane.go
index 3ba4293..8b4657a 100644
--- a/metropolis/node/core/roleserve/worker_controlplane.go
+++ b/metropolis/node/core/roleserve/worker_controlplane.go
@@ -145,6 +145,7 @@
consensusConfig: &consensus.Config{
Data: &s.storageRoot.Data.Etcd,
Ephemeral: &s.storageRoot.Ephemeral.Consensus,
+ NodeID: bd.Node.ID,
NodePrivateKey: bd.Node.PrivateKey,
},
bootstrap: bd,
@@ -197,6 +198,7 @@
consensusConfig: &consensus.Config{
Data: &s.storageRoot.Data.Etcd,
Ephemeral: &s.storageRoot.Ephemeral.Consensus,
+ NodeID: cc.nodeID(),
NodePrivateKey: cc.Credentials.TLSCredentials().PrivateKey.(ed25519.PrivateKey),
JoinCluster: &consensus.JoinCluster{
CACertificate: caCert,
@@ -274,6 +276,7 @@
n := curator.NewNodeForBootstrap(&curator.NewNodeData{
CUK: b.Node.ClusterUnlockKey,
+ ID: b.Node.ID,
Pubkey: npub,
JPub: jpub,
TPMUsage: b.Node.TPMUsage,
@@ -281,7 +284,7 @@
})
// The first node always runs consensus.
- join, err := st.AddNode(ctx, npub)
+ join, err := st.AddNode(ctx, b.Node.ID, npub)
if err != nil {
return fmt.Errorf("when retrieving node join data from consensus: %w", err)
}
diff --git a/metropolis/node/core/rpc/client.go b/metropolis/node/core/rpc/client.go
index 5fc76e3..72122b7 100644
--- a/metropolis/node/core/rpc/client.go
+++ b/metropolis/node/core/rpc/client.go
@@ -39,15 +39,12 @@
if err != nil {
return fmt.Errorf("server presented unparseable certificate: %w", err)
}
- pkey, err := identity.VerifyNodeInCluster(serverCert, ca)
+ id, err := identity.VerifyNodeInCluster(serverCert, ca)
if err != nil {
return fmt.Errorf("node certificate verification failed: %w", err)
}
- if nodeID != "" {
- id := identity.NodeID(pkey)
- if id != nodeID {
- return fmt.Errorf("wanted to reach node %q, got %q", nodeID, id)
- }
+ if nodeID != "" && id != nodeID {
+ return fmt.Errorf("wanted to reach node %q, got %q", nodeID, id)
}
return nil
diff --git a/metropolis/node/core/rpc/peerinfo.go b/metropolis/node/core/rpc/peerinfo.go
index 6a8443b..55f949c 100644
--- a/metropolis/node/core/rpc/peerinfo.go
+++ b/metropolis/node/core/rpc/peerinfo.go
@@ -11,7 +11,6 @@
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
- "source.monogon.dev/metropolis/node/core/identity"
epb "source.monogon.dev/metropolis/proto/ext"
)
@@ -51,8 +50,8 @@
// PeerInfoNode contains information about a Node on the other side of a gRPC
// connection.
type PeerInfoNode struct {
- // PublicKey is the ED25519 public key bytes of the node.
- PublicKey []byte
+ // ID is the node identifier.
+ ID string
// Permissions are the set of permissions this node has.
Permissions Permissions
@@ -121,7 +120,7 @@
}
switch {
case p.Node != nil:
- return fmt.Sprintf("node: %s, %s", identity.NodeID(p.Node.PublicKey), p.Node.Permissions)
+ return fmt.Sprintf("node: %s, %s", p.Node.ID, p.Node.Permissions)
case p.User != nil:
return fmt.Sprintf("user: %s", p.User.Identity)
case p.Unauthenticated != nil:
diff --git a/metropolis/node/core/rpc/server_authentication.go b/metropolis/node/core/rpc/server_authentication.go
index eed7dba..37c8fad 100644
--- a/metropolis/node/core/rpc/server_authentication.go
+++ b/metropolis/node/core/rpc/server_authentication.go
@@ -161,7 +161,7 @@
return nil, status.Errorf(codes.Unauthenticated, "certificate not signed by cluster CA: %v", err)
}
- nodepk, errNode := identity.VerifyNodeInCluster(cert, s.NodeCredentials.ClusterCA())
+ id, errNode := identity.VerifyNodeInCluster(cert, s.NodeCredentials.ClusterCA())
if errNode == nil {
// This is a Metropolis node.
np := s.nodePermissions
@@ -170,7 +170,7 @@
}
return &PeerInfo{
Node: &PeerInfoNode{
- PublicKey: nodepk,
+ ID: id,
Permissions: np,
},
}, nil