1package api
2
3import (
4	"fmt"
5	"time"
6
7	"github.com/mitchellh/mapstructure"
8)
9
10// CAConfig is the structure for the Connect CA configuration.
11type CAConfig struct {
12	// Provider is the CA provider implementation to use.
13	Provider string
14
15	// Configuration is arbitrary configuration for the provider. This
16	// should only contain primitive values and containers (such as lists
17	// and maps).
18	Config map[string]interface{}
19
20	// State is read-only data that the provider might have persisted for use
21	// after restart or leadership transition. For example this might include
22	// UUIDs of resources it has created. Setting this when writing a
23	// configuration is an error.
24	State map[string]string
25
26	// ForceWithoutCrossSigning indicates that the CA reconfiguration should go
27	// ahead even if the current CA is unable to cross sign certificates. This
28	// risks temporary connection failures during the rollout as new leafs will be
29	// rejected by proxies that have not yet observed the new root cert but is the
30	// only option if a CA that doesn't support cross signing needs to be
31	// reconfigured or mirated away from.
32	ForceWithoutCrossSigning bool
33
34	CreateIndex uint64
35	ModifyIndex uint64
36}
37
38// CommonCAProviderConfig is the common options available to all CA providers.
39type CommonCAProviderConfig struct {
40	LeafCertTTL      time.Duration
41	SkipValidate     bool
42	CSRMaxPerSecond  float32
43	CSRMaxConcurrent int
44}
45
46// ConsulCAProviderConfig is the config for the built-in Consul CA provider.
47type ConsulCAProviderConfig struct {
48	CommonCAProviderConfig `mapstructure:",squash"`
49
50	PrivateKey          string
51	RootCert            string
52	IntermediateCertTTL time.Duration
53}
54
55// ParseConsulCAConfig takes a raw config map and returns a parsed
56// ConsulCAProviderConfig.
57func ParseConsulCAConfig(raw map[string]interface{}) (*ConsulCAProviderConfig, error) {
58	var config ConsulCAProviderConfig
59	decodeConf := &mapstructure.DecoderConfig{
60		DecodeHook:       mapstructure.StringToTimeDurationHookFunc(),
61		Result:           &config,
62		WeaklyTypedInput: true,
63	}
64
65	decoder, err := mapstructure.NewDecoder(decodeConf)
66	if err != nil {
67		return nil, err
68	}
69
70	if err := decoder.Decode(raw); err != nil {
71		return nil, fmt.Errorf("error decoding config: %s", err)
72	}
73
74	return &config, nil
75}
76
77// CARootList is the structure for the results of listing roots.
78type CARootList struct {
79	ActiveRootID string
80	TrustDomain  string
81	Roots        []*CARoot
82}
83
84// CARoot represents a root CA certificate that is trusted.
85type CARoot struct {
86	// ID is a globally unique ID (UUID) representing this CA root.
87	ID string
88
89	// Name is a human-friendly name for this CA root. This value is
90	// opaque to Consul and is not used for anything internally.
91	Name string
92
93	// RootCertPEM is the PEM-encoded public certificate.
94	RootCertPEM string `json:"RootCert"`
95
96	// Active is true if this is the current active CA. This must only
97	// be true for exactly one CA. For any method that modifies roots in the
98	// state store, tests should be written to verify that multiple roots
99	// cannot be active.
100	Active bool
101
102	CreateIndex uint64
103	ModifyIndex uint64
104}
105
106// LeafCert is a certificate that has been issued by a Connect CA.
107type LeafCert struct {
108	// SerialNumber is the unique serial number for this certificate.
109	// This is encoded in standard hex separated by :.
110	SerialNumber string
111
112	// CertPEM and PrivateKeyPEM are the PEM-encoded certificate and private
113	// key for that cert, respectively. This should not be stored in the
114	// state store, but is present in the sign API response.
115	CertPEM       string `json:",omitempty"`
116	PrivateKeyPEM string `json:",omitempty"`
117
118	// Service is the name of the service for which the cert was issued.
119	// ServiceURI is the cert URI value.
120	Service    string
121	ServiceURI string
122
123	// ValidAfter and ValidBefore are the validity periods for the
124	// certificate.
125	ValidAfter  time.Time
126	ValidBefore time.Time
127
128	CreateIndex uint64
129	ModifyIndex uint64
130}
131
132// CARoots queries the list of available roots.
133func (h *Connect) CARoots(q *QueryOptions) (*CARootList, *QueryMeta, error) {
134	r := h.c.newRequest("GET", "/v1/connect/ca/roots")
135	r.setQueryOptions(q)
136	rtt, resp, err := requireOK(h.c.doRequest(r))
137	if err != nil {
138		return nil, nil, err
139	}
140	defer closeResponseBody(resp)
141
142	qm := &QueryMeta{}
143	parseQueryMeta(resp, qm)
144	qm.RequestTime = rtt
145
146	var out CARootList
147	if err := decodeBody(resp, &out); err != nil {
148		return nil, nil, err
149	}
150	return &out, qm, nil
151}
152
153// CAGetConfig returns the current CA configuration.
154func (h *Connect) CAGetConfig(q *QueryOptions) (*CAConfig, *QueryMeta, error) {
155	r := h.c.newRequest("GET", "/v1/connect/ca/configuration")
156	r.setQueryOptions(q)
157	rtt, resp, err := requireOK(h.c.doRequest(r))
158	if err != nil {
159		return nil, nil, err
160	}
161	defer closeResponseBody(resp)
162
163	qm := &QueryMeta{}
164	parseQueryMeta(resp, qm)
165	qm.RequestTime = rtt
166
167	var out CAConfig
168	if err := decodeBody(resp, &out); err != nil {
169		return nil, nil, err
170	}
171	return &out, qm, nil
172}
173
174// CASetConfig sets the current CA configuration.
175func (h *Connect) CASetConfig(conf *CAConfig, q *WriteOptions) (*WriteMeta, error) {
176	r := h.c.newRequest("PUT", "/v1/connect/ca/configuration")
177	r.setWriteOptions(q)
178	r.obj = conf
179	rtt, resp, err := requireOK(h.c.doRequest(r))
180	if err != nil {
181		return nil, err
182	}
183	defer closeResponseBody(resp)
184
185	wm := &WriteMeta{}
186	wm.RequestTime = rtt
187	return wm, nil
188}
189