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/cluster/BUILD.bazel b/metropolis/node/core/cluster/BUILD.bazel
index 1a724c1..93cad93 100644
--- a/metropolis/node/core/cluster/BUILD.bazel
+++ b/metropolis/node/core/cluster/BUILD.bazel
@@ -11,6 +11,7 @@
     importpath = "source.monogon.dev/metropolis/node/core/cluster",
     visibility = ["//metropolis/node/core:__subpackages__"],
     deps = [
+        "//metropolis/node/core/curator",
         "//metropolis/node/core/curator/proto/api",
         "//metropolis/node/core/identity",
         "//metropolis/node/core/localstorage",
diff --git a/metropolis/node/core/cluster/cluster.go b/metropolis/node/core/cluster/cluster.go
index adfc632..8a1a3ae 100644
--- a/metropolis/node/core/cluster/cluster.go
+++ b/metropolis/node/core/cluster/cluster.go
@@ -44,6 +44,7 @@
 	networkService *network.Service
 	roleServer     *roleserve.Service
 	nodeParams     *apb.NodeParameters
+	haveTPM        bool
 
 	oneway chan struct{}
 }
@@ -51,12 +52,13 @@
 // NewManager creates a new cluster Manager. The given localstorage Root must
 // be places, but not yet started (and will be started as the Manager makes
 // progress). The given network Service must already be running.
-func NewManager(storageRoot *localstorage.Root, networkService *network.Service, rs *roleserve.Service, nodeParams *apb.NodeParameters) *Manager {
+func NewManager(storageRoot *localstorage.Root, networkService *network.Service, rs *roleserve.Service, nodeParams *apb.NodeParameters, haveTPM bool) *Manager {
 	return &Manager{
 		storageRoot:    storageRoot,
 		networkService: networkService,
 		roleServer:     rs,
 		nodeParams:     nodeParams,
+		haveTPM:        haveTPM,
 		oneway:         make(chan struct{}),
 	}
 }
diff --git a/metropolis/node/core/cluster/cluster_bootstrap.go b/metropolis/node/core/cluster/cluster_bootstrap.go
index 0b51e1a..4f85fdc 100644
--- a/metropolis/node/core/cluster/cluster_bootstrap.go
+++ b/metropolis/node/core/cluster/cluster_bootstrap.go
@@ -24,7 +24,9 @@
 	"fmt"
 	"time"
 
+	"source.monogon.dev/metropolis/node/core/curator"
 	"source.monogon.dev/metropolis/pkg/supervisor"
+
 	apb "source.monogon.dev/metropolis/proto/api"
 	ppb "source.monogon.dev/metropolis/proto/private"
 )
@@ -32,6 +34,26 @@
 func (m *Manager) bootstrap(ctx context.Context, bootstrap *apb.NodeParameters_ClusterBootstrap) error {
 	supervisor.Logger(ctx).Infof("Bootstrapping new cluster, owner public key: %s", hex.EncodeToString(bootstrap.OwnerPublicKey))
 
+	var cc *curator.Cluster
+
+	if bootstrap.InitialClusterConfiguration == nil {
+		supervisor.Logger(ctx).Infof("No initial cluster configuration provided, using defaults.")
+		cc = curator.DefaultClusterConfiguration()
+	} else {
+		var err error
+		cc, err = curator.ClusterConfigurationFromInitial(bootstrap.InitialClusterConfiguration)
+		return fmt.Errorf("invalid initial cluster configuration: %w", err)
+	}
+
+	useTPM, err := cc.UseTPM(m.haveTPM)
+	if err != nil {
+		return fmt.Errorf("cannot join cluster: %w", err)
+	}
+
+	supervisor.Logger(ctx).Infof("TPM: cluster TPM mode: %s", cc.TPMMode)
+	supervisor.Logger(ctx).Infof("TPM: present in this node: %v", m.haveTPM)
+	supervisor.Logger(ctx).Infof("TPM: used by this node: %v", useTPM)
+
 	ownerKey := bootstrap.OwnerPublicKey
 	configuration := ppb.SealedConfiguration{}
 
@@ -67,7 +89,7 @@
 	}
 	supervisor.Logger(ctx).Infof("Bootstrapping: node public join key: %s", hex.EncodeToString([]byte(jpub)))
 
-	m.roleServer.ProvideBootstrapData(priv, ownerKey, cuk, nuk, jpriv)
+	m.roleServer.ProvideBootstrapData(priv, ownerKey, cuk, nuk, jpriv, cc)
 
 	supervisor.Signal(ctx, supervisor.SignalHealthy)
 	supervisor.Signal(ctx, supervisor.SignalDone)