blob: aeaed7ee41ad3bf3c6bc7a2c14aede373ede511b [file] [log] [blame]
Serge Bazanskid7d6e022021-09-01 15:03:06 +02001package rpc
2
3import (
4 "context"
5 "crypto/ed25519"
6 "crypto/rand"
7 "crypto/tls"
8 "crypto/x509"
9 "fmt"
10 "math/big"
11 "time"
12
Serge Bazanskid7d6e022021-09-01 15:03:06 +020013 "google.golang.org/grpc/credentials"
Serge Bazanski636032e2022-01-26 14:21:33 +010014 "google.golang.org/grpc/status"
Serge Bazanskid7d6e022021-09-01 15:03:06 +020015
Serge Bazanski3379a5d2021-09-09 12:56:40 +020016 "source.monogon.dev/metropolis/node/core/identity"
Serge Bazanskid7d6e022021-09-01 15:03:06 +020017 apb "source.monogon.dev/metropolis/proto/api"
18)
19
Serge Bazanski4ac71122023-07-24 13:08:34 +020020// UnknownNotAfter is a copy of //metroplis/pkg/pki.UnknownNotAfter.
21//
22// We copy it so that we can decouple the rpc package from the pki package, the
23// former being used by metroctl (and thus needing to be portable), the latter
24// having a dependency on fileargs (which isn't portable). The correct solution
25// here is to clarify portability policy of each workspace path, and apply it.
26// But this will do for now.
27//
28// TODO(issues/252): clean up and merge this back.
29var UnknownNotAfter = time.Unix(253402300799, 0)
30
Serge Bazanski3379a5d2021-09-09 12:56:40 +020031type verifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
32
Serge Bazanski8535cb52023-03-29 14:15:08 +020033func verifyClusterCertificateAndNodeID(ca *x509.Certificate, nodeID string) verifyPeerCertificate {
Serge Bazanski3379a5d2021-09-09 12:56:40 +020034 return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
35 if len(rawCerts) != 1 {
36 return fmt.Errorf("server presented %d certificates, wanted exactly one", len(rawCerts))
37 }
38 serverCert, err := x509.ParseCertificate(rawCerts[0])
39 if err != nil {
40 return fmt.Errorf("server presented unparseable certificate: %w", err)
41 }
Serge Bazanski8535cb52023-03-29 14:15:08 +020042 pkey, err := identity.VerifyNodeInCluster(serverCert, ca)
43 if err != nil {
Serge Bazanski3379a5d2021-09-09 12:56:40 +020044 return fmt.Errorf("node certificate verification failed: %w", err)
45 }
Serge Bazanski8535cb52023-03-29 14:15:08 +020046 if nodeID != "" {
47 id := identity.NodeID(pkey)
48 if id != nodeID {
49 return fmt.Errorf("wanted to reach node %q, got %q", nodeID, id)
50 }
51 }
Serge Bazanski3379a5d2021-09-09 12:56:40 +020052
53 return nil
54 }
55}
56
Serge Bazanski8535cb52023-03-29 14:15:08 +020057func verifyFail(err error) verifyPeerCertificate {
58 return func(_ [][]byte, _ [][]*x509.Certificate) error {
59 return err
60 }
61}
62
Serge Bazanski399ce552022-03-29 12:52:42 +020063// NewEphemeralCredentials returns gRPC TransportCredentials that can be used to
64// dial a cluster without authenticating with a certificate, but instead
65// authenticating by proving the possession of a private key, via an ephemeral
66// self-signed certificate.
Serge Bazanskid7d6e022021-09-01 15:03:06 +020067//
Serge Bazanski399ce552022-03-29 12:52:42 +020068// Currently these credentials are used in two flows:
69//
Serge Bazanski8535cb52023-03-29 14:15:08 +020070// 1. Registration of nodes into a cluster, after which a node receives a proper
71// node certificate
Serge Bazanski399ce552022-03-29 12:52:42 +020072//
Serge Bazanski8535cb52023-03-29 14:15:08 +020073// 2. Escrow of initial owner credentials into a proper manager
74// certificate
Serge Bazanskid7d6e022021-09-01 15:03:06 +020075//
Serge Bazanski3379a5d2021-09-09 12:56:40 +020076// If 'ca' is given, the remote side will be cryptographically verified to be a
77// node that's part of the cluster represented by the ca. Otherwise, no
78// verification is performed and this function is unsafe.
Serge Bazanski399ce552022-03-29 12:52:42 +020079func NewEphemeralCredentials(private ed25519.PrivateKey, ca *x509.Certificate) (credentials.TransportCredentials, error) {
Serge Bazanskid7d6e022021-09-01 15:03:06 +020080 template := x509.Certificate{
81 SerialNumber: big.NewInt(1),
82 NotBefore: time.Now(),
Serge Bazanski4ac71122023-07-24 13:08:34 +020083 NotAfter: UnknownNotAfter,
Serge Bazanskid7d6e022021-09-01 15:03:06 +020084
Serge Bazanski3379a5d2021-09-09 12:56:40 +020085 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
Serge Bazanskid7d6e022021-09-01 15:03:06 +020086 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
87 BasicConstraintsValid: true,
88 }
89 certificateBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, private.Public(), private)
90 if err != nil {
91 return nil, fmt.Errorf("when generating self-signed certificate: %w", err)
92 }
93 certificate := tls.Certificate{
94 Certificate: [][]byte{certificateBytes},
95 PrivateKey: private,
96 }
Serge Bazanski8535cb52023-03-29 14:15:08 +020097 var opts []AuthenticatedCredentialsOpt
98 if ca != nil {
99 opts = append(opts, WantRemoteCluster(ca))
100 } else {
101 opts = append(opts, WantInsecure())
102 }
103 return NewAuthenticatedCredentials(certificate, opts...), nil
104}
105
106// AuthenticatedCredentialsOpt are created using WantXXX functions and used in
107// NewAuthenticatedCredentials.
108type AuthenticatedCredentialsOpt struct {
109 wantCA *x509.Certificate
110 wantNodeID string
111 insecureOkay bool
112}
113
114func (a *AuthenticatedCredentialsOpt) merge(o *AuthenticatedCredentialsOpt) {
115 if a.wantNodeID == "" && o.wantNodeID != "" {
116 a.wantNodeID = o.wantNodeID
117 }
118 if a.wantCA == nil && o.wantCA != nil {
119 a.wantCA = o.wantCA
120 }
121 if !a.insecureOkay && o.insecureOkay {
122 a.insecureOkay = o.insecureOkay
123 }
124}
125
126// WantRemoteCluster enables the verification of the remote cluster identity when
127// using NewAuthanticatedCredentials. If the connection is not terminated at a
128// cluster with the given CA certificate, an error will be returned.
129//
130// This is the bare minimum option required to implement secure connections to
131// clusters.
132func WantRemoteCluster(ca *x509.Certificate) AuthenticatedCredentialsOpt {
133 return AuthenticatedCredentialsOpt{
134 wantCA: ca,
135 }
136}
137
138// WantRemoteNode enables the verification of the remote node identity when using
139// NewAuthenticatedCredentials. If the connection is not terminated at the node
140// ID 'id', an error will be returned. For this function to work,
141// WantRemoteCluster must also be set.
142func WantRemoteNode(id string) AuthenticatedCredentialsOpt {
143 return AuthenticatedCredentialsOpt{
144 wantNodeID: id,
145 }
146}
147
148// WantInsecure disables the verification of the remote side of the connection
149// via NewAuthenticatedCredentials. This is unsafe.
150func WantInsecure() AuthenticatedCredentialsOpt {
151 return AuthenticatedCredentialsOpt{
152 insecureOkay: true,
153 }
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200154}
Serge Bazanskid7d6e022021-09-01 15:03:06 +0200155
Serge Bazanski399ce552022-03-29 12:52:42 +0200156// NewAuthenticatedCredentials returns gRPC TransportCredentials that can be
157// used to dial a cluster with a given TLS certificate (from node or manager
158// credentials).
159//
Serge Bazanski8535cb52023-03-29 14:15:08 +0200160// The provided AuthenticatedCredentialsOpt specify the verification of the
161// remote side of the connection. When connecting to a cluster (any node), use
162// WantRemoteCluster. If you also want to verify the connection to a particular
163// node, specify WantRemoteNode alongside it. If no verification should be
164// performed use WantInsecure.
165//
166// The given options are parsed on a first-wins basis.
167func NewAuthenticatedCredentials(cert tls.Certificate, opts ...AuthenticatedCredentialsOpt) credentials.TransportCredentials {
Serge Bazanski399ce552022-03-29 12:52:42 +0200168 config := &tls.Config{
169 Certificates: []tls.Certificate{cert},
170 InsecureSkipVerify: true,
171 }
Serge Bazanski8535cb52023-03-29 14:15:08 +0200172
173 var merged AuthenticatedCredentialsOpt
174 for _, o := range opts {
175 merged.merge(&o)
Serge Bazanski399ce552022-03-29 12:52:42 +0200176 }
Serge Bazanski8535cb52023-03-29 14:15:08 +0200177
178 if merged.insecureOkay {
179 if merged.wantNodeID != "" || merged.wantCA != nil {
180 config.VerifyPeerCertificate = verifyFail(fmt.Errorf("WantInsecure specified alongside WantRemoteNode/WantRemoteCluster"))
181 }
182 } else {
183 switch {
184 case merged.wantNodeID != "" && merged.wantCA == nil:
185 config.VerifyPeerCertificate = verifyFail(fmt.Errorf("WantRemoteNode also requires WantRemoteCluster"))
186 case merged.wantCA == nil:
187 config.VerifyPeerCertificate = verifyFail(fmt.Errorf("no AuthenticaedCreentialsOpts specified"))
188 default:
189 config.VerifyPeerCertificate = verifyClusterCertificateAndNodeID(merged.wantCA, merged.wantNodeID)
190 }
191 }
192
Serge Bazanski399ce552022-03-29 12:52:42 +0200193 return credentials.NewTLS(config)
Serge Bazanskid7d6e022021-09-01 15:03:06 +0200194}
195
Serge Bazanski8535cb52023-03-29 14:15:08 +0200196// RetrieveOwnerCertificate uses AAA.Escrow to retrieve a cluster manager
Serge Bazanskid7d6e022021-09-01 15:03:06 +0200197// certificate for the initial owner of the cluster, authenticated by the
198// public/private key set in the clusters NodeParameters.ClusterBoostrap.
199//
200// The retrieved certificate can be used to dial further cluster RPCs.
201func RetrieveOwnerCertificate(ctx context.Context, aaa apb.AAAClient, private ed25519.PrivateKey) (*tls.Certificate, error) {
202 srv, err := aaa.Escrow(ctx)
203 if err != nil {
Serge Bazanski636032e2022-01-26 14:21:33 +0100204 if st, ok := status.FromError(err); ok {
205 return nil, status.Errorf(st.Code(), "Escrow call failed: %s", st.Message())
206 }
207 return nil, err
Serge Bazanskid7d6e022021-09-01 15:03:06 +0200208 }
209 if err := srv.Send(&apb.EscrowFromClient{
210 Parameters: &apb.EscrowFromClient_Parameters{
211 RequestedIdentityName: "owner",
212 PublicKey: private.Public().(ed25519.PublicKey),
213 },
214 }); err != nil {
215 return nil, fmt.Errorf("when sending client parameters: %w", err)
216 }
217 resp, err := srv.Recv()
218 if err != nil {
219 return nil, fmt.Errorf("when receiving server message: %w", err)
220 }
221 if len(resp.EmittedCertificate) == 0 {
222 return nil, fmt.Errorf("expected certificate, instead got needed proofs: %+v", resp.Needed)
223 }
224
225 return &tls.Certificate{
226 Certificate: [][]byte{resp.EmittedCertificate},
227 PrivateKey: private,
228 }, nil
229}