github.imxd.top/hashicorp/consul@v1.4.5/api/connect_intention.go (about)

     1  package api
     2  
     3  import (
     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.
    13  type 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.
    62  func (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.
    71  func (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.
    77  func (i *Intention) DestinationString() string {
    78  	return i.partString(i.DestinationNS, i.DestinationName)
    79  }
    80  
    81  func (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.
    92  const IntentionDefaultNamespace = "default"
    93  
    94  // IntentionAction is the action that the intention represents. This
    95  // can be "allow" or "deny" to whitelist or blacklist intentions.
    96  type IntentionAction string
    97  
    98  const (
    99  	IntentionActionAllow IntentionAction = "allow"
   100  	IntentionActionDeny  IntentionAction = "deny"
   101  )
   102  
   103  // IntentionSourceType is the type of the source within an intention.
   104  type IntentionSourceType string
   105  
   106  const (
   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.
   112  type 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.
   120  type IntentionMatchType string
   121  
   122  const (
   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.
   129  type 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.
   140  func (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.
   161  func (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.
   191  func (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.
   213  func (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.
   239  func (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.
   267  func (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.
   289  func (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  }