blob: 0e3c29a743cfa04b21c5af31f6ee3c24442df637 [file] [log] [blame]
Serge Bazanski439b95e2021-06-30 23:16:13 +02001package cluster
2
3import (
4 "crypto/ed25519"
5 "crypto/subtle"
Serge Bazanski5b2ae552021-08-17 13:00:14 +02006 "crypto/tls"
Serge Bazanski439b95e2021-06-30 23:16:13 +02007 "crypto/x509"
8 "fmt"
9
Serge Bazanski5b2ae552021-08-17 13:00:14 +020010 "google.golang.org/grpc/credentials"
11
Serge Bazanski439b95e2021-06-30 23:16:13 +020012 "source.monogon.dev/metropolis/node/core/curator"
13 "source.monogon.dev/metropolis/node/core/localstorage"
14)
15
16// NodeCertificate is the public part of the credentials of a node. They are
17// emitted for a node by the cluster CA contained within the curator.
18type NodeCertificate struct {
19 node *x509.Certificate
20 ca *x509.Certificate
21}
22
23// NodeCredentials are the public and private part of the credentials of a node.
24//
25// It represents all the data necessary for a node to authenticate over mTLS to
26// other nodes and the rest of the cluster.
27//
28// It must never be made available to any node other than the node it has been
29// emitted for.
30type NodeCredentials struct {
31 NodeCertificate
32 private ed25519.PrivateKey
33}
34
35// NewNodeCertificate wraps a pair CA and node DER-encoded certificates into
36// NodeCertificate, ensuring the given certificate data is valid and compatible
37// Metropolis assumptions.
38//
39// It does _not_ verify that the given CA is a known/trusted Metropolis CA for a
40// running cluster.
41func NewNodeCertificate(cert, ca []byte) (*NodeCertificate, error) {
42 certParsed, err := x509.ParseCertificate(cert)
43 if err != nil {
44 return nil, fmt.Errorf("could not parse node certificate: %w", err)
45 }
46 caCertParsed, err := x509.ParseCertificate(ca)
47 if err != nil {
48 return nil, fmt.Errorf("could not parse ca certificate: %w", err)
49 }
50
51 // Ensure both CA and node certs use ED25519.
52 if certParsed.PublicKeyAlgorithm != x509.Ed25519 {
53 return nil, fmt.Errorf("node certificate must use ED25519, is %s", certParsed.PublicKeyAlgorithm.String())
54 }
55 if pub, ok := certParsed.PublicKey.(ed25519.PublicKey); !ok || len(pub) != ed25519.PublicKeySize {
56 return nil, fmt.Errorf("node certificate ED25519 key invalid")
57 }
58 if caCertParsed.PublicKeyAlgorithm != x509.Ed25519 {
59 return nil, fmt.Errorf("CA certificate must use ED25519, is %s", caCertParsed.PublicKeyAlgorithm.String())
60 }
61 if pub, ok := caCertParsed.PublicKey.(ed25519.PublicKey); !ok || len(pub) != ed25519.PublicKeySize {
62 return nil, fmt.Errorf("CA certificate ED25519 key invalid")
63 }
64
65 // Ensure that the certificate is signed by the CA certificate.
66 if err := certParsed.CheckSignatureFrom(caCertParsed); err != nil {
67 return nil, fmt.Errorf("certificate not signed by given CA: %w", err)
68 }
69
70 // Ensure that the certificate has the node's calculated ID in its DNS names.
71 found := false
72 nid := curator.NodeID(certParsed.PublicKey.(ed25519.PublicKey))
73 for _, n := range certParsed.DNSNames {
74 if n == nid {
75 found = true
76 break
77 }
78 }
79 if !found {
80 return nil, fmt.Errorf("calculated node ID %q not found in node certificate's DNS names (%v)", nid, certParsed.DNSNames)
81 }
82
83 return &NodeCertificate{
84 node: certParsed,
85 ca: caCertParsed,
86 }, nil
87}
88
89// NewNodeCredentials wraps a pair of CA and node DER-encoded certificates plus
90// a private key into NodeCredentials, ensuring that the given data is valid and
91// compatible with Metropolis assumptions.
92//
93// It does _not_ verify that the given CA is a known/trusted Metropolis CA for a
94// running cluster.
95func NewNodeCredentials(priv, cert, ca []byte) (*NodeCredentials, error) {
96 nc, err := NewNodeCertificate(cert, ca)
97 if err != nil {
98 return nil, err
99 }
100
101 // Ensure that the private key is a valid length.
102 if want, got := ed25519.PrivateKeySize, len(priv); want != got {
103 return nil, fmt.Errorf("private key is not the correct length, wanted %d, got %d", want, got)
104 }
105
106 // Ensure that the given private key matches the given public key.
107 if want, got := ed25519.PrivateKey(priv).Public().(ed25519.PublicKey), nc.PublicKey(); subtle.ConstantTimeCompare(want, got) != 1 {
108 return nil, fmt.Errorf("public key does not match private key")
109 }
110
111 return &NodeCredentials{
112 NodeCertificate: *nc,
113 private: ed25519.PrivateKey(priv),
114 }, nil
115}
116
117// Save stores the given node credentials in local storage.
118func (c *NodeCredentials) Save(d *localstorage.PKIDirectory) error {
119 if err := d.CACertificate.Write(c.ca.Raw, 0400); err != nil {
120 return fmt.Errorf("when writing CA certificate: %w", err)
121 }
122 if err := d.Certificate.Write(c.node.Raw, 0400); err != nil {
123 return fmt.Errorf("when writing node certificate: %w", err)
124 }
125 if err := d.Key.Write(c.private, 0400); err != nil {
126 return fmt.Errorf("when writing node private key: %w", err)
127 }
128 return nil
129}
130
131// PublicKey returns the ED25519 public key corresponding to this node's
132// certificate/credentials.
133func (nc *NodeCertificate) PublicKey() ed25519.PublicKey {
134 // Safe: we have ensured that the given certificate has an ed25519 public key on
135 // NewNodeCertificate.
136 return nc.node.PublicKey.(ed25519.PublicKey)
137}
138
139// ID returns the canonical ID/name of the node for which this
140// certificate/credentials were emitted.
141func (nc *NodeCertificate) ID() string {
142 return curator.NodeID(nc.PublicKey())
143}
Serge Bazanski5b2ae552021-08-17 13:00:14 +0200144
145// PublicGRPCServerCredentials returns gRPC TransportCredentials that should be
146// used by this node to run public gRPC services (ie. the AAA service and any
147// other management/user services).
148//
149// SECURITY: The returned TransportCredentials accepts _any_ client certificate
150// served by the client and does not perform any verification. The gRPC service
151// instance (via per-method checks or middleware) should perform user
152// authentication/authorization.
153func (nc *NodeCredentials) PublicGRPCServerCredentials() credentials.TransportCredentials {
154 tlsCert := tls.Certificate{
155 Certificate: [][]byte{nc.node.Raw},
156 PrivateKey: nc.private,
157 }
158 return credentials.NewTLS(&tls.Config{
159 Certificates: []tls.Certificate{tlsCert},
160 ClientAuth: tls.RequireAnyClientCert,
161 })
162}