metropolis: finish implementing TPMMode

This wraps up the implementation of TPMMode in ClusterConfiguration,
allowing operators to select whether nodes should or should not use
their TPM, based on local availability.

We keep the default behaviour to require a TPM, as we'd like to be
secure by default.

Change-Id: Ic8ac76d88ecc9de51f58ca99c92daede79d78ad7
Reviewed-on: https://review.monogon.dev/c/monogon/+/1495
Tested-by: Jenkins CI
Reviewed-by: Lorenz Brun <lorenz@monogon.tech>
diff --git a/metropolis/node/core/cluster/cluster.go b/metropolis/node/core/cluster/cluster.go
index 8a1a3ae..529120b 100644
--- a/metropolis/node/core/cluster/cluster.go
+++ b/metropolis/node/core/cluster/cluster.go
@@ -73,6 +73,7 @@
 	}
 	close(m.oneway)
 
+	// Try sealed configuration first.
 	configuration, err := m.storageRoot.ESP.Metropolis.SealedConfiguration.Unseal()
 	if err == nil {
 		supervisor.Logger(ctx).Info("Sealed configuration present. attempting to join cluster")
@@ -84,13 +85,27 @@
 		if err != nil {
 			return fmt.Errorf("while reading cluster directory: %w", err)
 		}
-		return m.join(ctx, configuration, cd)
+		return m.join(ctx, configuration, cd, true)
 	}
 
 	if !errors.Is(err, localstorage.ErrNoSealed) {
 		return fmt.Errorf("unexpected sealed config error: %w", err)
 	}
 
+	configuration, err = m.storageRoot.ESP.Metropolis.SealedConfiguration.ReadUnsafe()
+	if err == nil {
+		supervisor.Logger(ctx).Info("Non-sealed configuration present. attempting to join cluster")
+
+		// Read Cluster Directory and unmarshal it. Since the node is already
+		// registered with the cluster, the directory won't be bootstrapped from
+		// Node Parameters.
+		cd, err := m.storageRoot.ESP.Metropolis.ClusterDirectory.Unmarshal()
+		if err != nil {
+			return fmt.Errorf("while reading cluster directory: %w", err)
+		}
+		return m.join(ctx, configuration, cd, false)
+	}
+
 	supervisor.Logger(ctx).Info("No sealed configuration, looking for node parameters")
 
 	switch inner := m.nodeParams.Cluster.(type) {
diff --git a/metropolis/node/core/cluster/cluster_bootstrap.go b/metropolis/node/core/cluster/cluster_bootstrap.go
index 4f85fdc..78dea0b 100644
--- a/metropolis/node/core/cluster/cluster_bootstrap.go
+++ b/metropolis/node/core/cluster/cluster_bootstrap.go
@@ -45,14 +45,13 @@
 		return fmt.Errorf("invalid initial cluster configuration: %w", err)
 	}
 
-	useTPM, err := cc.UseTPM(m.haveTPM)
+	tpmUsage, err := cc.NodeTPMUsage(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)
+	supervisor.Logger(ctx).Infof("TPM: node TPM usage: %s", tpmUsage)
 
 	ownerKey := bootstrap.OwnerPublicKey
 	configuration := ppb.SealedConfiguration{}
@@ -89,7 +88,7 @@
 	}
 	supervisor.Logger(ctx).Infof("Bootstrapping: node public join key: %s", hex.EncodeToString([]byte(jpub)))
 
-	m.roleServer.ProvideBootstrapData(priv, ownerKey, cuk, nuk, jpriv, cc)
+	m.roleServer.ProvideBootstrapData(priv, ownerKey, cuk, nuk, jpriv, cc, tpmUsage)
 
 	supervisor.Signal(ctx, supervisor.SignalHealthy)
 	supervisor.Signal(ctx, supervisor.SignalDone)
diff --git a/metropolis/node/core/cluster/cluster_join.go b/metropolis/node/core/cluster/cluster_join.go
index 2daea52..4fd6473 100644
--- a/metropolis/node/core/cluster/cluster_join.go
+++ b/metropolis/node/core/cluster/cluster_join.go
@@ -20,7 +20,7 @@
 )
 
 // join implements Join Flow of an already registered node.
