1package autoconf
2
3import (
4	"context"
5	"fmt"
6	"net"
7
8	"github.com/hashicorp/consul/agent/cache"
9	cachetype "github.com/hashicorp/consul/agent/cache-types"
10	"github.com/hashicorp/consul/agent/connect"
11	"github.com/hashicorp/consul/agent/structs"
12	"github.com/hashicorp/consul/proto/pbautoconf"
13)
14
15const (
16	// ID of the roots watch
17	rootsWatchID = "roots"
18
19	// ID of the leaf watch
20	leafWatchID = "leaf"
21
22	unknownTrustDomain = "unknown"
23)
24
25var (
26	defaultDNSSANs = []string{"localhost"}
27
28	defaultIPSANs = []net.IP{{127, 0, 0, 1}, net.ParseIP("::1")}
29)
30
31func extractPEMs(roots *structs.IndexedCARoots) []string {
32	var pems []string
33	for _, root := range roots.Roots {
34		pems = append(pems, root.RootCert)
35	}
36	return pems
37}
38
39// updateTLSFromResponse will update the TLS certificate and roots in the shared
40// TLS configurator.
41func (ac *AutoConfig) updateTLSFromResponse(resp *pbautoconf.AutoConfigResponse) error {
42	var pems []string
43	for _, root := range resp.GetCARoots().GetRoots() {
44		pems = append(pems, root.RootCert)
45	}
46
47	err := ac.acConfig.TLSConfigurator.UpdateAutoTLS(
48		resp.ExtraCACertificates,
49		pems,
50		resp.Certificate.GetCertPEM(),
51		resp.Certificate.GetPrivateKeyPEM(),
52		resp.Config.GetTLS().GetVerifyServerHostname(),
53	)
54
55	if err != nil {
56		return fmt.Errorf("Failed to update the TLS configurator with new certificates: %w", err)
57	}
58
59	return nil
60}
61
62func (ac *AutoConfig) setInitialTLSCertificates(certs *structs.SignedResponse) error {
63	if certs == nil {
64		return nil
65	}
66
67	if err := ac.populateCertificateCache(certs); err != nil {
68		return fmt.Errorf("error populating cache with certificates: %w", err)
69	}
70
71	connectCAPems := extractPEMs(&certs.ConnectCARoots)
72
73	err := ac.acConfig.TLSConfigurator.UpdateAutoTLS(
74		certs.ManualCARoots,
75		connectCAPems,
76		certs.IssuedCert.CertPEM,
77		certs.IssuedCert.PrivateKeyPEM,
78		certs.VerifyServerHostname,
79	)
80
81	if err != nil {
82		return fmt.Errorf("error updating TLS configurator with certificates: %w", err)
83	}
84
85	return nil
86}
87
88func (ac *AutoConfig) populateCertificateCache(certs *structs.SignedResponse) error {
89	cert, err := connect.ParseCert(certs.IssuedCert.CertPEM)
90	if err != nil {
91		return fmt.Errorf("Failed to parse certificate: %w", err)
92	}
93
94	// prepolutate roots cache
95	rootRes := cache.FetchResult{Value: &certs.ConnectCARoots, Index: certs.ConnectCARoots.QueryMeta.Index}
96	rootsReq := ac.caRootsRequest()
97	// getting the roots doesn't require a token so in order to potentially share the cache with another
98	if err := ac.acConfig.Cache.Prepopulate(cachetype.ConnectCARootName, rootRes, ac.config.Datacenter, "", rootsReq.CacheInfo().Key); err != nil {
99		return err
100	}
101
102	leafReq := ac.leafCertRequest()
103
104	// prepolutate leaf cache
105	certRes := cache.FetchResult{
106		Value: &certs.IssuedCert,
107		Index: certs.IssuedCert.RaftIndex.ModifyIndex,
108		State: cachetype.ConnectCALeafSuccess(connect.EncodeSigningKeyID(cert.AuthorityKeyId)),
109	}
110	if err := ac.acConfig.Cache.Prepopulate(cachetype.ConnectCALeafName, certRes, leafReq.Datacenter, leafReq.Token, leafReq.Key()); err != nil {
111		return err
112	}
113
114	return nil
115}
116
117func (ac *AutoConfig) setupCertificateCacheWatches(ctx context.Context) (context.CancelFunc, error) {
118	notificationCtx, cancel := context.WithCancel(ctx)
119
120	rootsReq := ac.caRootsRequest()
121	err := ac.acConfig.Cache.Notify(notificationCtx, cachetype.ConnectCARootName, &rootsReq, rootsWatchID, ac.cacheUpdates)
122	if err != nil {
123		cancel()
124		return nil, err
125	}
126
127	leafReq := ac.leafCertRequest()
128	err = ac.acConfig.Cache.Notify(notificationCtx, cachetype.ConnectCALeafName, &leafReq, leafWatchID, ac.cacheUpdates)
129	if err != nil {
130		cancel()
131		return nil, err
132	}
133
134	return cancel, nil
135}
136
137func (ac *AutoConfig) updateCARoots(roots *structs.IndexedCARoots) error {
138	switch {
139	case ac.config.AutoConfig.Enabled:
140		ac.Lock()
141		defer ac.Unlock()
142		var err error
143		ac.autoConfigResponse.CARoots, err = translateCARootsToProtobuf(roots)
144		if err != nil {
145			return err
146		}
147
148		if err := ac.updateTLSFromResponse(ac.autoConfigResponse); err != nil {
149			return err
150		}
151		return ac.persistAutoConfig(ac.autoConfigResponse)
152	case ac.config.AutoEncryptTLS:
153		pems := extractPEMs(roots)
154
155		if err := ac.acConfig.TLSConfigurator.UpdateAutoTLSCA(pems); err != nil {
156			return fmt.Errorf("failed to update Connect CA certificates: %w", err)
157		}
158		return nil
159	default:
160		return nil
161	}
162}
163
164func (ac *AutoConfig) updateLeafCert(cert *structs.IssuedCert) error {
165	switch {
166	case ac.config.AutoConfig.Enabled:
167		ac.Lock()
168		defer ac.Unlock()
169		var err error
170		ac.autoConfigResponse.Certificate, err = translateIssuedCertToProtobuf(cert)
171		if err != nil {
172			return err
173		}
174
175		if err := ac.updateTLSFromResponse(ac.autoConfigResponse); err != nil {
176			return err
177		}
178		return ac.persistAutoConfig(ac.autoConfigResponse)
179	case ac.config.AutoEncryptTLS:
180		if err := ac.acConfig.TLSConfigurator.UpdateAutoTLSCert(cert.CertPEM, cert.PrivateKeyPEM); err != nil {
181			return fmt.Errorf("failed to update the agent leaf cert: %w", err)
182		}
183		return nil
184	default:
185		return nil
186	}
187}
188
189func (ac *AutoConfig) caRootsRequest() structs.DCSpecificRequest {
190	return structs.DCSpecificRequest{Datacenter: ac.config.Datacenter}
191}
192
193func (ac *AutoConfig) leafCertRequest() cachetype.ConnectCALeafRequest {
194	return cachetype.ConnectCALeafRequest{
195		Datacenter: ac.config.Datacenter,
196		Agent:      ac.config.NodeName,
197		DNSSAN:     ac.getDNSSANs(),
198		IPSAN:      ac.getIPSANs(),
199		Token:      ac.acConfig.Tokens.AgentToken(),
200	}
201}
202
203// generateCSR will generate a CSR for an Agent certificate. This should
204// be sent along with the AutoConfig.InitialConfiguration RPC or the
205// AutoEncrypt.Sign RPC. The generated CSR does NOT have a real trust domain
206// as when generating this we do not yet have the CA roots. The server will
207// update the trust domain for us though.
208func (ac *AutoConfig) generateCSR() (csr string, key string, err error) {
209	// We don't provide the correct host here, because we don't know any
210	// better at this point. Apart from the domain, we would need the
211	// ClusterID, which we don't have. This is why we go with
212	// unknownTrustDomain the first time. Subsequent CSRs will have the
213	// correct TrustDomain.
214	id := &connect.SpiffeIDAgent{
215		// will be replaced
216		Host:       unknownTrustDomain,
217		Datacenter: ac.config.Datacenter,
218		Agent:      ac.config.NodeName,
219	}
220
221	caConfig, err := ac.config.ConnectCAConfiguration()
222	if err != nil {
223		return "", "", fmt.Errorf("Cannot generate CSR: %w", err)
224	}
225
226	conf, err := caConfig.GetCommonConfig()
227	if err != nil {
228		return "", "", fmt.Errorf("Failed to load common CA configuration: %w", err)
229	}
230
231	if conf.PrivateKeyType == "" {
232		conf.PrivateKeyType = connect.DefaultPrivateKeyType
233	}
234	if conf.PrivateKeyBits == 0 {
235		conf.PrivateKeyBits = connect.DefaultPrivateKeyBits
236	}
237
238	// Create a new private key
239	pk, pkPEM, err := connect.GeneratePrivateKeyWithConfig(conf.PrivateKeyType, conf.PrivateKeyBits)
240	if err != nil {
241		return "", "", fmt.Errorf("Failed to generate private key: %w", err)
242	}
243
244	dnsNames := ac.getDNSSANs()
245	ipAddresses := ac.getIPSANs()
246
247	// Create a CSR.
248	//
249	// The Common Name includes the dummy trust domain for now but Server will
250	// override this when it is signed anyway so it's OK.
251	cn := connect.AgentCN(ac.config.NodeName, unknownTrustDomain)
252	csr, err = connect.CreateCSR(id, cn, pk, dnsNames, ipAddresses)
253	if err != nil {
254		return "", "", err
255	}
256
257	return csr, pkPEM, nil
258}
259
260func (ac *AutoConfig) getDNSSANs() []string {
261	sans := defaultDNSSANs
262	switch {
263	case ac.config.AutoConfig.Enabled:
264		sans = append(sans, ac.config.AutoConfig.DNSSANs...)
265	case ac.config.AutoEncryptTLS:
266		sans = append(sans, ac.config.AutoEncryptDNSSAN...)
267	}
268	return sans
269}
270
271func (ac *AutoConfig) getIPSANs() []net.IP {
272	sans := defaultIPSANs
273	switch {
274	case ac.config.AutoConfig.Enabled:
275		sans = append(sans, ac.config.AutoConfig.IPSANs...)
276	case ac.config.AutoEncryptTLS:
277		sans = append(sans, ac.config.AutoEncryptIPSAN...)
278	}
279	return sans
280}
281