agent: return the default ACL policy to callers as a header (#9101)

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.
This commit is contained in:
R.B. Boyer 2020-11-12 10:38:32 -06:00 committed by GitHub
parent 990134371b
commit a5bd1ba323
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 89 additions and 4 deletions

3
.changelog/9101.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
agent: return the default ACL policy to callers as a header
```

View File

@ -77,8 +77,8 @@ type RuntimeConfig struct {
// ACLDefaultPolicy is used to control the ACL interaction when // ACLDefaultPolicy is used to control the ACL interaction when
// there is no defined policy. This can be "allow" which means // there is no defined policy. This can be "allow" which means
// ACLs are used to black-list, or "deny" which means ACLs are // ACLs are used to deny-list, or "deny" which means ACLs are
// white-lists. // allow-lists.
// //
// hcl: acl.default_policy = ("allow"|"deny") // hcl: acl.default_policy = ("allow"|"deny")
ACLDefaultPolicy string ACLDefaultPolicy string

View File

@ -268,8 +268,8 @@ type Config struct {
// ACLDefaultPolicy is used to control the ACL interaction when // ACLDefaultPolicy is used to control the ACL interaction when
// there is no defined policy. This can be "allow" which means // there is no defined policy. This can be "allow" which means
// ACLs are used to black-list, or "deny" which means ACLs are // ACLs are used to deny-list, or "deny" which means ACLs are
// white-lists. // allow-lists.
ACLDefaultPolicy string ACLDefaultPolicy string
// ACLDownPolicy controls the behavior of ACLs if the ACLDatacenter // ACLDownPolicy controls the behavior of ACLs if the ACLDatacenter

View File

@ -357,6 +357,7 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc
return func(resp http.ResponseWriter, req *http.Request) { return func(resp http.ResponseWriter, req *http.Request) {
setHeaders(resp, s.agent.config.HTTPResponseHeaders) setHeaders(resp, s.agent.config.HTTPResponseHeaders)
setTranslateAddr(resp, s.agent.config.TranslateWANAddrs) setTranslateAddr(resp, s.agent.config.TranslateWANAddrs)
setACLDefaultPolicy(resp, s.agent.config.ACLDefaultPolicy)
// Obfuscate any tokens from appearing in the logs // Obfuscate any tokens from appearing in the logs
formVals, err := url.ParseQuery(req.URL.RawQuery) formVals, err := url.ParseQuery(req.URL.RawQuery)
@ -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 // setLastContact is used to set the last contact header
func setLastContact(resp http.ResponseWriter, last time.Duration) { func setLastContact(resp http.ResponseWriter, last time.Duration) {
if last < 0 { if last < 0 {

View File

@ -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) { func TestHTTPAPIResponseHeaders(t *testing.T) {
t.Parallel() t.Parallel()
a := NewTestAgent(t, ` a := NewTestAgent(t, `

View File

@ -254,6 +254,11 @@ type QueryMeta struct {
// CacheAge is set if request was ?cached and indicates how stale the cached // CacheAge is set if request was ?cached and indicates how stale the cached
// response is. // response is.
CacheAge time.Duration 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 // WriteMeta is used to return meta data about a write
@ -962,6 +967,12 @@ func parseQueryMeta(resp *http.Response, q *QueryMeta) error {
q.AddressTranslationEnabled = false 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 // Parse Cache info
if cacheStr := header.Get("X-Cache"); cacheStr != "" { if cacheStr := header.Get("X-Cache"); cacheStr != "" {
q.CacheHit = strings.EqualFold(cacheStr, "HIT") q.CacheHit = strings.EqualFold(cacheStr, "HIT")

View File

@ -840,6 +840,7 @@ func TestAPI_ParseQueryMeta(t *testing.T) {
resp.Header.Set("X-Consul-LastContact", "80") resp.Header.Set("X-Consul-LastContact", "80")
resp.Header.Set("X-Consul-KnownLeader", "true") resp.Header.Set("X-Consul-KnownLeader", "true")
resp.Header.Set("X-Consul-Translate-Addresses", "true") resp.Header.Set("X-Consul-Translate-Addresses", "true")
resp.Header.Set("X-Consul-Default-ACL-Policy", "deny")
qm := &QueryMeta{} qm := &QueryMeta{}
if err := parseQueryMeta(resp, qm); err != nil { if err := parseQueryMeta(resp, qm); err != nil {
@ -858,6 +859,9 @@ func TestAPI_ParseQueryMeta(t *testing.T) {
if !qm.AddressTranslationEnabled { if !qm.AddressTranslationEnabled {
t.Fatalf("Bad: %v", qm) t.Fatalf("Bad: %v", qm)
} }
if qm.DefaultACLPolicy != "deny" {
t.Fatalf("Bad: %v", qm)
}
} }
func TestAPI_UnixSocket(t *testing.T) { func TestAPI_UnixSocket(t *testing.T) {

View File

@ -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 and will have a value of `true`. If translation is not enabled then this header
will not be present. 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
UUID-format identifiers generated by the Consul API use the UUID-format identifiers generated by the Consul API use the