From 0efe4780449fee270d105e6e896ce23767e223d4 Mon Sep 17 00:00:00 2001 From: Dan Upton Date: Fri, 3 Dec 2021 17:11:26 +0000 Subject: [PATCH] Groundwork for exposing when queries are filtered by ACLs (#11569) --- .changelog/11569.txt | 3 + agent/consul/acl.go | 1 + agent/consul/acl_endpoint.go | 4 +- agent/consul/internal_endpoint.go | 2 +- agent/consul/internal_endpoint_test.go | 7 ++ agent/consul/prepared_query_endpoint.go | 10 +- agent/consul/rpc.go | 39 +++++++- agent/consul/rpc_test.go | 34 +++++++ agent/consul/snapshot_endpoint.go | 2 +- agent/consul/txn_endpoint.go | 5 +- agent/consul/txn_endpoint_test.go | 6 +- agent/http.go | 11 ++ agent/http_test.go | 43 +++++--- agent/structs/protobuf_compat.go | 14 +++ agent/structs/structs.go | 5 + agent/structs/structs_test.go | 9 +- api/api.go | 13 +++ api/api_test.go | 4 + proto/pbcommon/common.pb.go | 127 ++++++++++++++++-------- proto/pbcommon/common.proto | 10 +- 20 files changed, 275 insertions(+), 74 deletions(-) create mode 100644 .changelog/11569.txt diff --git a/.changelog/11569.txt b/.changelog/11569.txt new file mode 100644 index 000000000..84657f984 --- /dev/null +++ b/.changelog/11569.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +api: responses that contain only a partial subset of results, due to filtering by ACL policies, may now include an `X-Consul-Results-Filtered-By-ACLs` header +``` diff --git a/agent/consul/acl.go b/agent/consul/acl.go index c659e7b37..685159071 100644 --- a/agent/consul/acl.go +++ b/agent/consul/acl.go @@ -1807,6 +1807,7 @@ func filterACLWithAuthorizer(logger hclog.Logger, authorizer acl.Authorizer, sub filtered := filt.filterServiceTopology(v.ServiceTopology) if filtered { v.FilteredByACLs = true + v.QueryMeta.ResultsFilteredByACLs = true } case *structs.DatacenterIndexedCheckServiceNodes: diff --git a/agent/consul/acl_endpoint.go b/agent/consul/acl_endpoint.go index f3e7f3980..0b58c2397 100644 --- a/agent/consul/acl_endpoint.go +++ b/agent/consul/acl_endpoint.go @@ -1360,7 +1360,7 @@ func (a *ACL) PolicyResolve(args *structs.ACLPolicyBatchGetRequest, reply *struc } } - a.srv.setQueryMeta(&reply.QueryMeta) + a.srv.setQueryMeta(&reply.QueryMeta, args.Token) return nil } @@ -1761,7 +1761,7 @@ func (a *ACL) RoleResolve(args *structs.ACLRoleBatchGetRequest, reply *structs.A } } - a.srv.setQueryMeta(&reply.QueryMeta) + a.srv.setQueryMeta(&reply.QueryMeta, args.Token) return nil } diff --git a/agent/consul/internal_endpoint.go b/agent/consul/internal_endpoint.go index 7eb725a53..27f8ba688 100644 --- a/agent/consul/internal_endpoint.go +++ b/agent/consul/internal_endpoint.go @@ -411,7 +411,7 @@ func (m *Internal) EventFire(args *structs.EventFireRequest, } // Set the query meta data - m.srv.setQueryMeta(&reply.QueryMeta) + m.srv.setQueryMeta(&reply.QueryMeta, args.Token) // Add the consul prefix to the event name eventName := userEventName(args.Name) diff --git a/agent/consul/internal_endpoint_test.go b/agent/consul/internal_endpoint_test.go index d5a49525a..a1fd93427 100644 --- a/agent/consul/internal_endpoint_test.go +++ b/agent/consul/internal_endpoint_test.go @@ -1728,6 +1728,7 @@ func TestInternal_ServiceTopology(t *testing.T) { var out structs.IndexedServiceTopology require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out)) require.False(r, out.FilteredByACLs) + require.False(r, out.QueryMeta.ResultsFilteredByACLs) require.Equal(r, "http", out.ServiceTopology.MetricsProtocol) // foo/api, foo/api-proxy @@ -1767,6 +1768,7 @@ func TestInternal_ServiceTopology(t *testing.T) { var out structs.IndexedServiceTopology require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out)) require.False(r, out.FilteredByACLs) + require.False(r, out.QueryMeta.ResultsFilteredByACLs) require.Equal(r, "http", out.ServiceTopology.MetricsProtocol) // edge/ingress @@ -1822,6 +1824,7 @@ func TestInternal_ServiceTopology(t *testing.T) { var out structs.IndexedServiceTopology require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out)) require.False(r, out.FilteredByACLs) + require.False(r, out.QueryMeta.ResultsFilteredByACLs) require.Equal(r, "http", out.ServiceTopology.MetricsProtocol) // foo/api, foo/api-proxy @@ -1875,6 +1878,7 @@ func TestInternal_ServiceTopology(t *testing.T) { var out structs.IndexedServiceTopology require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out)) require.False(r, out.FilteredByACLs) + require.False(r, out.QueryMeta.ResultsFilteredByACLs) require.Equal(r, "http", out.ServiceTopology.MetricsProtocol) require.Len(r, out.ServiceTopology.Upstreams, 0) @@ -1931,6 +1935,7 @@ func TestInternal_ServiceTopology_RoutingConfig(t *testing.T) { var out structs.IndexedServiceTopology require.NoError(r, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out)) require.False(r, out.FilteredByACLs) + require.False(r, out.QueryMeta.ResultsFilteredByACLs) require.Equal(r, "http", out.ServiceTopology.MetricsProtocol) require.Empty(r, out.ServiceTopology.Downstreams) @@ -2010,6 +2015,7 @@ service "web" { policy = "read" } require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out)) require.True(t, out.FilteredByACLs) + require.True(t, out.QueryMeta.ResultsFilteredByACLs) require.Equal(t, "http", out.ServiceTopology.MetricsProtocol) // The web-proxy upstream gets filtered out from both bar and baz @@ -2030,6 +2036,7 @@ service "web" { policy = "read" } require.NoError(t, msgpackrpc.CallWithCodec(codec, "Internal.ServiceTopology", &args, &out)) require.True(t, out.FilteredByACLs) + require.True(t, out.QueryMeta.ResultsFilteredByACLs) require.Equal(t, "http", out.ServiceTopology.MetricsProtocol) // The redis upstream gets filtered out but the api and proxy downstream are returned diff --git a/agent/consul/prepared_query_endpoint.go b/agent/consul/prepared_query_endpoint.go index 5df85ef60..6c9b90c4f 100644 --- a/agent/consul/prepared_query_endpoint.go +++ b/agent/consul/prepared_query_endpoint.go @@ -299,7 +299,7 @@ func (p *PreparedQuery) Explain(args *structs.PreparedQueryExecuteRequest, defer metrics.MeasureSince([]string{"prepared-query", "explain"}, time.Now()) // We have to do this ourselves since we are not doing a blocking RPC. - p.srv.setQueryMeta(&reply.QueryMeta) + p.srv.setQueryMeta(&reply.QueryMeta, args.Token) if args.RequireConsistent { if err := p.srv.consistentRead(); err != nil { return err @@ -346,7 +346,6 @@ func (p *PreparedQuery) Execute(args *structs.PreparedQueryExecuteRequest, defer metrics.MeasureSince([]string{"prepared-query", "execute"}, time.Now()) // We have to do this ourselves since we are not doing a blocking RPC. - p.srv.setQueryMeta(&reply.QueryMeta) if args.RequireConsistent { if err := p.srv.consistentRead(); err != nil { return err @@ -383,6 +382,9 @@ func (p *PreparedQuery) Execute(args *structs.PreparedQueryExecuteRequest, // might not be worth the code complexity and behavior differences, // though, since this is essentially a misconfiguration. + // We have to do this ourselves since we are not doing a blocking RPC. + p.srv.setQueryMeta(&reply.QueryMeta, token) + // Shuffle the results in case coordinates are not available if they // requested an RTT sort. reply.Nodes.Shuffle() @@ -481,7 +483,6 @@ func (p *PreparedQuery) ExecuteRemote(args *structs.PreparedQueryExecuteRemoteRe defer metrics.MeasureSince([]string{"prepared-query", "execute_remote"}, time.Now()) // We have to do this ourselves since we are not doing a blocking RPC. - p.srv.setQueryMeta(&reply.QueryMeta) if args.RequireConsistent { if err := p.srv.consistentRead(); err != nil { return err @@ -503,6 +504,9 @@ func (p *PreparedQuery) ExecuteRemote(args *structs.PreparedQueryExecuteRemoteRe return err } + // We have to do this ourselves since we are not doing a blocking RPC. + p.srv.setQueryMeta(&reply.QueryMeta, token) + // We don't bother trying to do an RTT sort here since we are by // definition in another DC. We just shuffle to make sure that we // balance the load across the results. diff --git a/agent/consul/rpc.go b/agent/consul/rpc.go index c8d733a28..9a93b562e 100644 --- a/agent/consul/rpc.go +++ b/agent/consul/rpc.go @@ -938,8 +938,6 @@ func (s *Server) blockingQuery(queryOpts structs.QueryOptionsCompat, queryMeta s RUN_QUERY: // Setup blocking loop - // Update the query metadata. - s.setQueryMeta(queryMeta) // Validate // If the read must be consistent we verify that we are still the leader. @@ -968,6 +966,10 @@ RUN_QUERY: // Execute the queryFn err := fn(ws, state) + + // Update the query metadata. + s.setQueryMeta(queryMeta, queryOpts.GetToken()) + // Note we check queryOpts.MinQueryIndex is greater than zero to determine if // blocking was requested by client, NOT meta.Index since the state function // might return zero if something is not initialized and care wasn't taken to @@ -1001,7 +1003,9 @@ RUN_QUERY: } // setQueryMeta is used to populate the QueryMeta data for an RPC call -func (s *Server) setQueryMeta(m structs.QueryMetaCompat) { +// +// Note: This method must be called *after* filtering query results with ACLs. +func (s *Server) setQueryMeta(m structs.QueryMetaCompat, token string) { if s.IsLeader() { m.SetLastContact(0) m.SetKnownLeader(true) @@ -1009,6 +1013,7 @@ func (s *Server) setQueryMeta(m structs.QueryMetaCompat) { m.SetLastContact(time.Since(s.raft.LastContact())) m.SetKnownLeader(s.raft.Leader() != "") } + maskResultsFilteredByACLs(token, m) } // consistentRead is used to ensure we do not perform a stale @@ -1043,3 +1048,31 @@ func (s *Server) consistentRead() error { return structs.ErrNotReadyForConsistentReads } + +// maskResultsFilteredByACLs blanks out the ResultsFilteredByACLs flag if the +// request is unauthenticated, to limit information leaking. +// +// Endpoints that support bexpr filtering could be used in combination with +// this flag/header to discover the existence of resources to which the user +// does not have access, therefore we only expose it when the user presents +// a valid ACL token. This doesn't completely remove the risk (by nature the +// purpose of this flag is to let the user know there are resources they can +// not access) but it prevents completely unauthenticated users from doing so. +// +// Notes: +// +// * The definition of "unauthenticated" here is incomplete, as it doesn't +// account for the fact that operators can modify the anonymous token with +// custom policies, or set namespace default policies. As these scenarios +// are less common and this flag is a best-effort UX improvement, we think +// the trade-off for reduced complexity is acceptable. +// +// * This method assumes that the given token has already been validated (and +// will only check whether it is blank or not). It's a safe assumption because +// ResultsFilteredByACLs is only set to try when applying the already-resolved +// token's policies. +func maskResultsFilteredByACLs(token string, meta structs.QueryMetaCompat) { + if token == "" { + meta.SetResultsFilteredByACLs(false) + } +} diff --git a/agent/consul/rpc_test.go b/agent/consul/rpc_test.go index f3b013bcd..8afb225a4 100644 --- a/agent/consul/rpc_test.go +++ b/agent/consul/rpc_test.go @@ -36,6 +36,7 @@ import ( "github.com/hashicorp/consul/agent/structs" tokenStore "github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/proto/pbsubscribe" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" @@ -369,6 +370,39 @@ func TestRPC_blockingQuery(t *testing.T) { t.Fatalf("bad: %d", calls) } } + + t.Run("ResultsFilteredByACLs is reset for unauthenticated calls", func(t *testing.T) { + opts := structs.QueryOptions{ + Token: "", + } + var meta structs.QueryMeta + fn := func(_ memdb.WatchSet, _ *state.Store) error { + meta.ResultsFilteredByACLs = true + return nil + } + + err := s.blockingQuery(&opts, &meta, fn) + require.NoError(err) + require.False(meta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be reset for unauthenticated calls") + }) + + t.Run("ResultsFilteredByACLs is honored for authenticated calls", func(t *testing.T) { + token, err := lib.GenerateUUID(nil) + require.NoError(err) + + opts := structs.QueryOptions{ + Token: token, + } + var meta structs.QueryMeta + fn := func(_ memdb.WatchSet, _ *state.Store) error { + meta.ResultsFilteredByACLs = true + return nil + } + + err = s.blockingQuery(&opts, &meta, fn) + require.NoError(err) + require.True(meta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be honored for authenticated calls") + }) } func TestRPC_ReadyForConsistentReads(t *testing.T) { diff --git a/agent/consul/snapshot_endpoint.go b/agent/consul/snapshot_endpoint.go index 831cec24f..66b394989 100644 --- a/agent/consul/snapshot_endpoint.go +++ b/agent/consul/snapshot_endpoint.go @@ -77,7 +77,7 @@ func (s *Server) dispatchSnapshotRequest(args *structs.SnapshotRequest, in io.Re // Set the metadata here before we do anything; this should always be // pessimistic if we get more data while the snapshot is being taken. - s.setQueryMeta(&reply.QueryMeta) + s.setQueryMeta(&reply.QueryMeta, args.Token) // Take the snapshot and capture the index. snap, err := snapshot.New(s.logger, s.raft) diff --git a/agent/consul/txn_endpoint.go b/agent/consul/txn_endpoint.go index 4e6ff80d4..7decef147 100644 --- a/agent/consul/txn_endpoint.go +++ b/agent/consul/txn_endpoint.go @@ -183,7 +183,6 @@ func (t *Txn) Read(args *structs.TxnReadRequest, reply *structs.TxnReadResponse) defer metrics.MeasureSince([]string{"txn", "read"}, time.Now()) // We have to do this ourselves since we are not doing a blocking RPC. - t.srv.setQueryMeta(&reply.QueryMeta) if args.RequireConsistent { if err := t.srv.consistentRead(); err != nil { return err @@ -204,5 +203,9 @@ func (t *Txn) Read(args *structs.TxnReadRequest, reply *structs.TxnReadResponse) state := t.srv.fsm.State() reply.Results, reply.Errors = state.TxnRO(args.Ops) reply.Results = FilterTxnResults(authz, reply.Results) + + // We have to do this ourselves since we are not doing a blocking RPC. + t.srv.setQueryMeta(&reply.QueryMeta, args.Token) + return nil } diff --git a/agent/consul/txn_endpoint_test.go b/agent/consul/txn_endpoint_test.go index d850e0b2b..9c3bd7b73 100644 --- a/agent/consul/txn_endpoint_test.go +++ b/agent/consul/txn_endpoint_test.go @@ -941,11 +941,7 @@ func TestTxn_Read_ACLDeny(t *testing.T) { } // Verify the transaction's return value. - expected := structs.TxnReadResponse{ - QueryMeta: structs.QueryMeta{ - KnownLeader: true, - }, - } + var expected structs.TxnReadResponse for i, op := range arg.Ops { switch { case op.KV != nil: diff --git a/agent/http.go b/agent/http.go index 02e95e6ba..a1d8461d0 100644 --- a/agent/http.go +++ b/agent/http.go @@ -734,6 +734,7 @@ func setMeta(resp http.ResponseWriter, m structs.QueryMetaCompat) { setKnownLeader(resp, m.GetKnownLeader()) setConsistency(resp, m.GetConsistencyLevel()) setQueryBackend(resp, m.GetBackend()) + setResultsFilteredByACLs(resp, m.GetResultsFilteredByACLs()) } func setQueryBackend(resp http.ResponseWriter, backend structs.QueryBackend) { @@ -757,6 +758,16 @@ func setCacheMeta(resp http.ResponseWriter, m *cache.ResultMeta) { } } +// setResultsFilteredByACLs sets an HTTP response header to indicate that the +// query results were filtered by enforcing ACLs. If the given filtered value +// is false the header will be omitted, as its ambiguous whether the results +// were not filtered or whether the endpoint doesn't yet support this header. +func setResultsFilteredByACLs(resp http.ResponseWriter, filtered bool) { + if filtered { + resp.Header().Set("X-Consul-Results-Filtered-By-ACLs", "true") + } +} + // setHeaders is used to set canonical response header fields func setHeaders(resp http.ResponseWriter, headers map[string]string) { for field, value := range headers { diff --git a/agent/http_test.go b/agent/http_test.go index d391af3c1..2a68b5ab0 100644 --- a/agent/http_test.go +++ b/agent/http_test.go @@ -264,6 +264,22 @@ func TestSetKnownLeader(t *testing.T) { } } +func TestSetFilteredByACLs(t *testing.T) { + t.Parallel() + resp := httptest.NewRecorder() + setResultsFilteredByACLs(resp, true) + header := resp.Header().Get("X-Consul-Results-Filtered-By-ACLs") + if header != "true" { + t.Fatalf("Bad: %v", header) + } + resp = httptest.NewRecorder() + setResultsFilteredByACLs(resp, false) + header = resp.Header().Get("X-Consul-Results-Filtered-By-ACLs") + if header != "" { + t.Fatalf("Bad: %v", header) + } +} + func TestSetLastContact(t *testing.T) { t.Parallel() tests := []struct { @@ -291,23 +307,24 @@ func TestSetLastContact(t *testing.T) { func TestSetMeta(t *testing.T) { t.Parallel() meta := structs.QueryMeta{ - Index: 1000, - KnownLeader: true, - LastContact: 123456 * time.Microsecond, + Index: 1000, + KnownLeader: true, + LastContact: 123456 * time.Microsecond, + ResultsFilteredByACLs: true, } resp := httptest.NewRecorder() setMeta(resp, &meta) - header := resp.Header().Get("X-Consul-Index") - if header != "1000" { - t.Fatalf("Bad: %v", header) + + testCases := map[string]string{ + "X-Consul-Index": "1000", + "X-Consul-KnownLeader": "true", + "X-Consul-LastContact": "123", + "X-Consul-Results-Filtered-By-ACLs": "true", } - header = resp.Header().Get("X-Consul-KnownLeader") - if header != "true" { - t.Fatalf("Bad: %v", header) - } - header = resp.Header().Get("X-Consul-LastContact") - if header != "123" { - t.Fatalf("Bad: %v", header) + for header, expectedValue := range testCases { + if v := resp.Header().Get(header); v != expectedValue { + t.Fatalf("expected %q for header %s got %q", expectedValue, header, v) + } } } diff --git a/agent/structs/protobuf_compat.go b/agent/structs/protobuf_compat.go index 667443c9e..090190e62 100644 --- a/agent/structs/protobuf_compat.go +++ b/agent/structs/protobuf_compat.go @@ -45,6 +45,8 @@ type QueryMetaCompat interface { GetConsistencyLevel() string SetConsistencyLevel(string) GetBackend() QueryBackend + GetResultsFilteredByACLs() bool + SetResultsFilteredByACLs(bool) } // GetToken helps implement the QueryOptionsCompat interface @@ -274,3 +276,15 @@ func (q *QueryMeta) SetConsistencyLevel(consistencyLevel string) { func (q *QueryMeta) GetBackend() QueryBackend { return q.Backend } + +// GetResultsFilteredByACLs is needed to implement the structs.QueryMetaCompat +// interface. +func (q *QueryMeta) GetResultsFilteredByACLs() bool { + return q.ResultsFilteredByACLs +} + +// SetResultsFilteredByACLs is needed to implement the structs.QueryMetaCompat +// interface. +func (q *QueryMeta) SetResultsFilteredByACLs(v bool) { + q.ResultsFilteredByACLs = v +} diff --git a/agent/structs/structs.go b/agent/structs/structs.go index 86c724b6a..6edbc5545 100644 --- a/agent/structs/structs.go +++ b/agent/structs/structs.go @@ -392,6 +392,11 @@ type QueryMeta struct { // Backend used to handle this query, either blocking-query or streaming. Backend QueryBackend + + // ResultsFilteredByACLs is true when some of the query's results were + // filtered out by enforcing ACLs. It may be false because nothing was + // removed, or because the endpoint does not yet support this flag. + ResultsFilteredByACLs bool } // RegisterRequest is used for the Catalog.Register endpoint diff --git a/agent/structs/structs_test.go b/agent/structs/structs_test.go index 53f35bde3..72616fc02 100644 --- a/agent/structs/structs_test.go +++ b/agent/structs/structs_test.go @@ -2449,10 +2449,11 @@ func TestSnapshotRequestResponse_MsgpackEncodeDecode(t *testing.T) { in := &SnapshotResponse{ Error: "blah", QueryMeta: QueryMeta{ - Index: 3, - LastContact: 5 * time.Second, - KnownLeader: true, - ConsistencyLevel: "default", + Index: 3, + LastContact: 5 * time.Second, + KnownLeader: true, + ConsistencyLevel: "default", + ResultsFilteredByACLs: true, }, } TestMsgpackEncodeDecode(t, in, true) diff --git a/api/api.go b/api/api.go index 47830f25e..d97f1879f 100644 --- a/api/api.go +++ b/api/api.go @@ -281,6 +281,11 @@ type QueryMeta struct { // defined policy. This can be "allow" which means ACLs are used to // deny-list, or "deny" which means ACLs are allow-lists. DefaultACLPolicy string + + // ResultsFilteredByACLs is true when some of the query's results were + // filtered out by enforcing ACLs. It may be false because nothing was + // removed, or because the endpoint does not yet support this flag. + ResultsFilteredByACLs bool } // WriteMeta is used to return meta data about a write @@ -1071,6 +1076,14 @@ func parseQueryMeta(resp *http.Response, q *QueryMeta) error { q.DefaultACLPolicy = v } + // Parse the X-Consul-Results-Filtered-By-ACLs + switch header.Get("X-Consul-Results-Filtered-By-ACLs") { + case "true": + q.ResultsFilteredByACLs = true + default: + q.ResultsFilteredByACLs = false + } + // Parse Cache info if cacheStr := header.Get("X-Cache"); cacheStr != "" { q.CacheHit = strings.EqualFold(cacheStr, "HIT") diff --git a/api/api_test.go b/api/api_test.go index 89852d373..01dc0b681 100644 --- a/api/api_test.go +++ b/api/api_test.go @@ -932,6 +932,7 @@ func TestAPI_ParseQueryMeta(t *testing.T) { resp.Header.Set("X-Consul-KnownLeader", "true") resp.Header.Set("X-Consul-Translate-Addresses", "true") resp.Header.Set("X-Consul-Default-ACL-Policy", "deny") + resp.Header.Set("X-Consul-Results-Filtered-By-ACLs", "true") qm := &QueryMeta{} if err := parseQueryMeta(resp, qm); err != nil { @@ -953,6 +954,9 @@ func TestAPI_ParseQueryMeta(t *testing.T) { if qm.DefaultACLPolicy != "deny" { t.Fatalf("Bad: %v", qm) } + if !qm.ResultsFilteredByACLs { + t.Fatalf("Bad: %v", qm) + } } func TestAPI_UnixSocket(t *testing.T) { diff --git a/proto/pbcommon/common.pb.go b/proto/pbcommon/common.pb.go index 0217bf819..3b29930d5 100644 --- a/proto/pbcommon/common.pb.go +++ b/proto/pbcommon/common.pb.go @@ -391,6 +391,10 @@ type QueryMeta struct { // Having `discovery_max_stale` on the agent can affect whether // the request was served by a leader. ConsistencyLevel string `protobuf:"bytes,4,opt,name=ConsistencyLevel,proto3" json:"ConsistencyLevel,omitempty"` + // ResultsFilteredByACLs is true when some of the query's results were + // filtered out by enforcing ACLs. It may be false because nothing was + // removed, or because the endpoint does not yet support this flag. + ResultsFilteredByACLs bool `protobuf:"varint,7,opt,name=ResultsFilteredByACLs,proto3" json:"ResultsFilteredByACLs,omitempty"` } func (m *QueryMeta) Reset() { *m = QueryMeta{} } @@ -454,6 +458,13 @@ func (m *QueryMeta) GetConsistencyLevel() string { return "" } +func (m *QueryMeta) GetResultsFilteredByACLs() bool { + if m != nil { + return m.ResultsFilteredByACLs + } + return false +} + // EnterpriseMeta contains metadata that is only used by the Enterprise version // of Consul. type EnterpriseMeta struct { @@ -509,46 +520,49 @@ func init() { func init() { proto.RegisterFile("proto/pbcommon/common.proto", fileDescriptor_a6f5ac44994d718c) } var fileDescriptor_a6f5ac44994d718c = []byte{ - // 620 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0x41, 0x4f, 0xd4, 0x40, - 0x14, 0xde, 0xe2, 0xb2, 0x6e, 0xdf, 0x02, 0xc1, 0x09, 0x31, 0x15, 0x4d, 0x97, 0x6c, 0x8c, 0x21, - 0x44, 0xb7, 0x09, 0xde, 0xf0, 0x04, 0x0b, 0x1a, 0xe2, 0x56, 0x74, 0xc4, 0x90, 0x78, 0x9b, 0xed, - 0xbe, 0xed, 0x4e, 0x6c, 0x3b, 0x75, 0x3a, 0x85, 0xe5, 0x1f, 0x78, 0xf4, 0x48, 0x3c, 0xf9, 0x43, - 0xfc, 0x01, 0x1c, 0x39, 0x7a, 0x42, 0x65, 0xff, 0x81, 0xbf, 0xc0, 0x74, 0x5a, 0xa0, 0x08, 0x18, - 0x3c, 0xed, 0x7e, 0xdf, 0x7c, 0xdf, 0xeb, 0x9b, 0xf7, 0xbe, 0x16, 0xee, 0xc7, 0x52, 0x28, 0xe1, - 0xc4, 0x3d, 0x4f, 0x84, 0xa1, 0x88, 0x9c, 0xfc, 0xa7, 0xad, 0x59, 0x52, 0xcb, 0xd1, 0xbc, 0xed, - 0x0b, 0xe1, 0x07, 0xe8, 0x68, 0xb6, 0x97, 0x0e, 0x9c, 0x7e, 0x2a, 0x99, 0xe2, 0xa7, 0xba, 0xf9, - 0x39, 0x5f, 0xf8, 0x22, 0x2f, 0x94, 0xfd, 0xcb, 0xd9, 0x56, 0x08, 0x26, 0x65, 0x03, 0xb5, 0x19, - 0xf5, 0x71, 0x44, 0x1c, 0x68, 0x74, 0x24, 0x32, 0x85, 0x1a, 0x5a, 0xc6, 0x82, 0xb1, 0x58, 0x5d, - 0x9b, 0xfe, 0x7d, 0xdc, 0x34, 0x7b, 0x38, 0x8a, 0xe5, 0x4a, 0xeb, 0x49, 0x8b, 0x96, 0x15, 0x99, - 0xc1, 0x15, 0x7d, 0x3e, 0xd8, 0xcf, 0x0d, 0x13, 0x57, 0x1a, 0x4a, 0x8a, 0xd6, 0x32, 0xcc, 0x6e, - 0x33, 0xe9, 0xa3, 0x5a, 0x67, 0x8a, 0x79, 0x18, 0x29, 0x94, 0xc4, 0x06, 0x38, 0x47, 0xfa, 0xa1, - 0x26, 0x2d, 0x31, 0xad, 0x25, 0x98, 0xda, 0x91, 0x5c, 0x21, 0xc5, 0x8f, 0x29, 0x26, 0x8a, 0xcc, - 0xc1, 0xe4, 0xb6, 0xf8, 0x80, 0x51, 0x21, 0xcd, 0xc1, 0x4a, 0xf5, 0xd3, 0xd7, 0xa6, 0xd1, 0xda, - 0x81, 0x06, 0x45, 0xd6, 0xff, 0xa7, 0x94, 0x3c, 0x86, 0x3b, 0x99, 0x80, 0x4b, 0xec, 0x88, 0x28, - 0xe1, 0x89, 0xc2, 0x48, 0xe9, 0xde, 0xeb, 0xf4, 0xf2, 0x41, 0x51, 0xf8, 0x4b, 0x15, 0xa6, 0xde, - 0xa4, 0x28, 0xf7, 0xb7, 0xe2, 0x6c, 0xa6, 0xc9, 0x35, 0xa5, 0x1f, 0xc2, 0xb4, 0xcb, 0x23, 0x2d, - 0x2c, 0x8d, 0x84, 0x5e, 0x24, 0xc9, 0x0b, 0x98, 0x72, 0xd9, 0x48, 0x13, 0xdb, 0x3c, 0x44, 0xeb, - 0xd6, 0x82, 0xb1, 0xd8, 0x58, 0xbe, 0xd7, 0xce, 0x37, 0xd8, 0x3e, 0xdd, 0x60, 0x7b, 0xbd, 0xd8, - 0xe0, 0x5a, 0xfd, 0xf0, 0xb8, 0x59, 0x39, 0xf8, 0xd1, 0x34, 0xe8, 0x05, 0x63, 0x36, 0xba, 0xd5, - 0x20, 0x10, 0x7b, 0x6f, 0x15, 0x0b, 0xd0, 0xaa, 0xea, 0x2b, 0x94, 0x98, 0xab, 0x6f, 0x3a, 0x79, - 0xcd, 0x4d, 0xc9, 0x3c, 0xd4, 0xdf, 0x25, 0xd8, 0x61, 0xde, 0x10, 0xad, 0x9a, 0x16, 0x9d, 0x61, - 0xb2, 0x05, 0xb3, 0x2e, 0x1b, 0xe9, 0xaa, 0xa7, 0x5d, 0x59, 0xb7, 0x6f, 0xde, 0xf6, 0x25, 0x33, - 0x79, 0x06, 0x35, 0x97, 0x8d, 0x56, 0x7d, 0xb4, 0xea, 0x37, 0x2f, 0x53, 0x58, 0xc8, 0x23, 0x98, - 0x71, 0xd3, 0x44, 0x51, 0xdc, 0x65, 0x01, 0xef, 0x33, 0x85, 0x96, 0xa9, 0xfb, 0xfd, 0x8b, 0xcd, - 0x06, 0xad, 0x9f, 0xba, 0x39, 0xd8, 0x90, 0x52, 0x48, 0x0b, 0xfe, 0x63, 0xd0, 0x65, 0x23, 0xb9, - 0x0b, 0xb5, 0xe7, 0x3c, 0xc8, 0xf2, 0xd9, 0xd0, 0xeb, 0x2e, 0x50, 0x11, 0x8e, 0x6f, 0x06, 0x98, - 0x7a, 0x29, 0x2e, 0x2a, 0x96, 0x25, 0xa3, 0xf4, 0xfe, 0xd0, 0x1c, 0x90, 0x0d, 0x68, 0x74, 0x59, - 0xa2, 0x3a, 0x22, 0x52, 0xcc, 0xcb, 0xe3, 0x76, 0xc3, 0x4e, 0xca, 0x3e, 0xb2, 0x00, 0x8d, 0x97, - 0x91, 0xd8, 0x8b, 0xba, 0xc8, 0xfa, 0x28, 0x75, 0x72, 0xea, 0xb4, 0x4c, 0x91, 0x25, 0x98, 0x3d, - 0xdb, 0xa9, 0xb7, 0xdf, 0xc5, 0x5d, 0x0c, 0x74, 0x32, 0x4c, 0x7a, 0x89, 0x2f, 0xda, 0xef, 0xc2, - 0xcc, 0x46, 0xf6, 0xa6, 0xc5, 0x92, 0x27, 0xa8, 0xaf, 0xf0, 0x00, 0xcc, 0x57, 0x2c, 0xc4, 0x24, - 0x66, 0x1e, 0x16, 0x01, 0x3f, 0x27, 0xb2, 0xd3, 0xd7, 0x4c, 0x2a, 0xae, 0x43, 0x30, 0x91, 0x9f, - 0x9e, 0x11, 0x6b, 0xdd, 0xc3, 0x5f, 0x76, 0xe5, 0xf0, 0xc4, 0x36, 0x8e, 0x4e, 0x6c, 0xe3, 0xe7, - 0x89, 0x6d, 0x7c, 0x1e, 0xdb, 0x95, 0x83, 0xb1, 0x5d, 0x39, 0x1a, 0xdb, 0x95, 0xef, 0x63, 0xbb, - 0xf2, 0x7e, 0xc9, 0xe7, 0x6a, 0x98, 0xf6, 0xda, 0x9e, 0x08, 0x9d, 0x21, 0x4b, 0x86, 0xdc, 0x13, - 0x32, 0x76, 0x3c, 0x11, 0x25, 0x69, 0xe0, 0x5c, 0xfc, 0xd4, 0xf5, 0x6a, 0x1a, 0x3f, 0xfd, 0x13, - 0x00, 0x00, 0xff, 0xff, 0x9c, 0xf6, 0xbd, 0xcc, 0x03, 0x05, 0x00, 0x00, + // 657 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x54, 0xcd, 0x4e, 0xdb, 0x4a, + 0x18, 0x8d, 0xb9, 0x21, 0xd8, 0x13, 0x40, 0xb9, 0x23, 0xee, 0x95, 0x2f, 0xb7, 0x72, 0x90, 0x55, + 0x55, 0x08, 0xb5, 0xb1, 0x44, 0xbb, 0xa2, 0x2b, 0x12, 0x68, 0x05, 0x8d, 0x4b, 0x3b, 0xa5, 0x42, + 0xea, 0x6e, 0x62, 0x7f, 0x71, 0xac, 0x3a, 0x1e, 0x77, 0x66, 0x0c, 0xc9, 0x1b, 0x74, 0xd9, 0x25, + 0xea, 0xaa, 0x8f, 0xc3, 0x92, 0x65, 0x57, 0xb4, 0x25, 0x6f, 0xd0, 0x07, 0xa8, 0x2a, 0x8f, 0x0d, + 0x98, 0x02, 0x15, 0x5d, 0x25, 0xe7, 0xcc, 0x39, 0xdf, 0x7c, 0x7f, 0x63, 0xf4, 0x7f, 0xc2, 0x99, + 0x64, 0x4e, 0xd2, 0xf3, 0xd8, 0x70, 0xc8, 0x62, 0x27, 0xff, 0x69, 0x29, 0x16, 0xd7, 0x72, 0xb4, + 0x68, 0x05, 0x8c, 0x05, 0x11, 0x38, 0x8a, 0xed, 0xa5, 0x7d, 0xc7, 0x4f, 0x39, 0x95, 0xe1, 0x99, + 0x6e, 0x71, 0x21, 0x60, 0x01, 0xcb, 0x03, 0x65, 0xff, 0x72, 0xd6, 0x1e, 0x22, 0x83, 0xd0, 0xbe, + 0xdc, 0x8a, 0x7d, 0x18, 0x61, 0x07, 0xd5, 0x3b, 0x1c, 0xa8, 0x04, 0x05, 0x4d, 0x6d, 0x49, 0x5b, + 0xae, 0xb6, 0xe7, 0xbe, 0x9f, 0x34, 0x8d, 0x1e, 0x8c, 0x12, 0xbe, 0x66, 0x3f, 0xb0, 0x49, 0x59, + 0x91, 0x19, 0x5c, 0xe6, 0x87, 0xfd, 0x71, 0x6e, 0x98, 0xba, 0xd6, 0x50, 0x52, 0xd8, 0xab, 0xa8, + 0xb1, 0x4b, 0x79, 0x00, 0x72, 0x83, 0x4a, 0xea, 0x41, 0x2c, 0x81, 0x63, 0x0b, 0xa1, 0x0b, 0xa4, + 0x2e, 0x35, 0x48, 0x89, 0xb1, 0x57, 0xd0, 0xec, 0x1e, 0x0f, 0x25, 0x10, 0x78, 0x97, 0x82, 0x90, + 0x78, 0x01, 0x4d, 0xef, 0xb2, 0xb7, 0x10, 0x17, 0xd2, 0x1c, 0xac, 0x55, 0xdf, 0x7f, 0x6a, 0x6a, + 0xf6, 0x1e, 0xaa, 0x13, 0xa0, 0xfe, 0x6f, 0xa5, 0xf8, 0x3e, 0xfa, 0x3b, 0x13, 0x84, 0x1c, 0x3a, + 0x2c, 0x16, 0xa1, 0x90, 0x10, 0x4b, 0x95, 0xbb, 0x4e, 0xae, 0x1e, 0x14, 0x81, 0x3f, 0x56, 0xd1, + 0xec, 0xcb, 0x14, 0xf8, 0x78, 0x27, 0xc9, 0x7a, 0x2a, 0x6e, 0x08, 0x7d, 0x17, 0xcd, 0xb9, 0x61, + 0xac, 0x84, 0xa5, 0x96, 0x90, 0xcb, 0x24, 0x7e, 0x8a, 0x66, 0x5d, 0x3a, 0x52, 0xc4, 0x6e, 0x38, + 0x04, 0xf3, 0xaf, 0x25, 0x6d, 0xb9, 0xbe, 0xfa, 0x5f, 0x2b, 0x9f, 0x60, 0xeb, 0x6c, 0x82, 0xad, + 0x8d, 0x62, 0x82, 0x6d, 0xfd, 0xe8, 0xa4, 0x59, 0x39, 0xfc, 0xd2, 0xd4, 0xc8, 0x25, 0x63, 0xd6, + 0xba, 0xf5, 0x28, 0x62, 0x07, 0xaf, 0x24, 0x8d, 0xc0, 0xac, 0xaa, 0x12, 0x4a, 0xcc, 0xf5, 0x95, + 0x4e, 0xdf, 0x50, 0x29, 0x5e, 0x44, 0xfa, 0x6b, 0x01, 0x1d, 0xea, 0x0d, 0xc0, 0xac, 0x29, 0xd1, + 0x39, 0xc6, 0x3b, 0xa8, 0xe1, 0xd2, 0x91, 0x8a, 0x7a, 0x96, 0x95, 0x39, 0x73, 0xfb, 0xb4, 0xaf, + 0x98, 0xf1, 0x63, 0x54, 0x73, 0xe9, 0x68, 0x3d, 0x00, 0x53, 0xbf, 0x7d, 0x98, 0xc2, 0x82, 0xef, + 0xa1, 0x79, 0x37, 0x15, 0x92, 0xc0, 0x3e, 0x8d, 0x42, 0x9f, 0x4a, 0x30, 0x0d, 0x95, 0xef, 0x2f, + 0x6c, 0xd6, 0x68, 0x75, 0xeb, 0x56, 0x7f, 0x93, 0x73, 0xc6, 0x4d, 0xf4, 0x07, 0x8d, 0x2e, 0x1b, + 0xf1, 0xbf, 0xa8, 0xf6, 0x24, 0x8c, 0xb2, 0xfd, 0xac, 0xab, 0x71, 0x17, 0xa8, 0x58, 0x8e, 0x1f, + 0x1a, 0x32, 0xd4, 0x50, 0x5c, 0x90, 0x34, 0xdb, 0x8c, 0xd2, 0xfb, 0x21, 0x39, 0xc0, 0x9b, 0xa8, + 0xde, 0xa5, 0x42, 0x76, 0x58, 0x2c, 0xa9, 0x97, 0xaf, 0xdb, 0x2d, 0x33, 0x29, 0xfb, 0xf0, 0x12, + 0xaa, 0x3f, 0x8b, 0xd9, 0x41, 0xdc, 0x05, 0xea, 0x03, 0x57, 0x9b, 0xa3, 0x93, 0x32, 0x85, 0x57, + 0x50, 0xe3, 0x7c, 0xa6, 0xde, 0xb8, 0x0b, 0xfb, 0x10, 0xa9, 0xcd, 0x30, 0xc8, 0x15, 0x1e, 0x3f, + 0x42, 0xff, 0x10, 0x10, 0x69, 0x24, 0x45, 0x5e, 0x0f, 0xf8, 0xed, 0xf1, 0x7a, 0xa7, 0x2b, 0xd4, + 0x68, 0x75, 0x72, 0xfd, 0x61, 0x5e, 0xf4, 0x76, 0x55, 0x9f, 0x6e, 0xd4, 0xb6, 0xab, 0x7a, 0xad, + 0x31, 0x63, 0x77, 0xd1, 0xfc, 0x66, 0xf6, 0x56, 0x13, 0x1e, 0x0a, 0x50, 0x4d, 0xb8, 0x83, 0x8c, + 0xe7, 0x74, 0x08, 0x22, 0xa1, 0x1e, 0x14, 0x4f, 0xe4, 0x82, 0xc8, 0x4e, 0x5f, 0x50, 0x2e, 0x43, + 0xb5, 0x46, 0x53, 0xf9, 0xe9, 0x39, 0xd1, 0xee, 0x1e, 0x7d, 0xb3, 0x2a, 0x47, 0xa7, 0x96, 0x76, + 0x7c, 0x6a, 0x69, 0x5f, 0x4f, 0x2d, 0xed, 0xc3, 0xc4, 0xaa, 0x1c, 0x4e, 0xac, 0xca, 0xf1, 0xc4, + 0xaa, 0x7c, 0x9e, 0x58, 0x95, 0x37, 0x2b, 0x41, 0x28, 0x07, 0x69, 0xaf, 0xe5, 0xb1, 0xa1, 0x33, + 0xa0, 0x62, 0x10, 0x7a, 0x8c, 0x27, 0x8e, 0xc7, 0x62, 0x91, 0x46, 0xce, 0xe5, 0x8f, 0x65, 0xaf, + 0xa6, 0xf0, 0xc3, 0x9f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xab, 0xfa, 0x4f, 0xec, 0x45, 0x05, 0x00, + 0x00, } func (m *RaftIndex) Marshal() (dAtA []byte, err error) { @@ -818,6 +832,16 @@ func (m *QueryMeta) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.ResultsFilteredByACLs { + i-- + if m.ResultsFilteredByACLs { + dAtA[i] = 1 + } else { + dAtA[i] = 0 + } + i-- + dAtA[i] = 0x38 + } if len(m.ConsistencyLevel) > 0 { i -= len(m.ConsistencyLevel) copy(dAtA[i:], m.ConsistencyLevel) @@ -1014,6 +1038,9 @@ func (m *QueryMeta) Size() (n int) { if l > 0 { n += 1 + l + sovCommon(uint64(l)) } + if m.ResultsFilteredByACLs { + n += 2 + } return n } @@ -1872,6 +1899,26 @@ func (m *QueryMeta) Unmarshal(dAtA []byte) error { } m.ConsistencyLevel = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ResultsFilteredByACLs", wireType) + } + var v int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCommon + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + m.ResultsFilteredByACLs = bool(v != 0) default: iNdEx = preIndex skippy, err := skipCommon(dAtA[iNdEx:]) diff --git a/proto/pbcommon/common.proto b/proto/pbcommon/common.proto index 2a0d2c6dc..a771fb40d 100644 --- a/proto/pbcommon/common.proto +++ b/proto/pbcommon/common.proto @@ -150,6 +150,14 @@ message QueryMeta { // Having `discovery_max_stale` on the agent can affect whether // the request was served by a leader. string ConsistencyLevel = 4; + + // Reserved for NotModified and Backend. + reserved 5, 6; + + // ResultsFilteredByACLs is true when some of the query's results were + // filtered out by enforcing ACLs. It may be false because nothing was + // removed, or because the endpoint does not yet support this flag. + bool ResultsFilteredByACLs = 7; } // EnterpriseMeta contains metadata that is only used by the Enterprise version @@ -159,4 +167,4 @@ message EnterpriseMeta { string Namespace = 1; // Partition in which the entity exists. string Partition = 2; -} \ No newline at end of file +}