blob: 5913f1410b8cd817e777f7af7ef7b05479b7d548 [file] [log] [blame]
Serge Bazanski1f9a03b2021-08-17 13:40:53 +02001package launch
2
3import (
4 "context"
5 "crypto/ed25519"
6 "crypto/rand"
7 "crypto/tls"
8 "crypto/x509"
9 "encoding/pem"
10 "fmt"
11 "math/big"
12 "time"
13
14 "google.golang.org/grpc"
15 "google.golang.org/grpc/credentials"
16
17 apb "source.monogon.dev/metropolis/proto/api"
18)
19
20// InitialClient implements a gRPC wrapper for dialing a Metropolis cluster
21// while not (yet) authenticated, i.e. using only a self-signed public
22// certificate to prove ownership of an ed25519 public key.
23//
24// This is used to dial a cluster's AAA.Escrow service after cluster bootstrap
25// using the owner key configured in NodeParams.
26type InitialClient struct {
27 // conn is the underlying dialed gRPC connection to the cluster.
28 conn *grpc.ClientConn
29 // aaa is a stub to the AAA service running on conn.
30 aaa apb.AAAClient
31 // options are the options this client has been opened with.
32 options *InitialClientOptions
33}
34
35type InitialClientOptions struct {
36 // Remote is an address:port to connect to. This should be a cluster node's
37 // curator port.
38 Remote string
39 // Private is the cluster owner private key, which should correspond to the
40 // owner public key defined in NodeParametrs.ClusterBootstrap when a cluster
41 // is bootstrapped.
42 Private ed25519.PrivateKey
43}
44
45// NewInitialClient dials a cluster's curator service using just a self-signed
46// certificate and can be used to then escrow real cluster credentials for the
47// owner.
48//
49// MVP SECURITY: this does not verify the identity of the cluster/node. However,
50// because any intercepting party cannot forward the presented client
51// certificate to any real cluster, no danger of intercepting administrative
52// access to the expected cluster is possible. Instead, the interceptor can just
53// pretend to be the cluster which was expected.
54func NewInitialClient(o *InitialClientOptions) (*InitialClient, error) {
55 template := x509.Certificate{
56 SerialNumber: big.NewInt(1),
57 NotBefore: time.Now(),
58 NotAfter: time.Now().Add(time.Hour),
59
60 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
61 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
62 BasicConstraintsValid: true,
63 }
64 certificateBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, o.Private.Public(), o.Private)
65 if err != nil {
66 return nil, fmt.Errorf("when generating self-signed certificate: %w", err)
67 }
68 keyBytes, err := x509.MarshalPKCS8PrivateKey(o.Private)
69 if err != nil {
70 return nil, fmt.Errorf("when marshaling private key: %w", err)
71 }
72 key := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes})
73 certificate := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificateBytes})
74 clientCert, err := tls.X509KeyPair(certificate, key)
75 if err != nil {
76 return nil, fmt.Errorf("when building self-signed TLS client certificate: %w", err)
77 }
78
79 creds := credentials.NewTLS(&tls.Config{
80 Certificates: []tls.Certificate{
81 clientCert,
82 },
83 InsecureSkipVerify: true,
84 VerifyPeerCertificate: o.verify,
85 })
86
87 conn, err := grpc.Dial(o.Remote, grpc.WithTransportCredentials(creds))
88 if err != nil {
89 return nil, fmt.Errorf("when dialing: %w", err)
90 }
91
92 return &InitialClient{
93 conn: conn,
94 aaa: apb.NewAAAClient(conn),
95 options: o,
96 }, nil
97}
98
99// Close must be called when the InitialClient is not used anymore. This closes
100// the underlying gRPC connection(s).
101func (i *InitialClient) Close() error {
102 return i.conn.Close()
103}
104
105func (o *InitialClientOptions) verify(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
106 // SECURITY: Always permit all server certificates. See NewInitialClient godoc
107 // for more information.
108 return nil
109}
110
111// RetrieveOwnerCertificates uses AAA.Escrow to retrieve a cluster manager
112// certificate for the initial owner of the cluster, authenticated by the
113// public/private key set in the clusters NodeParameters.ClusterBoostrap.
114//
115// The retrieved certificate can be used to dial further cluster RPCs.
116func (i *InitialClient) RetrieveOwnerCertificate(ctx context.Context) (*tls.Certificate, error) {
117 srv, err := i.aaa.Escrow(ctx)
118 if err != nil {
119 return nil, fmt.Errorf("when opening Escrow RPC: %w", err)
120 }
121 if err := srv.Send(&apb.EscrowFromClient{
122 Parameters: &apb.EscrowFromClient_Parameters{
123 RequestedIdentityName: "owner",
124 PublicKey: i.options.Private.Public().(ed25519.PublicKey),
125 },
126 }); err != nil {
127 return nil, fmt.Errorf("when sending client parameters: %w", err)
128 }
129 resp, err := srv.Recv()
130 if err != nil {
131 return nil, fmt.Errorf("when receiving server message: %w", err)
132 }
133 if len(resp.EmittedCertificate) == 0 {
134 return nil, fmt.Errorf("expected certificate, instead got needed proofs: %+v", resp.Needed)
135 }
136
137 certificateBytes := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: resp.EmittedCertificate})
138 key, err := x509.MarshalPKCS8PrivateKey(i.options.Private)
139 if err != nil {
140 return nil, fmt.Errorf("while marshalling private key: %w", err)
141 }
142 keyBytes := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: key})
143 ownerCert, err := tls.X509KeyPair(certificateBytes, keyBytes)
144 if err != nil {
145 return nil, fmt.Errorf("could not build certificate from data received from cluster: %w", err)
146 }
147
148 return &ownerCert, nil
149}