blob: 656fee54be4d79a4f97bd6f9bd946421908de463 [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 "source.monogon.dev/metropolis/pkg/pki"
18 apb "source.monogon.dev/metropolis/proto/api"
19)
20
Serge Bazanski3379a5d2021-09-09 12:56:40 +020021type verifyPeerCertificate func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error
22
Serge Bazanski8535cb52023-03-29 14:15:08 +020023func verifyClusterCertificateAndNodeID(ca *x509.Certificate, nodeID string) verifyPeerCertificate {
Serge Bazanski3379a5d2021-09-09 12:56:40 +020024 return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
25 if len(rawCerts) != 1 {
26 return fmt.Errorf("server presented %d certificates, wanted exactly one", len(rawCerts))
27 }
28 serverCert, err := x509.ParseCertificate(rawCerts[0])
29 if err != nil {
30 return fmt.Errorf("server presented unparseable certificate: %w", err)
31 }
Serge Bazanski8535cb52023-03-29 14:15:08 +020032 pkey, err := identity.VerifyNodeInCluster(serverCert, ca)
33 if err != nil {
Serge Bazanski3379a5d2021-09-09 12:56:40 +020034 return fmt.Errorf("node certificate verification failed: %w", err)
35 }
Serge Bazanski8535cb52023-03-29 14:15:08 +020036 if nodeID != "" {
37 id := identity.NodeID(pkey)
38 if id != nodeID {
39 return fmt.Errorf("wanted to reach node %q, got %q", nodeID, id)
40 }
41 }
Serge Bazanski3379a5d2021-09-09 12:56:40 +020042
43 return nil
44 }
45}
46
Serge Bazanski8535cb52023-03-29 14:15:08 +020047func verifyFail(err error) verifyPeerCertificate {
48 return func(_ [][]byte, _ [][]*x509.Certificate) error {
49 return err
50 }
51}
52
Serge Bazanski399ce552022-03-29 12:52:42 +020053// NewEphemeralCredentials returns gRPC TransportCredentials that can be used to
54// dial a cluster without authenticating with a certificate, but instead
55// authenticating by proving the possession of a private key, via an ephemeral
56// self-signed certificate.
Serge Bazanskid7d6e022021-09-01 15:03:06 +020057//
Serge Bazanski399ce552022-03-29 12:52:42 +020058// Currently these credentials are used in two flows:
59//
Serge Bazanski8535cb52023-03-29 14:15:08 +020060// 1. Registration of nodes into a cluster, after which a node receives a proper
61// node certificate
Serge Bazanski399ce552022-03-29 12:52:42 +020062//
Serge Bazanski8535cb52023-03-29 14:15:08 +020063// 2. Escrow of initial owner credentials into a proper manager
64// certificate
Serge Bazanskid7d6e022021-09-01 15:03:06 +020065//
Serge Bazanski3379a5d2021-09-09 12:56:40 +020066// If 'ca' is given, the remote side will be cryptographically verified to be a
67// node that's part of the cluster represented by the ca. Otherwise, no
68// verification is performed and this function is unsafe.
Serge Bazanski399ce552022-03-29 12:52:42 +020069func NewEphemeralCredentials(private ed25519.PrivateKey, ca *x509.Certificate) (credentials.TransportCredentials, error) {
Serge Bazanskid7d6e022021-09-01 15:03:06 +020070 template := x509.Certificate{
71 SerialNumber: big.NewInt(1),
72 NotBefore: time.Now(),
73 NotAfter: pki.UnknownNotAfter,
74
Serge Bazanski3379a5d2021-09-09 12:56:40 +020075 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
Serge Bazanskid7d6e022021-09-01 15:03:06 +020076 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
77 BasicConstraintsValid: true,
78 }
79 certificateBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, private.Public(), private)
80 if err != nil {
81 return nil, fmt.Errorf("when generating self-signed certificate: %w", err)
82 }
83 certificate := tls.Certificate{
84 Certificate: [][]byte{certificateBytes},
85 PrivateKey: private,
86 }
Serge Bazanski8535cb52023-03-29 14:15:08 +020087 var opts []AuthenticatedCredentialsOpt
88 if ca != nil {
89 opts = append(opts, WantRemoteCluster(ca))
90 } else {
91 opts = append(opts, WantInsecure())
92 }
93 return NewAuthenticatedCredentials(certificate, opts...), nil
94}
95
96// AuthenticatedCredentialsOpt are created using WantXXX functions and used in
97// NewAuthenticatedCredentials.
98type AuthenticatedCredentialsOpt struct {
99 wantCA *x509.Certificate
100 wantNodeID string
101 insecureOkay bool
102}
103
104func (a *AuthenticatedCredentialsOpt) merge(o *AuthenticatedCredentialsOpt) {
105 if a.wantNodeID == "" && o.wantNodeID != "" {
106 a.wantNodeID = o.wantNodeID
107 }
108 if a.wantCA == nil && o.wantCA != nil {
109 a.wantCA = o.wantCA
110 }
111 if !a.insecureOkay && o.insecureOkay {
112 a.insecureOkay = o.insecureOkay
113 }
114}
115
116// WantRemoteCluster enables the verification of the remote cluster identity when
117// using NewAuthanticatedCredentials. If the connection is not terminated at a
118// cluster with the given CA certificate, an error will be returned.
119//
120// This is the bare minimum option required to implement secure connections to
121// clusters.
122func WantRemoteCluster(ca *x509.Certificate) AuthenticatedCredentialsOpt {
123 return AuthenticatedCredentialsOpt{
124 wantCA: ca,
125 }
126}
127
128// WantRemoteNode enables the verification of the remote node identity when using
129// NewAuthenticatedCredentials. If the connection is not terminated at the node
130// ID 'id', an error will be returned. For this function to work,
131// WantRemoteCluster must also be set.
132func WantRemoteNode(id string) AuthenticatedCredentialsOpt {
133 return AuthenticatedCredentialsOpt{
134 wantNodeID: id,
135 }
136}
137
138// WantInsecure disables the verification of the remote side of the connection
139// via NewAuthenticatedCredentials. This is unsafe.
140func WantInsecure() AuthenticatedCredentialsOpt {
141 return AuthenticatedCredentialsOpt{
142 insecureOkay: true,
143 }
Serge Bazanski3379a5d2021-09-09 12:56:40 +0200144}
Serge Bazanskid7d6e022021-09-01 15:03:06 +0200145
Serge Bazanski399ce552022-03-29 12:52:42 +0200146// NewAuthenticatedCredentials returns gRPC TransportCredentials that can be
147// used to dial a cluster with a given TLS certificate (from node or manager
148// credentials).
149//
Serge Bazanski8535cb52023-03-29 14:15:08 +0200150// The provided AuthenticatedCredentialsOpt specify the verification of the
151// remote side of the connection. When connecting to a cluster (any node), use
152// WantRemoteCluster. If you also want to verify the connection to a particular
153// node, specify WantRemoteNode alongside it. If no verification should be
154// performed use WantInsecure.
155//
156// The given options are parsed on a first-wins basis.
157func NewAuthenticatedCredentials(cert tls.Certificate, opts ...AuthenticatedCredentialsOpt) credentials.TransportCredentials {
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
163 var merged AuthenticatedCredentialsOpt
164 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 {
169 if merged.wantNodeID != "" || merged.wantCA != nil {
170 config.VerifyPeerCertificate = verifyFail(fmt.Errorf("WantInsecure specified alongside WantRemoteNode/WantRemoteCluster"))
171 }
172 } else {
173 switch {
174 case merged.wantNodeID != "" && merged.wantCA == nil:
175 config.VerifyPeerCertificate = verifyFail(fmt.Errorf("WantRemoteNode also requires WantRemoteCluster"))
176 case merged.wantCA == nil:
177 config.VerifyPeerCertificate = verifyFail(fmt.Errorf("no AuthenticaedCreentialsOpts specified"))
178 default:
179 config.VerifyPeerCertificate = verifyClusterCertificateAndNodeID(merged.wantCA, merged.wantNodeID)
180 }
181 }
182
Serge Bazanski399ce552022-03-29 12:52:42 +0200183 return credentials.NewTLS(config)
Serge Bazanskid7d6e022021-09-01 15:03:06 +0200184}
185
Serge Bazanski8535cb52023-03-29 14:15:08 +0200186// RetrieveOwnerCertificate uses AAA.Escrow to retrieve a cluster manager
Serge Bazanskid7d6e022021-09-01 15:03:06 +0200187// certificate for the initial owner of the cluster, authenticated by the
188// public/private key set in the clusters NodeParameters.ClusterBoostrap.
189//
190// The retrieved certificate can be used to dial further cluster RPCs.
191func RetrieveOwnerCertificate(ctx context.Context, aaa apb.AAAClient, private ed25519.PrivateKey) (*tls.Certificate, error) {
192 srv, err := aaa.Escrow(ctx)
193 if err != nil {
Serge Bazanski636032e2022-01-26 14:21:33 +0100194 if st, ok := status.FromError(err); ok {
195 return nil, status.Errorf(st.Code(), "Escrow call failed: %s", st.Message())
196 }
197 return nil, err
Serge Bazanskid7d6e022021-09-01 15:03:06 +0200198 }
199 if err := srv.Send(&apb.EscrowFromClient{
200 Parameters: &apb.EscrowFromClient_Parameters{
201 RequestedIdentityName: "owner",
202 PublicKey: private.Public().(ed25519.PublicKey),
203 },
204 }); err != nil {
205 return nil, fmt.Errorf("when sending client parameters: %w", err)
206 }
207 resp, err := srv.Recv()
208 if err != nil {
209 return nil, fmt.Errorf("when receiving server message: %w", err)
210 }
211 if len(resp.EmittedCertificate) == 0 {
212 return nil, fmt.Errorf("expected certificate, instead got needed proofs: %+v", resp.Needed)
213 }
214
215 return &tls.Certificate{
216 Certificate: [][]byte{resp.EmittedCertificate},
217 PrivateKey: private,
218 }, nil
219}