-func (m *Manager) join(ctx context.Context, sc *ppb.SealedConfiguration, cd *cpb.ClusterDirectory) error {
+func (m *Manager) join(ctx context.Context, sc *ppb.SealedConfiguration, cd *cpb.ClusterDirectory, sealed bool) error {
 	// Generate a complete ED25519 Join Key based on the seed included in Sealed
 	// Configuration.
 	var jpriv ed25519.PrivateKey = sc.JoinKey
@@ -34,6 +34,7 @@
 	// Tell the user what we're doing.
 	hpkey := hex.EncodeToString(jpriv.Public().(ed25519.PublicKey))
 	supervisor.Logger(ctx).Infof("Joining an existing cluster.")
+	supervisor.Logger(ctx).Infof("  Using TPM-secured configuration: %v", sealed)
 	supervisor.Logger(ctx).Infof("  Node Join public key: %s", hpkey)
 	supervisor.Logger(ctx).Infof("  Directory:")
 	logClusterDirectory(ctx, cd)
@@ -75,7 +76,9 @@
 	bo := backoff.NewExponentialBackOff()
 	bo.MaxElapsedTime = 0
 	backoff.Retry(func() error {
-		jr, err = cur.JoinNode(ctx, &ipb.JoinNodeRequest{})
+		jr, err = cur.JoinNode(ctx, &ipb.JoinNodeRequest{
+			UsingSealedConfiguration: sealed,
+		})
 		if err != nil {
 			supervisor.Logger(ctx).Warningf("Join failed: %v", err)
 			// This is never used.
diff --git a/metropolis/node/core/cluster/cluster_register.go b/metropolis/node/core/cluster/cluster_register.go
index 6ef9763..0096fa7 100644
--- a/metropolis/node/core/cluster/cluster_register.go
+++ b/metropolis/node/core/cluster/cluster_register.go
@@ -14,11 +14,12 @@
 	"google.golang.org/grpc"
 	"google.golang.org/protobuf/proto"
 
-	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/metropolis/node/core/rpc/resolver"
 	"source.monogon.dev/metropolis/pkg/supervisor"
+
+	ipb "source.monogon.dev/metropolis/node/core/curator/proto/api"
 	apb "source.monogon.dev/metropolis/proto/api"
 	ppb "source.monogon.dev/metropolis/proto/private"
 )
@@ -139,14 +140,18 @@
 	// logic into some sort of state machine where we can atomically make progress
 	// on each of the stages and get rid of the retry loops. The cluster enrolment
 	// code should let us do this quite easily.
-	_, err = cur.RegisterNode(ctx, &ipb.RegisterNodeRequest{
+	res, err := cur.RegisterNode(ctx, &ipb.RegisterNodeRequest{
 		RegisterTicket: register.RegisterTicket,
 		JoinKey:        jpub,
+		HaveLocalTpm:   m.haveTPM,
 	})
 	if err != nil {
 		return fmt.Errorf("register call failed: %w", err)
 	}
 
+	supervisor.Logger(ctx).Infof("TPM: cluster TPM mode: %s", res.ClusterConfiguration.TpmMode)
+	supervisor.Logger(ctx).Infof("TPM: node TPM usage: %v", res.TpmUsage)
+
 	// Attempt to commit in a loop. This will succeed once the node is approved.
 	supervisor.Logger(ctx).Infof("Registering: success, attempting to commit...")
 	var certBytes, caCertBytes []byte
@@ -185,7 +190,7 @@
 	// Include the Cluster CA in Sealed Configuration.
 	sc.ClusterCa = register.CaCertificate
 	// Save Cluster CA, NUK and Join Credentials into Sealed Configuration.
-	if err = m.storageRoot.ESP.Metropolis.SealedConfiguration.SealSecureBoot(&sc); err != nil {
+	if err = m.storageRoot.ESP.Metropolis.SealedConfiguration.SealSecureBoot(&sc, res.TpmUsage); err != nil {
 		return err
 	}
 	unix.Sync()