m/n/c/curator: return CA public key in GetClusterInfo
This is needed for node registration (and is generally useful data
whenever a caller might not be aware of the CA's public key but already
has access to a Management client). In theory, all callers should be
aware of the public key, but in the future some other cluster
verification might be performed with the CA public key ignored on
connectivity, but used by some other logic.
Change-Id: If1928435bd5606c733460eb1a4a29a6578c8c723
Reviewed-on: https://review.monogon.dev/c/monogon/+/342
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/metropolis/node/core/curator/impl_leader.go b/metropolis/node/core/curator/impl_leader.go
index 04665db..9e25097 100644
--- a/metropolis/node/core/curator/impl_leader.go
+++ b/metropolis/node/core/curator/impl_leader.go
@@ -11,6 +11,7 @@
"google.golang.org/grpc/status"
"source.monogon.dev/metropolis/node/core/consensus/client"
+ "source.monogon.dev/metropolis/node/core/identity"
)
// leadership represents the curator leader's ability to perform actions as a
@@ -85,10 +86,10 @@
leaderManagement
}
-func newCuratorLeader(l *leadership) *curatorLeader {
+func newCuratorLeader(l *leadership, node *identity.Node) *curatorLeader {
return &curatorLeader{
leaderCurator{leadership: l},
leaderAAA{leadership: l},
- leaderManagement{leadership: l},
+ leaderManagement{leadership: l, node: node},
}
}
diff --git a/metropolis/node/core/curator/impl_leader_management.go b/metropolis/node/core/curator/impl_leader_management.go
index aa2d54c..849f9b8 100644
--- a/metropolis/node/core/curator/impl_leader_management.go
+++ b/metropolis/node/core/curator/impl_leader_management.go
@@ -3,6 +3,7 @@
import (
"bytes"
"context"
+ "crypto/ed25519"
"crypto/rand"
"sort"
@@ -12,12 +13,19 @@
"google.golang.org/protobuf/proto"
ppb "source.monogon.dev/metropolis/node/core/curator/proto/private"
+ "source.monogon.dev/metropolis/node/core/identity"
apb "source.monogon.dev/metropolis/proto/api"
cpb "source.monogon.dev/metropolis/proto/common"
)
type leaderManagement struct {
*leadership
+
+ // node certificate on which leaderManagement runs. It's used by
+ // GetClusterInformation which needs access to the CA pubkey.
+ // Alternatively this could be stored in etcd, instead of being dependency
+ // injected here.
+ node *identity.Node
}
const (
@@ -130,5 +138,6 @@
return &apb.GetClusterInfoResponse{
ClusterDirectory: directory,
+ CaPublicKey: l.node.ClusterCA().PublicKey.(ed25519.PublicKey),
}, nil
}
diff --git a/metropolis/node/core/curator/impl_leader_test.go b/metropolis/node/core/curator/impl_leader_test.go
index 059c444..0b87892 100644
--- a/metropolis/node/core/curator/impl_leader_test.go
+++ b/metropolis/node/core/curator/impl_leader_test.go
@@ -3,6 +3,8 @@
import (
"bytes"
"context"
+ "crypto/ed25519"
+ "encoding/hex"
"testing"
"go.etcd.io/etcd/integration"
@@ -55,18 +57,17 @@
}
lockRev := res.Header.Revision
+ // Build a test cluster PKI and node/manager certificates.
+ ephemeral := rpc.NewEphemeralClusterCredentials(t, 2)
+ nodeCredentials := ephemeral.Nodes[0]
+
// Build a curator leader object. This implements methods that will be
// exercised by tests.
leader := newCuratorLeader(&leadership{
lockKey: lockKey,
lockRev: lockRev,
etcd: cl,
- })
-
- // Build a test cluster PKI and node/manager certificates, and create the
- // listener security parameters which will authenticate incoming requests.
- ephemeral := rpc.NewEphemeralClusterCredentials(t, 2)
- nodeCredentials := ephemeral.Nodes[0]
+ }, &nodeCredentials.Node)
cNode := NewNodeForBootstrap(nil, nodeCredentials.PublicKey())
// Inject new node into leader, using curator bootstrap functionality.
@@ -130,6 +131,7 @@
localNodeConn: lcl,
localNodeID: nodeCredentials.ID(),
otherNodeID: ephemeral.Nodes[1].ID(),
+ caPubKey: ephemeral.CA.PublicKey.(ed25519.PublicKey),
cancel: ctxC,
}
}
@@ -149,6 +151,8 @@
// otherNodeID is the NodeID of some other node present in the curator
// state.
otherNodeID string
+ // caPubKey is the public key of the CA for this cluster.
+ caPubKey ed25519.PublicKey
// cancel shuts down the fake leader and all client connections.
cancel context.CancelFunc
}
@@ -292,6 +296,11 @@
t.Fatalf("ClusterDirectory.Nodes[0].Addresses has %d elements, wanted %d", want, got)
}
if want, got := "192.0.2.10", node.Addresses[0].Host; want != got {
- t.Fatalf("Nodes[0].Addresses[0].Host is %q, wanted %q", want, got)
+ t.Errorf("Nodes[0].Addresses[0].Host is %q, wanted %q", want, got)
+ }
+
+ // Cluster CA public key should match
+ if want, got := cl.caPubKey, res.CaPublicKey; !bytes.Equal(want, got) {
+ t.Fatalf("CaPublicKey mismatch (wanted %s, got %s)", hex.EncodeToString(want), hex.EncodeToString(got))
}
}
diff --git a/metropolis/node/core/curator/listener.go b/metropolis/node/core/curator/listener.go
index c4f0062..8a8dcd8 100644
--- a/metropolis/node/core/curator/listener.go
+++ b/metropolis/node/core/curator/listener.go
@@ -149,7 +149,7 @@
lockKey: leader.lockKey,
lockRev: leader.lockRev,
etcd: l.etcd,
- })
+ }, &l.node.Node)
} else {
supervisor.Logger(ctx).Info("Dispatcher switching over to follower")
t.impl = &curatorFollower{}
diff --git a/metropolis/proto/api/management.proto b/metropolis/proto/api/management.proto
index 309fb19..9f0e646 100644
--- a/metropolis/proto/api/management.proto
+++ b/metropolis/proto/api/management.proto
@@ -44,4 +44,7 @@
// cluster_directory contains information about individual nodes in the
// cluster that can be used to dial the cluster's services.
metropolis.proto.common.ClusterDirectory cluster_directory = 1;
+
+ // ca_public_key is the Ed25519 public key of the CA of the cluster.
+ bytes ca_public_key = 2;
}