blob: a996c03e5e7e63b48a9e73ee76b894e1ee865392 [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
57 CreateIndex uint64
58 ModifyIndex uint64
59}
60
61// String returns human-friendly output describing ths intention.
62func (i *Intention) String() string {
63 return fmt.Sprintf("%s => %s (%s)",
64 i.SourceString(),
65 i.DestinationString(),
66 i.Action)
67}
68
69// SourceString returns the namespace/name format for the source, or
70// just "name" if the namespace is the default namespace.
71func (i *Intention) SourceString() string {
72 return i.partString(i.SourceNS, i.SourceName)
73}
74
75// DestinationString returns the namespace/name format for the source, or
76// just "name" if the namespace is the default namespace.
77func (i *Intention) DestinationString() string {
78 return i.partString(i.DestinationNS, i.DestinationName)
79}
80
81func (i *Intention) partString(ns, n string) string {
82 // For now we omit the default namespace from the output. In the future
83 // we might want to look at this and show this in a multi-namespace world.
84 if ns != "" && ns != IntentionDefaultNamespace {
85 n = ns + "/" + n
86 }
87
88 return n
89}
90
91// IntentionDefaultNamespace is the default namespace value.
92const IntentionDefaultNamespace = "default"
93
94// IntentionAction is the action that the intention represents. This
95// can be "allow" or "deny" to whitelist or blacklist intentions.
96type IntentionAction string
97
98const (
99 IntentionActionAllow IntentionAction = "allow"
100 IntentionActionDeny IntentionAction = "deny"
101)
102
103// IntentionSourceType is the type of the source within an intention.
104type IntentionSourceType string
105
106const (
107 // IntentionSourceConsul is a service within the Consul catalog.
108 IntentionSourceConsul IntentionSourceType = "consul"
109)
110
111// IntentionMatch are the arguments for the intention match API.
112type IntentionMatch struct {
113 By IntentionMatchType
114 Names []string
115}
116
117// IntentionMatchType is the target for a match request. For example,
118// matching by source will look for all intentions that match the given
119// source value.
120type IntentionMatchType string
121
122const (
123 IntentionMatchSource IntentionMatchType = "source"
124 IntentionMatchDestination IntentionMatchType = "destination"
125)
126
127// IntentionCheck are the arguments for the intention check API. For
128// more documentation see the IntentionCheck function.
129type IntentionCheck struct {
130 // Source and Destination are the source and destination values to
131 // check. The destination is always a Consul service, but the source
132 // may be other values as defined by the SourceType.
133 Source, Destination string
134
135 // SourceType is the type of the value for the source.
136 SourceType IntentionSourceType
137}
138
139// Intentions returns the list of intentions.
140func (h *Connect) Intentions(q *QueryOptions) ([]*Intention, *QueryMeta, error) {
141 r := h.c.newRequest("GET", "/v1/connect/intentions")
142 r.setQueryOptions(q)
143 rtt, resp, err := requireOK(h.c.doRequest(r))
144 if err != nil {
145 return nil, nil, err
146 }
147 defer resp.Body.Close()
148
149 qm := &QueryMeta{}
150 parseQueryMeta(resp, qm)
151 qm.RequestTime = rtt
152
153 var out []*Intention
154 if err := decodeBody(resp, &out); err != nil {
155 return nil, nil, err
156 }
157 return out, qm, nil
158}
159
160// IntentionGet retrieves a single intention.
161func (h *Connect) IntentionGet(id string, q *QueryOptions) (*Intention, *QueryMeta, error) {
162 r := h.c.newRequest("GET", "/v1/connect/intentions/"+id)
163 r.setQueryOptions(q)
164 rtt, resp, err := h.c.doRequest(r)
165 if err != nil {
166 return nil, nil, err
167 }
168 defer resp.Body.Close()
169
170 qm := &QueryMeta{}
171 parseQueryMeta(resp, qm)
172 qm.RequestTime = rtt
173
174 if resp.StatusCode == 404 {
175 return nil, qm, nil
176 } else if resp.StatusCode != 200 {
177 var buf bytes.Buffer
178 io.Copy(&buf, resp.Body)
179 return nil, nil, fmt.Errorf(
180 "Unexpected response %d: %s", resp.StatusCode, buf.String())
181 }
182
183 var out Intention
184 if err := decodeBody(resp, &out); err != nil {
185 return nil, nil, err
186 }
187 return &out, qm, nil
188}
189
190// IntentionDelete deletes a single intention.
191func (h *Connect) IntentionDelete(id string, q *WriteOptions) (*WriteMeta, error) {
192 r := h.c.newRequest("DELETE", "/v1/connect/intentions/"+id)
193 r.setWriteOptions(q)
194 rtt, resp, err := requireOK(h.c.doRequest(r))
195 if err != nil {
196 return nil, err
197 }
198 defer resp.Body.Close()
199
200 qm := &WriteMeta{}
201 qm.RequestTime = rtt
202
203 return qm, nil
204}
205
206// IntentionMatch returns the list of intentions that match a given source
207// or destination. The returned intentions are ordered by precedence where
208// result[0] is the highest precedence (if that matches, then that rule overrides
209// all other rules).
210//
211// Matching can be done for multiple names at the same time. The resulting
212// map is keyed by the given names. Casing is preserved.
213func (h *Connect) IntentionMatch(args *IntentionMatch, q *QueryOptions) (map[string][]*Intention, *QueryMeta, error) {
214 r := h.c.newRequest("GET", "/v1/connect/intentions/match")
215 r.setQueryOptions(q)
216 r.params.Set("by", string(args.By))
217 for _, name := range args.Names {
218 r.params.Add("name", name)
219 }
220 rtt, resp, err := requireOK(h.c.doRequest(r))
221 if err != nil {
222 return nil, nil, err
223 }
224 defer resp.Body.Close()
225
226 qm := &QueryMeta{}
227 parseQueryMeta(resp, qm)
228 qm.RequestTime = rtt
229
230 var out map[string][]*Intention
231 if err := decodeBody(resp, &out); err != nil {
232 return nil, nil, err
233 }
234 return out, qm, nil
235}
236
237// IntentionCheck returns whether a given source/destination would be allowed
238// or not given the current set of intentions and the configuration of Consul.
239func (h *Connect) IntentionCheck(args *IntentionCheck, q *QueryOptions) (bool, *QueryMeta, error) {
240 r := h.c.newRequest("GET", "/v1/connect/intentions/check")
241 r.setQueryOptions(q)
242 r.params.Set("source", args.Source)
243 r.params.Set("destination", args.Destination)
244 if args.SourceType != "" {
245 r.params.Set("source-type", string(args.SourceType))
246 }
247 rtt, resp, err := requireOK(h.c.doRequest(r))
248 if err != nil {
249 return false, nil, err
250 }
251 defer resp.Body.Close()
252
253 qm := &QueryMeta{}
254 parseQueryMeta(resp, qm)
255 qm.RequestTime = rtt
256
257 var out struct{ Allowed bool }
258 if err := decodeBody(resp, &out); err != nil {
259 return false, nil, err
260 }
261 return out.Allowed, qm, nil
262}
263
264// IntentionCreate will create a new intention. The ID in the given
265// structure must be empty and a generate ID will be returned on
266// success.
267func (c *Connect) IntentionCreate(ixn *Intention, q *WriteOptions) (string, *WriteMeta, error) {
268 r := c.c.newRequest("POST", "/v1/connect/intentions")
269 r.setWriteOptions(q)
270 r.obj = ixn
271 rtt, resp, err := requireOK(c.c.doRequest(r))
272 if err != nil {
273 return "", nil, err
274 }
275 defer resp.Body.Close()
276
277 wm := &WriteMeta{}
278 wm.RequestTime = rtt
279
280 var out struct{ ID string }
281 if err := decodeBody(resp, &out); err != nil {
282 return "", nil, err
283 }
284 return out.ID, wm, nil
285}
286
287// IntentionUpdate will update an existing intention. The ID in the given
288// structure must be non-empty.
289func (c *Connect) IntentionUpdate(ixn *Intention, q *WriteOptions) (*WriteMeta, error) {
290 r := c.c.newRequest("PUT", "/v1/connect/intentions/"+ixn.ID)
291 r.setWriteOptions(q)
292 r.obj = ixn
293 rtt, resp, err := requireOK(c.c.doRequest(r))
294 if err != nil {
295 return nil, err
296 }
297 defer resp.Body.Close()
298
299 wm := &WriteMeta{}
300 wm.RequestTime = rtt
301 return wm, nil
302}