blob: 72122b7b179901cd99b41b2af0500c572ad63db2 [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 }
Jan Schär39d9c242024-09-24 13:49:55 +020042 id, err := identity.VerifyNodeInCluster(serverCert, ca)
Serge Bazanski8535cb52023-03-29 14:15:08 +020043 if err != nil {
Serge Bazanski3379a5d2021-09-09 12:56:40 +020044 return fmt.Errorf("node certificate verification failed: %w", err)
45 }
Jan Schär39d9c242024-09-24 13:49:55 +020046 if nodeID != "" && id != nodeID {
47 return fmt.Errorf("wanted to reach node %q, got %q", nodeID, id)
Serge Bazanski8535cb52023-03-29 14:15:08 +020048 }
Serge Bazanski3379a5d2021-09-09 12:56:40 +020049
50 return nil
51 }
52}
53
Serge Bazanski8535cb52023-03-29 14:15:08 +020054func verifyFail(err error) verifyPeerCertificate {
55 return func(_ [][]byte, _ [][]*x509.Certificate) error {
56 return err
57 }
58}
59
Serge Bazanski399ce552022-03-29 12:52:42 +020060// NewEphemeralCredentials returns gRPC TransportCredentials that can be used to
61// dial a cluster without authenticating with a certificate, but instead
62// authenticating by proving the possession of a private key, via an ephemeral
63// self-signed certificate.
Serge Bazanskid7d6e022021-09-01 15:03:06 +020064//
Serge Bazanski399ce552022-03-29 12:52:42 +020065// Currently these credentials are used in two flows:
66//
Serge Bazanski8535cb52023-03-29 14:15:08 +020067// 1. Registration of nodes into a cluster, after which a node receives a proper
68// node certificate
Serge Bazanski399ce552022-03-29 12:52:42 +020069//
Serge Bazanski8535cb52023-03-29 14:15:08 +020070// 2. Escrow of initial owner credentials into a proper manager
71// certificate
Serge Bazanskid7d6e022021-09-01 15:03:06 +020072//
Serge Bazanski0c280152024-02-05 14:33:19 +010073// The given opts can be used to lock down the remote side of the connection, eg.
74// expecting a given cluster CA certificate or disabling remote side verification
75// by using WantInsecure().
76func NewEphemeralCredentials(private ed25519.PrivateKey, opts ...CredentialsOpt) (credentials.TransportCredentials, error) {
Serge Bazanskid7d6e022021-09-01 15:03:06 +020077 template := x509.Certificate{
78 SerialNumber: big.NewInt(1),
79 NotBefore: time.Now(),
Serge Bazanski4ac71122023-07-24 13:08:34 +020080 NotAfter: UnknownNotAfter,
Serge Bazanskid7d6e022021-09-01 15:03:06 +020081
Serge Bazanski3379a5d2021-09-09 12:56:40 +020082 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
Serge Bazanskid7d6e022021-09-01 15:03:06 +020083 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
84 BasicConstraintsValid: true,
85 }
86 certificateBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, private.Public(), private)
87 if err != nil {
88 return nil, fmt.Errorf("when generating self-signed certificate: %w", err)
89 }
90 certificate := tls.Certificate{
91 Certificate: [][]byte{certificateBytes},
92 PrivateKey: private,
93 }
Serge Bazanski8535cb52023-03-29 14:15:08 +020094 return NewAuthenticatedCredentials(certificate, opts...), nil
95}
96
Serge Bazanski0c280152024-02-05 14:33:19 +010097// CredentialsOpt are created using WantXXX functions and used in
98// NewCredentials.
99type CredentialsOpt struct {
Serge Bazanski8535cb52023-03-29 14:15:08 +0200100 wantCA *x509.Certificate
101 wantNodeID string
102 insecureOkay bool
103}
104
Serge Bazanski0c280152024-02-05 14:33:19 +0100105func (a *CredentialsOpt) merge(o *CredentialsOpt) {
Serge Bazanski8535cb52023-03-29 14:15:08 +0200106 if a.wantNodeID == "" && o.wantNodeID != "" {
107 a.wantNodeID = o.wantNodeID
108 }
109 if a.wantCA == nil && o.wantCA != nil {
110 a.wantCA = o.wantCA
111 }
112 if !a.insecureOkay && o.insecureOkay {
113 a.insecureOkay = o.insecureOkay
114 }
115}
116
117// WantRemoteCluster enables the verification of the remote cluster identity when
118// using NewAuthanticatedCredentials. If the connection is not terminated at a
119// cluster with the given CA certificate, an error will be returned.
120//
121// This is the bare minimum option required to implement secure connections to
122// clusters.
Serge Bazanski0c280152024-02-05 14:33:19 +0100123func WantRemoteCluster(ca *x509.Certificate) CredentialsOpt {
124 return CredentialsOpt{
Serge Bazanski8535cb52023-03-29 14:15:08 +0200125 wantCA: ca,
126 }
127}
128
129// WantRemoteNode enables the verification of the remote node identity when using
Serge Bazanski0c280152024-02-05 14:33:19 +0100130// NewCredentials. If the connection is not terminated at the node
Serge Bazanski8535cb52023-03-29 14:15:08 +0200131// ID 'id', an error will be returned. For this function to work,
132// WantRemoteCluster must also be set.
Serge Bazanski0c280152024-02-05 14:33:19 +0100133func WantRemoteNode(id string) CredentialsOpt {
134 return CredentialsOpt{
Serge Bazanski8535cb52023-03-29 14:15:08 +0200135 wantNodeID: id,
136 }
137}
138
139// WantInsecure disables the verification of the remote side of the connection
Serge Bazanski0c280152024-02-05 14:33:19 +0100140// via NewCredentials. This is unsafe.
141func WantInsecure() CredentialsOpt {
142 return CredentialsOpt{
Serge Bazanski8535cb52023-03-29 14:15:08 +0200143 insecureOkay: true,
144 }
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200145}
Serge Bazanskid7d6e022021-09-01 15:03:06 +0200146
Serge Bazanskia3e38cf2024-07-31 14:40:04 +0000147// NewAuthenticatedTLSConfig returns a tls.Config that can be used to dial a
148// cluster with a given TLS certificate (from node or manager credentials).
Serge Bazanski399ce552022-03-29 12:52:42 +0200149//
Serge Bazanski0c280152024-02-05 14:33:19 +0100150// The provided CredentialsOpt specify the verification of the remote side of the
151// connection. When connecting to a cluster (any node), use WantRemoteCluster. If
152// you also want to verify the connection to a particular node, specify
153// WantRemoteNode alongside it. If no verification should be performed use
154// WantInsecure.
Serge Bazanski8535cb52023-03-29 14:15:08 +0200155//
156// The given options are parsed on a first-wins basis.
Serge Bazanskia3e38cf2024-07-31 14:40:04 +0000157func NewAuthenticatedTLSConfig(cert tls.Certificate, opts ...CredentialsOpt) *tls.Config {
Serge Bazanski399ce552022-03-29 12:52:42 +0200158 config := &tls.Config{
159 Certificates: []tls.Certificate{cert},
160 InsecureSkipVerify: true,
161 }
Serge Bazanski8535cb52023-03-29 14:15:08 +0200162
Serge Bazanski0c280152024-02-05 14:33:19 +0100163 var merged CredentialsOpt
Serge Bazanski8535cb52023-03-29 14:15:08 +0200164 for _, o := range opts {
165 merged.merge(&o)
Serge Bazanski399ce552022-03-29 12:52:42 +0200166 }
Serge Bazanski8535cb52023-03-29 14:15:08 +0200167
168 if merged.insecureOkay {
Serge Bazanski0c280152024-02-05 14:33:19 +0100169 if merged.wantNodeID != "" {
170 config.VerifyPeerCertificate = verifyFail(fmt.Errorf("WantInsecure specified alongside WantRemoteNode"))
171 } else if merged.wantCA != nil {
172 config.VerifyPeerCertificate = verifyFail(fmt.Errorf("WantInsecure specified alongside WantRemoteCluster"))
Serge Bazanski8535cb52023-03-29 14:15:08 +0200173 }
174 } else {
175 switch {
Serge Bazanski0c280152024-02-05 14:33:19 +0100176 case merged.wantNodeID == "" && merged.wantCA == nil:
177 config.VerifyPeerCertificate = verifyFail(fmt.Errorf("WantRemoteNode/WantRemoteCluster/WantInsecure not specified"))
Serge Bazanski8535cb52023-03-29 14:15:08 +0200178 case merged.wantNodeID != "" && merged.wantCA == nil:
179 config.VerifyPeerCertificate = verifyFail(fmt.Errorf("WantRemoteNode also requires WantRemoteCluster"))
180 case merged.wantCA == nil:
181 config.VerifyPeerCertificate = verifyFail(fmt.Errorf("no AuthenticaedCreentialsOpts specified"))
182 default:
183 config.VerifyPeerCertificate = verifyClusterCertificateAndNodeID(merged.wantCA, merged.wantNodeID)
184 }
185 }
186
Serge Bazanskia3e38cf2024-07-31 14:40:04 +0000187 return config
188}
189
190// NewAuthenticatedCredentials returns gRPC TransportCredentials that can be used
191// to dial a cluster with a given TLS certificate (from node or manager
192// credentials).
193//
194// The provided CredentialsOpt specify the verification of the remote side of the
195// connection. When connecting to a cluster (any node), use WantRemoteCluster. If
196// you also want to verify the connection to a particular node, specify
197// WantRemoteNode alongside it. If no verification should be performed use
198// WantInsecure.
199//
200// The given options are parsed on a first-wins basis.
201func NewAuthenticatedCredentials(cert tls.Certificate, opts ...CredentialsOpt) credentials.TransportCredentials {
202 return credentials.NewTLS(NewAuthenticatedTLSConfig(cert, opts...))
Serge Bazanskid7d6e022021-09-01 15:03:06 +0200203}
204
Serge Bazanski8535cb52023-03-29 14:15:08 +0200205// RetrieveOwnerCertificate uses AAA.Escrow to retrieve a cluster manager
Serge Bazanskid7d6e022021-09-01 15:03:06 +0200206// certificate for the initial owner of the cluster, authenticated by the
207// public/private key set in the clusters NodeParameters.ClusterBoostrap.
208//
209// The retrieved certificate can be used to dial further cluster RPCs.
210func RetrieveOwnerCertificate(ctx context.Context, aaa apb.AAAClient, private ed25519.PrivateKey) (*tls.Certificate, error) {
211 srv, err := aaa.Escrow(ctx)
212 if err != nil {
Serge Bazanski636032e2022-01-26 14:21:33 +0100213 if st, ok := status.FromError(err); ok {
214 return nil, status.Errorf(st.Code(), "Escrow call failed: %s", st.Message())
215 }
216 return nil, err
Serge Bazanskid7d6e022021-09-01 15:03:06 +0200217 }
218 if err := srv.Send(&apb.EscrowFromClient{
219 Parameters: &apb.EscrowFromClient_Parameters{
220 RequestedIdentityName: "owner",
221 PublicKey: private.Public().(ed25519.PublicKey),
222 },
223 }); err != nil {
224 return nil, fmt.Errorf("when sending client parameters: %w", err)
225 }
226 resp, err := srv.Recv()
227 if err != nil {
228 return nil, fmt.Errorf("when receiving server message: %w", err)
229 }
230 if len(resp.EmittedCertificate) == 0 {
231 return nil, fmt.Errorf("expected certificate, instead got needed proofs: %+v", resp.Needed)
232 }
233
234 return &tls.Certificate{
235 Certificate: [][]byte{resp.EmittedCertificate},
236 PrivateKey: private,
237 }, nil
238}