1package autoconf
2
3import (
4	"context"
5	"fmt"
6	"net"
7	"strings"
8
9	"github.com/hashicorp/consul/agent/structs"
10)
11
12func (ac *AutoConfig) autoEncryptInitialCerts(ctx context.Context) (*structs.SignedResponse, error) {
13	// generate a CSR
14	csr, key, err := ac.generateCSR()
15	if err != nil {
16		return nil, err
17	}
18
19	ac.acConfig.Waiter.Reset()
20	for {
21		resp, err := ac.autoEncryptInitialCertsOnce(ctx, csr, key)
22		switch {
23		case err == nil && resp != nil:
24			return resp, nil
25		case err != nil:
26			ac.logger.Error(err.Error())
27		default:
28			ac.logger.Error("No error returned when fetching certificates from the servers but no response was either")
29		}
30
31		if err := ac.acConfig.Waiter.Wait(ctx); err != nil {
32			ac.logger.Info("interrupted during retrieval of auto-encrypt certificates", "err", err)
33			return nil, err
34		}
35	}
36}
37
38func (ac *AutoConfig) autoEncryptInitialCertsOnce(ctx context.Context, csr, key string) (*structs.SignedResponse, error) {
39	request := structs.CASignRequest{
40		WriteRequest: structs.WriteRequest{Token: ac.acConfig.Tokens.AgentToken()},
41		Datacenter:   ac.config.Datacenter,
42		CSR:          csr,
43	}
44	var resp structs.SignedResponse
45
46	servers, err := ac.joinHosts()
47	if err != nil {
48		return nil, err
49	}
50
51	for _, s := range servers {
52		// try each IP to see if we can successfully make the request
53		for _, addr := range ac.resolveHost(s) {
54			if ctx.Err() != nil {
55				return nil, ctx.Err()
56			}
57
58			ac.logger.Debug("making AutoEncrypt.Sign RPC", "addr", addr.String())
59			err = ac.acConfig.DirectRPC.RPC(ac.config.Datacenter, ac.config.NodeName, &addr, "AutoEncrypt.Sign", &request, &resp)
60			if err != nil {
61				ac.logger.Error("AutoEncrypt.Sign RPC failed", "addr", addr.String(), "error", err)
62				continue
63			}
64
65			resp.IssuedCert.PrivateKeyPEM = key
66			return &resp, nil
67		}
68	}
69	return nil, fmt.Errorf("No servers successfully responded to the auto-encrypt request")
70}
71
72func (ac *AutoConfig) joinHosts() ([]string, error) {
73	// use servers known to gossip if there are any
74	if ac.acConfig.ServerProvider != nil {
75		if srv := ac.acConfig.ServerProvider.FindLANServer(); srv != nil {
76			return []string{srv.Addr.String()}, nil
77		}
78	}
79
80	hosts, err := ac.discoverServers(ac.config.RetryJoinLAN)
81	if err != nil {
82		return nil, err
83	}
84
85	var addrs []string
86
87	// The addresses we use for auto-encrypt are the retry join and start join
88	// addresses. These are for joining serf and therefore we cannot rely on the
89	// ports for these. This loop strips any port that may have been specified and
90	// will let subsequent resolveAddr calls add on the default RPC port.
91	for _, addr := range append(ac.config.StartJoinAddrsLAN, hosts...) {
92		host, _, err := net.SplitHostPort(addr)
93		if err != nil {
94			if strings.Contains(err.Error(), "missing port in address") {
95				host = addr
96			} else {
97				ac.logger.Warn("error splitting host address into IP and port", "address", addr, "error", err)
98				continue
99			}
100		}
101		addrs = append(addrs, host)
102	}
103
104	if len(addrs) == 0 {
105		return nil, fmt.Errorf("no auto-encrypt server addresses available for use")
106	}
107
108	return addrs, nil
109}
110