metropolis: implement cluster configuration

This adds a cluster configuration to Metropolis. We'll be keeping any
non-node-specific options there. The config is stored in etcd by the
curator.

An initial cluster configuration can be specified when bootstrapping a
cluster. By design the configuration is then immutable by default, but
we might add some purpose-specific management API calls to change some
values if needed.

We initialize the cluster configuration with a setting for node TPM
policy, 'TPMMode'. It's currently populated on cluster bootstrap, but
not used otherwise. That will come in a follow-up CR.

Change-Id: I44ddcd099c9ae68c20519c77e3fa77c894cf5a20
Reviewed-on: https://review.monogon.dev/c/monogon/+/1494
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
Tested-by: Jenkins CI
diff --git a/metropolis/node/core/roleserve/roleserve.go b/metropolis/node/core/roleserve/roleserve.go
index d2e5bf0..f97e9c9 100644
--- a/metropolis/node/core/roleserve/roleserve.go
+++ b/metropolis/node/core/roleserve/roleserve.go
@@ -1,4 +1,4 @@
-// package roleserve implements the roleserver/“Role Server”.
+// Package roleserve implements the roleserver/“Role Server”.
 //
 // The Role Server runs on every node and is responsible for running all of the
 // node's role dependant services, like the control plane (Consensus/etcd and
@@ -45,6 +45,7 @@
 
 	common "source.monogon.dev/metropolis/node"
 	"source.monogon.dev/metropolis/node/core/clusternet"
+	"source.monogon.dev/metropolis/node/core/curator"
 	"source.monogon.dev/metropolis/node/core/identity"
 	"source.monogon.dev/metropolis/node/core/localstorage"
 	"source.monogon.dev/metropolis/node/core/network"
@@ -164,7 +165,7 @@
 	return s
 }
 
-func (s *Service) ProvideBootstrapData(privkey ed25519.PrivateKey, iok, cuk, nuk, jkey []byte) {
+func (s *Service) ProvideBootstrapData(privkey ed25519.PrivateKey, iok, cuk, nuk, jkey []byte, icc *curator.Cluster) {
 	pubkey := privkey.Public().(ed25519.PublicKey)
 	nid := identity.NodeID(pubkey)
 
@@ -177,11 +178,12 @@
 		resolver: s.Resolver,
 	})
 	s.bootstrapData.Set(&bootstrapData{
-		nodePrivateKey:     privkey,
-		initialOwnerKey:    iok,
-		clusterUnlockKey:   cuk,
-		nodeUnlockKey:      nuk,
-		nodePrivateJoinKey: jkey,
+		nodePrivateKey:              privkey,
+		initialOwnerKey:             iok,
+		clusterUnlockKey:            cuk,
+		nodeUnlockKey:               nuk,
+		nodePrivateJoinKey:          jkey,
+		initialClusterConfiguration: icc,
 	})
 }
 
diff --git a/metropolis/node/core/roleserve/value_bootstrapdata.go b/metropolis/node/core/roleserve/value_bootstrapdata.go
index 29a6ae2..90af955 100644
--- a/metropolis/node/core/roleserve/value_bootstrapdata.go
+++ b/metropolis/node/core/roleserve/value_bootstrapdata.go
@@ -2,6 +2,8 @@
 
 import (
 	"crypto/ed25519"
+
+	"source.monogon.dev/metropolis/node/core/curator"
 )
 
 // bootstrapData is an internal EventValue structure which is populated by the
@@ -9,9 +11,10 @@
 // the control plane logic to go into bootstrap mode and bring up a control
 // plane from scratch.
 type bootstrapData struct {
-	nodePrivateKey     ed25519.PrivateKey
-	clusterUnlockKey   []byte
-	nodeUnlockKey      []byte
-	initialOwnerKey    []byte
-	nodePrivateJoinKey ed25519.PrivateKey
+	nodePrivateKey              ed25519.PrivateKey
+	clusterUnlockKey            []byte
+	nodeUnlockKey               []byte
+	initialOwnerKey             []byte
+	nodePrivateJoinKey          ed25519.PrivateKey
+	initialClusterConfiguration *curator.Cluster
 }
diff --git a/metropolis/node/core/roleserve/worker_controlplane.go b/metropolis/node/core/roleserve/worker_controlplane.go
index 885b39e..1a8e420 100644
--- a/metropolis/node/core/roleserve/worker_controlplane.go
+++ b/metropolis/node/core/roleserve/worker_controlplane.go
@@ -295,7 +295,7 @@
 				n.EnableKubernetesController()
 
 				var nodeCert []byte
-				caCert, nodeCert, err = curator.BootstrapNodeFinish(ctx, ckv, &n, b.initialOwnerKey)
+				caCert, nodeCert, err = curator.BootstrapNodeFinish(ctx, ckv, &n, b.initialOwnerKey, b.initialClusterConfiguration)
 				if err != nil {
 					return fmt.Errorf("while bootstrapping node: %w", err)
 				}