1package consul
2
3import (
4	"errors"
5	"fmt"
6	"time"
7
8	"github.com/hashicorp/go-hclog"
9	"github.com/hashicorp/go-memdb"
10
11	"github.com/hashicorp/consul/acl"
12	"github.com/hashicorp/consul/agent/connect"
13	"github.com/hashicorp/consul/agent/consul/state"
14	"github.com/hashicorp/consul/agent/structs"
15)
16
17var (
18	// Err strings. net/rpc doesn't have a way to transport typed/rich errors so
19	// we currently rely on sniffing the error string in a few cases where we need
20	// to change client behavior. These are the canonical error strings to use.
21	// Note though that client code can't use `err == consul.Err*` directly since
22	// the error returned by RPC will be a plain error.errorString created by
23	// net/rpc client so will not be the same _instance_ that this package
24	// variable points to. Clients need to compare using `err.Error() ==
25	// consul.ErrRateLimited.Error()` which is very sad. Short of replacing our
26	// RPC mechanism it's hard to know how to make that much better though.
27	ErrConnectNotEnabled    = errors.New("Connect must be enabled in order to use this endpoint")
28	ErrRateLimited          = errors.New("Rate limit reached, try again later")
29	ErrNotPrimaryDatacenter = errors.New("not the primary datacenter")
30	ErrStateReadOnly        = errors.New("CA Provider State is read-only")
31)
32
33const (
34	// csrLimitWait is the maximum time we'll wait for a slot when CSR concurrency
35	// limiting or rate limiting is occurring. It's intentionally short so small
36	// batches of requests can be accommodated when server has capacity (assuming
37	// signing one cert takes much less than this) but failing requests fast when
38	// a thundering herd comes along.
39	csrLimitWait = 500 * time.Millisecond
40)
41
42// ConnectCA manages the Connect CA.
43type ConnectCA struct {
44	// srv is a pointer back to the server.
45	srv *Server
46
47	logger hclog.Logger
48}
49
50// ConfigurationGet returns the configuration for the CA.
51func (s *ConnectCA) ConfigurationGet(
52	args *structs.DCSpecificRequest,
53	reply *structs.CAConfiguration) error {
54	// Exit early if Connect hasn't been enabled.
55	if !s.srv.config.ConnectEnabled {
56		return ErrConnectNotEnabled
57	}
58
59	if done, err := s.srv.ForwardRPC("ConnectCA.ConfigurationGet", args, reply); done {
60		return err
61	}
62
63	// This action requires operator read access.
64	rule, err := s.srv.ResolveToken(args.Token)
65	if err != nil {
66		return err
67	}
68	if rule != nil && rule.OperatorWrite(nil) != acl.Allow {
69		return acl.ErrPermissionDenied
70	}
71
72	state := s.srv.fsm.State()
73	_, config, err := state.CAConfig(nil)
74	if err != nil {
75		return err
76	}
77	*reply = *config
78
79	return nil
80}
81
82// ConfigurationSet updates the configuration for the CA.
83func (s *ConnectCA) ConfigurationSet(
84	args *structs.CARequest,
85	reply *interface{}) error {
86	// Exit early if Connect hasn't been enabled.
87	if !s.srv.config.ConnectEnabled {
88		return ErrConnectNotEnabled
89	}
90
91	if done, err := s.srv.ForwardRPC("ConnectCA.ConfigurationSet", args, reply); done {
92		return err
93	}
94
95	// This action requires operator write access.
96	rule, err := s.srv.ResolveToken(args.Token)
97	if err != nil {
98		return err
99	}
100	if rule != nil && rule.OperatorWrite(nil) != acl.Allow {
101		return acl.ErrPermissionDenied
102	}
103
104	return s.srv.caManager.UpdateConfiguration(args)
105}
106
107// Roots returns the currently trusted root certificates.
108func (s *ConnectCA) Roots(
109	args *structs.DCSpecificRequest,
110	reply *structs.IndexedCARoots) error {
111	// Forward if necessary
112	if done, err := s.srv.ForwardRPC("ConnectCA.Roots", args, reply); done {
113		return err
114	}
115
116	// Exit early if Connect hasn't been enabled.
117	if !s.srv.config.ConnectEnabled {
118		return ErrConnectNotEnabled
119	}
120
121	return s.srv.blockingQuery(
122		&args.QueryOptions, &reply.QueryMeta,
123		func(ws memdb.WatchSet, state *state.Store) error {
124			roots, err := s.srv.getCARoots(ws, state)
125			if err != nil {
126				return err
127			}
128
129			*reply = *roots
130			return nil
131		},
132	)
133}
134
135// Sign signs a certificate for a service.
136func (s *ConnectCA) Sign(
137	args *structs.CASignRequest,
138	reply *structs.IssuedCert) error {
139	// Exit early if Connect hasn't been enabled.
140	if !s.srv.config.ConnectEnabled {
141		return ErrConnectNotEnabled
142	}
143
144	if done, err := s.srv.ForwardRPC("ConnectCA.Sign", args, reply); done {
145		return err
146	}
147
148	// Parse the CSR
149	csr, err := connect.ParseCSR(args.CSR)
150	if err != nil {
151		return err
152	}
153
154	// Parse the SPIFFE ID
155	spiffeID, err := connect.ParseCertURI(csr.URIs[0])
156	if err != nil {
157		return err
158	}
159
160	// Verify that the ACL token provided has permission to act as this service
161	rule, err := s.srv.ResolveToken(args.Token)
162	if err != nil {
163		return err
164	}
165
166	var authzContext acl.AuthorizerContext
167	var entMeta structs.EnterpriseMeta
168
169	serviceID, isService := spiffeID.(*connect.SpiffeIDService)
170	agentID, isAgent := spiffeID.(*connect.SpiffeIDAgent)
171	if !isService && !isAgent {
172		return fmt.Errorf("SPIFFE ID in CSR must be a service or agent ID")
173	}
174
175	if isService {
176		entMeta.Merge(serviceID.GetEnterpriseMeta())
177		entMeta.FillAuthzContext(&authzContext)
178		if rule != nil && rule.ServiceWrite(serviceID.Service, &authzContext) != acl.Allow {
179			return acl.ErrPermissionDenied
180		}
181
182		// Verify that the DC in the service URI matches us. We might relax this
183		// requirement later but being restrictive for now is safer.
184		if serviceID.Datacenter != s.srv.config.Datacenter {
185			return fmt.Errorf("SPIFFE ID in CSR from a different datacenter: %s, "+
186				"we are %s", serviceID.Datacenter, s.srv.config.Datacenter)
187		}
188	} else if isAgent {
189		structs.DefaultEnterpriseMeta().FillAuthzContext(&authzContext)
190		if rule != nil && rule.NodeWrite(agentID.Agent, &authzContext) != acl.Allow {
191			return acl.ErrPermissionDenied
192		}
193	}
194
195	cert, err := s.srv.SignCertificate(csr, spiffeID)
196	if err != nil {
197		return err
198	}
199	*reply = *cert
200	return nil
201}
202
203// SignIntermediate signs an intermediate certificate for a remote datacenter.
204func (s *ConnectCA) SignIntermediate(
205	args *structs.CASignRequest,
206	reply *string) error {
207	// Exit early if Connect hasn't been enabled.
208	if !s.srv.config.ConnectEnabled {
209		return ErrConnectNotEnabled
210	}
211
212	if done, err := s.srv.ForwardRPC("ConnectCA.SignIntermediate", args, reply); done {
213		return err
214	}
215
216	// Verify we are allowed to serve this request
217	if s.srv.config.PrimaryDatacenter != s.srv.config.Datacenter {
218		return ErrNotPrimaryDatacenter
219	}
220
221	// This action requires operator write access.
222	rule, err := s.srv.ResolveToken(args.Token)
223	if err != nil {
224		return err
225	}
226	if rule != nil && rule.OperatorWrite(nil) != acl.Allow {
227		return acl.ErrPermissionDenied
228	}
229
230	provider, _ := s.srv.caManager.getCAProvider()
231	if provider == nil {
232		return fmt.Errorf("internal error: CA provider is nil")
233	}
234
235	csr, err := connect.ParseCSR(args.CSR)
236	if err != nil {
237		return err
238	}
239
240	cert, err := provider.SignIntermediate(csr)
241	if err != nil {
242		return err
243	}
244
245	*reply = cert
246
247	return nil
248}
249