blob: d25cb844fb8b629ac0e307ed52a0ac0a60166d64 [file] [log] [blame]
William Kurkianea869482019-04-09 15:16:11 -04001package api
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "time"
8)
9
10// Intention defines an intention for the Connect Service Graph. This defines
11// the allowed or denied behavior of a connection between two services using
12// Connect.
13type Intention struct {
14 // ID is the UUID-based ID for the intention, always generated by Consul.
15 ID string
16
17 // Description is a human-friendly description of this intention.
18 // It is opaque to Consul and is only stored and transferred in API
19 // requests.
20 Description string
21
22 // SourceNS, SourceName are the namespace and name, respectively, of
23 // the source service. Either of these may be the wildcard "*", but only
24 // the full value can be a wildcard. Partial wildcards are not allowed.
25 // The source may also be a non-Consul service, as specified by SourceType.
26 //
27 // DestinationNS, DestinationName is the same, but for the destination
28 // service. The same rules apply. The destination is always a Consul
29 // service.
30 SourceNS, SourceName string
31 DestinationNS, DestinationName string
32
33 // SourceType is the type of the value for the source.
34 SourceType IntentionSourceType
35
36 // Action is whether this is a whitelist or blacklist intention.
37 Action IntentionAction
38
39 // DefaultAddr, DefaultPort of the local listening proxy (if any) to
40 // make this connection.
41 DefaultAddr string
42 DefaultPort int
43
44 // Meta is arbitrary metadata associated with the intention. This is
45 // opaque to Consul but is served in API responses.
46 Meta map[string]string
47
48 // Precedence is the order that the intention will be applied, with
49 // larger numbers being applied first. This is a read-only field, on
50 // any intention update it is updated.
51 Precedence int
52
53 // CreatedAt and UpdatedAt keep track of when this record was created
54 // or modified.
55 CreatedAt, UpdatedAt time.Time
56
David Bainbridge788e5202019-10-21 18:49:40 +000057 // Hash of the contents of the intention
58 //
59 // This is needed mainly for replication purposes. When replicating from
60 // one DC to another keeping the content Hash will allow us to detect
61 // content changes more efficiently than checking every single field
62 Hash []byte
63
William Kurkianea869482019-04-09 15:16:11 -040064 CreateIndex uint64
65 ModifyIndex uint64
66}
67
68// String returns human-friendly output describing ths intention.
69func (i *Intention) String() string {
70 return fmt.Sprintf("%s => %s (%s)",
71 i.SourceString(),
72 i.DestinationString(),
73 i.Action)
74}
75
76// SourceString returns the namespace/name format for the source, or
77// just "name" if the namespace is the default namespace.
78func (i *Intention) SourceString() string {
79 return i.partString(i.SourceNS, i.SourceName)
80}
81
82// DestinationString returns the namespace/name format for the source, or
83// just "name" if the namespace is the default namespace.
84func (i *Intention) DestinationString() string {
85 return i.partString(i.DestinationNS, i.DestinationName)
86}
87
88func (i *Intention) partString(ns, n string) string {
89 // For now we omit the default namespace from the output. In the future
90 // we might want to look at this and show this in a multi-namespace world.
91 if ns != "" && ns != IntentionDefaultNamespace {
92 n = ns + "/" + n
93 }
94
95 return n
96}
97
98// IntentionDefaultNamespace is the default namespace value.
99const IntentionDefaultNamespace = "default"
100
101// IntentionAction is the action that the intention represents. This
102// can be "allow" or "deny" to whitelist or blacklist intentions.
103type IntentionAction string
104
105const (
106 IntentionActionAllow IntentionAction = "allow"
107 IntentionActionDeny IntentionAction = "deny"
108)
109
110// IntentionSourceType is the type of the source within an intention.
111type IntentionSourceType string
112
113const (
114 // IntentionSourceConsul is a service within the Consul catalog.
115 IntentionSourceConsul IntentionSourceType = "consul"
116)
117
118// IntentionMatch are the arguments for the intention match API.
119type IntentionMatch struct {
120 By IntentionMatchType
121 Names []string
122}
123
124// IntentionMatchType is the target for a match request. For example,
125// matching by source will look for all intentions that match the given
126// source value.
127type IntentionMatchType string
128
129const (
130 IntentionMatchSource IntentionMatchType = "source"
131 IntentionMatchDestination IntentionMatchType = "destination"
132)
133
134// IntentionCheck are the arguments for the intention check API. For
135// more documentation see the IntentionCheck function.
136type IntentionCheck struct {
137 // Source and Destination are the source and destination values to
138 // check. The destination is always a Consul service, but the source
139 // may be other values as defined by the SourceType.
140 Source, Destination string
141
142 // SourceType is the type of the value for the source.
143 SourceType IntentionSourceType
144}
145
146// Intentions returns the list of intentions.
147func (h *Connect) Intentions(q *QueryOptions) ([]*Intention, *QueryMeta, error) {
148 r := h.c.newRequest("GET", "/v1/connect/intentions")
149 r.setQueryOptions(q)
150 rtt, resp, err := requireOK(h.c.doRequest(r))
151 if err != nil {
152 return nil, nil, err
153 }
154 defer resp.Body.Close()
155
156 qm := &QueryMeta{}
157 parseQueryMeta(resp, qm)
158 qm.RequestTime = rtt
159
160 var out []*Intention
161 if err := decodeBody(resp, &out); err != nil {
162 return nil, nil, err
163 }
164 return out, qm, nil
165}
166
167// IntentionGet retrieves a single intention.
168func (h *Connect) IntentionGet(id string, q *QueryOptions) (*Intention, *QueryMeta, error) {
169 r := h.c.newRequest("GET", "/v1/connect/intentions/"+id)
170 r.setQueryOptions(q)
171 rtt, resp, err := h.c.doRequest(r)
172 if err != nil {
173 return nil, nil, err
174 }
175 defer resp.Body.Close()
176
177 qm := &QueryMeta{}
178 parseQueryMeta(resp, qm)
179 qm.RequestTime = rtt
180
181 if resp.StatusCode == 404 {
182 return nil, qm, nil
183 } else if resp.StatusCode != 200 {
184 var buf bytes.Buffer
185 io.Copy(&buf, resp.Body)
186 return nil, nil, fmt.Errorf(
187 "Unexpected response %d: %s", resp.StatusCode, buf.String())
188 }
189
190 var out Intention
191 if err := decodeBody(resp, &out); err != nil {
192 return nil, nil, err
193 }
194 return &out, qm, nil
195}
196
197// IntentionDelete deletes a single intention.
198func (h *Connect) IntentionDelete(id string, q *WriteOptions) (*WriteMeta, error) {
199 r := h.c.newRequest("DELETE", "/v1/connect/intentions/"+id)
200 r.setWriteOptions(q)
201 rtt, resp, err := requireOK(h.c.doRequest(r))
202 if err != nil {
203 return nil, err
204 }
205 defer resp.Body.Close()
206
207 qm := &WriteMeta{}
208 qm.RequestTime = rtt
209
210 return qm, nil
211}
212
213// IntentionMatch returns the list of intentions that match a given source
214// or destination. The returned intentions are ordered by precedence where
215// result[0] is the highest precedence (if that matches, then that rule overrides
216// all other rules).
217//
218// Matching can be done for multiple names at the same time. The resulting
219// map is keyed by the given names. Casing is preserved.
220func (h *Connect) IntentionMatch(args *IntentionMatch, q *QueryOptions) (map[string][]*Intention, *QueryMeta, error) {
221 r := h.c.newRequest("GET", "/v1/connect/intentions/match")
222 r.setQueryOptions(q)
223 r.params.Set("by", string(args.By))
224 for _, name := range args.Names {
225 r.params.Add("name", name)
226 }
227 rtt, resp, err := requireOK(h.c.doRequest(r))
228 if err != nil {
229 return nil, nil, err
230 }
231 defer resp.Body.Close()
232
233 qm := &QueryMeta{}
234 parseQueryMeta(resp, qm)
235 qm.RequestTime = rtt
236
237 var out map[string][]*Intention
238 if err := decodeBody(resp, &out); err != nil {
239 return nil, nil, err
240 }
241 return out, qm, nil
242}
243
244// IntentionCheck returns whether a given source/destination would be allowed
245// or not given the current set of intentions and the configuration of Consul.
246func (h *Connect) IntentionCheck(args *IntentionCheck, q *QueryOptions) (bool, *QueryMeta, error) {
247 r := h.c.newRequest("GET", "/v1/connect/intentions/check")
248 r.setQueryOptions(q)
249 r.params.Set("source", args.Source)
250 r.params.Set("destination", args.Destination)
251 if args.SourceType != "" {
252 r.params.Set("source-type", string(args.SourceType))
253 }
254 rtt, resp, err := requireOK(h.c.doRequest(r))
255 if err != nil {
256 return false, nil, err
257 }
258 defer resp.Body.Close()
259
260 qm := &QueryMeta{}
261 parseQueryMeta(resp, qm)
262 qm.RequestTime = rtt
263
264 var out struct{ Allowed bool }
265 if err := decodeBody(resp, &out); err != nil {
266 return false, nil, err
267 }
268 return out.Allowed, qm, nil
269}
270
271// IntentionCreate will create a new intention. The ID in the given
272// structure must be empty and a generate ID will be returned on
273// success.
274func (c *Connect) IntentionCreate(ixn *Intention, q *WriteOptions) (string, *WriteMeta, error) {
275 r := c.c.newRequest("POST", "/v1/connect/intentions")
276 r.setWriteOptions(q)
277 r.obj = ixn
278 rtt, resp, err := requireOK(c.c.doRequest(r))
279 if err != nil {
280 return "", nil, err
281 }
282 defer resp.Body.Close()
283
284 wm := &WriteMeta{}
285 wm.RequestTime = rtt
286
287 var out struct{ ID string }
288 if err := decodeBody(resp, &out); err != nil {
289 return "", nil, err
290 }
291 return out.ID, wm, nil
292}
293
294// IntentionUpdate will update an existing intention. The ID in the given
295// structure must be non-empty.
296func (c *Connect) IntentionUpdate(ixn *Intention, q *WriteOptions) (*WriteMeta, error) {
297 r := c.c.newRequest("PUT", "/v1/connect/intentions/"+ixn.ID)
298 r.setWriteOptions(q)
299 r.obj = ixn
300 rtt, resp, err := requireOK(c.c.doRequest(r))
301 if err != nil {
302 return nil, err
303 }
304 defer resp.Body.Close()
305
306 wm := &WriteMeta{}
307 wm.RequestTime = rtt
308 return wm, nil
309}