m/n/c/{cluster,roleserve}: implement Join Flow

This implements Join Flow for:
- Registered nodes attempting to re-join the cluster.
- Nodes bootstrapping the cluster.

See: Cluster Lifecycle and Integrity design document

Change-Id: I74ab98fdec650c4f6aa59e34a16c0f95745dc0e9
Reviewed-on: https://review.monogon.dev/c/monogon/+/556
Reviewed-by: Sergiusz Bazanski <serge@monogon.tech>
diff --git a/metropolis/node/core/identity/identity.go b/metropolis/node/core/identity/identity.go
index 862e794..8749b6d 100644
--- a/metropolis/node/core/identity/identity.go
+++ b/metropolis/node/core/identity/identity.go
@@ -124,6 +124,35 @@
 	return nil
 }
 
+// Read initializes NodeCredentials' contents with the data stored in the
+// PKIDirectory d. It may return an I/O error, or a parsing error.
+func (n *NodeCredentials) Read(d *localstorage.PKIDirectory) error {
+	if car, err := d.CACertificate.Read(); err != nil {
+		return fmt.Errorf("while reading CA certificate: %w", err)
+	} else {
+		cert, err := x509.ParseCertificate(car)
+		if err != nil {
+			return fmt.Errorf("while parsing CA certificate: %w", err)
+		}
+		n.ca = cert
+	}
+	if nr, err := d.Certificate.Read(); err != nil {
+		return fmt.Errorf("while reading node certificate: %w", err)
+	} else {
+		cert, err := x509.ParseCertificate(nr)
+		if err != nil {
+			return fmt.Errorf("while parsing node certificate: %w", err)
+		}
+		n.node = cert
+	}
+	if npr, err := d.Key.Read(); err != nil {
+		return fmt.Errorf("while reading node private key: %w", err)
+	} else {
+		n.private = npr
+	}
+	return nil
+}
+
 // NodeIDBare returns the `{pubkeyHash}` part of the node ID.
 func NodeIDBare(pub []byte) string {
 	return hex.EncodeToString(pub[:16])