Skip to content

Commit

Permalink
agent: return the default ACL policy to callers as a header (#9101)
Browse files Browse the repository at this point in the history
Header is: X-Consul-Default-ACL-Policy=<allow|deny>

This is of particular utility when fetching matching intentions, as the
fallthrough for a request that doesn't match any intentions is to
enforce using the default acl policy.
  • Loading branch information
rboyer authored and hashicorp-ci committed Nov 12, 2020
1 parent cbf788b commit f815014
Show file tree
Hide file tree
Showing 8 changed files with 89 additions and 4 deletions.
3 changes: 3 additions & 0 deletions .changelog/9101.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:feature
agent: return the default ACL policy to callers as a header
```
4 changes: 2 additions & 2 deletions agent/config/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ type RuntimeConfig struct {

// ACLDefaultPolicy is used to control the ACL interaction when
// there is no defined policy. This can be "allow" which means
// ACLs are used to black-list, or "deny" which means ACLs are
// white-lists.
// ACLs are used to deny-list, or "deny" which means ACLs are
// allow-lists.
//
// hcl: acl.default_policy = ("allow"|"deny")
ACLDefaultPolicy string
Expand Down
4 changes: 2 additions & 2 deletions agent/consul/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,8 +268,8 @@ type Config struct {

// ACLDefaultPolicy is used to control the ACL interaction when
// there is no defined policy. This can be "allow" which means
// ACLs are used to black-list, or "deny" which means ACLs are
// white-lists.
// ACLs are used to deny-list, or "deny" which means ACLs are
// allow-lists.
ACLDefaultPolicy string

// ACLDownPolicy controls the behavior of ACLs if the ACLDatacenter
Expand Down
7 changes: 7 additions & 0 deletions agent/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc
return func(resp http.ResponseWriter, req *http.Request) {
setHeaders(resp, s.agent.config.HTTPResponseHeaders)
setTranslateAddr(resp, s.agent.config.TranslateWANAddrs)
setACLDefaultPolicy(resp, s.agent.config.ACLDefaultPolicy)

// Obfuscate any tokens from appearing in the logs
formVals, err := url.ParseQuery(req.URL.RawQuery)
Expand Down Expand Up @@ -697,6 +698,12 @@ func setConsistency(resp http.ResponseWriter, consistency string) {
}
}

func setACLDefaultPolicy(resp http.ResponseWriter, aclDefaultPolicy string) {
if aclDefaultPolicy != "" {
resp.Header().Set("X-Consul-Default-ACL-Policy", aclDefaultPolicy)
}
}

// setLastContact is used to set the last contact header
func setLastContact(resp http.ResponseWriter, last time.Duration) {
if last < 0 {
Expand Down
48 changes: 48 additions & 0 deletions agent/http_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,6 +415,54 @@ func TestHTTPAPI_TranslateAddrHeader(t *testing.T) {
}
}

func TestHTTPAPI_DefaultACLPolicy(t *testing.T) {
t.Parallel()

type testcase struct {
name string
hcl string
expect string
}

cases := []testcase{
{
name: "default is allow",
hcl: ``,
expect: "allow",
},
{
name: "explicit allow",
hcl: `acl { default_policy = "allow" }`,
expect: "allow",
},
{
name: "explicit deny",
hcl: `acl { default_policy = "deny" }`,
expect: "deny",
},
}

for _, tc := range cases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()

a := NewTestAgent(t, tc.hcl)
defer a.Shutdown()

resp := httptest.NewRecorder()
handler := func(resp http.ResponseWriter, req *http.Request) (interface{}, error) {
return nil, nil
}

req, _ := http.NewRequest("GET", "/v1/agent/self", nil)
a.srv.wrap(handler, []string{"GET"})(resp, req)

require.Equal(t, tc.expect, resp.Header().Get("X-Consul-Default-ACL-Policy"))
})
}
}

func TestHTTPAPIResponseHeaders(t *testing.T) {
t.Parallel()
a := NewTestAgent(t, `
Expand Down
11 changes: 11 additions & 0 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ type QueryMeta struct {
// CacheAge is set if request was ?cached and indicates how stale the cached
// response is.
CacheAge time.Duration

// DefaultACLPolicy is used to control the ACL interaction when there is no
// defined policy. This can be "allow" which means ACLs are used to
// deny-list, or "deny" which means ACLs are allow-lists.
DefaultACLPolicy string
}

// WriteMeta is used to return meta data about a write
Expand Down Expand Up @@ -962,6 +967,12 @@ func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
q.AddressTranslationEnabled = false
}

// Parse X-Consul-Default-ACL-Policy
switch v := header.Get("X-Consul-Default-ACL-Policy"); v {
case "allow", "deny":
q.DefaultACLPolicy = v
}

// Parse Cache info
if cacheStr := header.Get("X-Cache"); cacheStr != "" {
q.CacheHit = strings.EqualFold(cacheStr, "HIT")
Expand Down
4 changes: 4 additions & 0 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,7 @@ func TestAPI_ParseQueryMeta(t *testing.T) {
resp.Header.Set("X-Consul-LastContact", "80")
resp.Header.Set("X-Consul-KnownLeader", "true")
resp.Header.Set("X-Consul-Translate-Addresses", "true")
resp.Header.Set("X-Consul-Default-ACL-Policy", "deny")

qm := &QueryMeta{}
if err := parseQueryMeta(resp, qm); err != nil {
Expand All @@ -858,6 +859,9 @@ func TestAPI_ParseQueryMeta(t *testing.T) {
if !qm.AddressTranslationEnabled {
t.Fatalf("Bad: %v", qm)
}
if qm.DefaultACLPolicy != "deny" {
t.Fatalf("Bad: %v", qm)
}
}

func TestAPI_UnixSocket(t *testing.T) {
Expand Down
12 changes: 12 additions & 0 deletions website/pages/api-docs/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ to allow clients to know if address translation is in effect, the
and will have a value of `true`. If translation is not enabled then this header
will not be present.

## Default ACL Policy

All API responses for Consul versions after 1.9 will include an HTTP response
header `X-Consul-Default-ACL-Policy` set to either "allow" or "deny" which
mirrors the current value of the agent's
[`acl.default_policy`](/docs/agent/options#acl_default_policy) option.

This is also the default [intention](/docs/connect/intentions) enforcement
action if no intention matches.

This is returned even if ACLs are disabled.

## UUID Format

UUID-format identifiers generated by the Consul API use the
Expand Down

0 comments on commit f815014

Please sign in to comment.