Skip to content

Commit d6001f2

Browse files
hc-github-team-consul-coresujay-hashicorpsindhu-shree-1
authored
Backport of [SECVULN-8636] Filter ACL for anonymous token along with empty token into release/1.21.x (#22678)
* backport of commit 6af949c * [SECVULN-8636] Filter ACL for anonymous token along with empty token (#22534) * (agent/rpc) update SetQueryMetadata to check for anonymous token * Extract functionality into original method * Improve tests and remove regression * Add test for anonymous token, fix test for authenticated calls * Add changelog file * Update test for anonymous token in intention-endpoint * Keep anonymous variables package-private * Restructure the maskResults function * Fix lint issue --------- Co-authored-by: Sujay Kumar Suman <sujaykumar.suman@hashicorp.com> --------- Co-authored-by: Sujay Kumar Suman <sujaykumar.suman@hashicorp.com> Co-authored-by: Sindhu Shree <sindhu20091@gmail.com>
1 parent 38febd6 commit d6001f2

5 files changed

Lines changed: 77 additions & 23 deletions

File tree

.changelog/22534.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:security
2+
agent: Fix a security vulnerability to filter out anonymous tokens along with empty tokens when setting the Results-Filtered-By-ACLs header
3+
```

agent/consul/acl_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,26 @@ func testIdentityForToken(token string) (bool, structs.ACLIdentity, error) {
204204
},
205205
},
206206
}, nil
207+
case "authenticated":
208+
return true, &structs.ACLToken{
209+
AccessorID: "some-id",
210+
SecretID: "some-id",
211+
Policies: []structs.ACLTokenPolicyLink{
212+
{
213+
ID: "node-wr",
214+
},
215+
},
216+
}, nil
217+
case "anonymous":
218+
return true, &structs.ACLToken{
219+
AccessorID: "00000000-0000-0000-0000-000000000002",
220+
SecretID: "anonymous",
221+
Policies: []structs.ACLTokenPolicyLink{
222+
{
223+
ID: "node-wr",
224+
},
225+
},
226+
}, nil
207227
default:
208228
return true, nil, acl.ErrNotFound
209229
}

agent/consul/intention_endpoint_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1773,7 +1773,7 @@ func TestIntentionList_acl(t *testing.T) {
17731773
var resp structs.IndexedIntentions
17741774
require.NoError(t, msgpackrpc.CallWithCodec(codec, "Intention.List", req, &resp))
17751775
require.Len(t, resp.Intentions, 0)
1776-
require.True(t, resp.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
1776+
require.False(t, resp.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
17771777
})
17781778
}
17791779

agent/consul/rpc.go

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ const (
9292
// value is ever reached. However, it prevents us from blocking
9393
// the requesting goroutine forever.
9494
enqueueLimit = 30 * time.Second
95+
96+
// AnonymousAccessorID is the accessor ID for anonymous token.
97+
anonymousAccessorID = "00000000-0000-0000-0000-000000000002"
98+
99+
// AnonymousSecretID is the secret ID for anonymous token
100+
anonymousSecretID = "anonymous"
95101
)
96102

97103
var ErrChunkingResubmit = errors.New("please resubmit call for rechunking")
@@ -1045,7 +1051,7 @@ func (s *Server) SetQueryMeta(m blockingquery.ResponseMeta, token string) {
10451051
m.SetLastContact(time.Since(s.raft.LastContact()))
10461052
m.SetKnownLeader(s.raft.Leader() != "")
10471053
}
1048-
maskResultsFilteredByACLs(token, m)
1054+
maskResultsFilteredByACLs(token, m, s)
10491055

10501056
// Always set a non-zero QueryMeta.Index. Generally we expect the
10511057
// QueryMeta.Index to be set to structs.RaftIndex.ModifyIndex. If the query
@@ -1136,18 +1142,24 @@ func (s *Server) RPCQueryTimeout(queryTimeout time.Duration) time.Duration {
11361142
//
11371143
// Notes:
11381144
//
1139-
// - The definition of "unauthenticated" here is incomplete, as it doesn't
1140-
// account for the fact that operators can modify the anonymous token with
1141-
// custom policies, or set namespace default policies. As these scenarios
1142-
// are less common and this flag is a best-effort UX improvement, we think
1143-
// the trade-off for reduced complexity is acceptable.
1144-
//
11451145
// - This method assumes that the given token has already been validated (and
1146-
// will only check whether it is blank or not). It's a safe assumption because
1146+
// will only check whether it is blank or anonymous). It's a safe assumption because
11471147
// ResultsFilteredByACLs is only set to try when applying the already-resolved
11481148
// token's policies.
1149-
func maskResultsFilteredByACLs(token string, meta blockingQueryResponseMeta) {
1149+
func maskResultsFilteredByACLs(token string, m blockingQueryResponseMeta, s *Server) {
11501150
if token == "" {
1151-
meta.SetResultsFilteredByACLs(false)
1151+
m.SetResultsFilteredByACLs(false)
1152+
return
1153+
}
1154+
1155+
identity, err := s.resolveIdentityFromToken(token)
1156+
if err != nil {
1157+
s.rpcLogger().Error("Failed to resolve identity from token", "err", err)
1158+
m.SetResultsFilteredByACLs(false)
1159+
return
1160+
}
1161+
1162+
if identity.ID() == anonymousAccessorID && identity.SecretToken() == anonymousSecretID {
1163+
m.SetResultsFilteredByACLs(false)
11521164
}
11531165
}

agent/consul/rpc_test.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,6 @@ import (
3939
"github.com/hashicorp/consul/agent/structs"
4040
tokenStore "github.com/hashicorp/consul/agent/token"
4141
"github.com/hashicorp/consul/api"
42-
"github.com/hashicorp/consul/lib"
4342
"github.com/hashicorp/consul/proto/private/pbsubscribe"
4443
"github.com/hashicorp/consul/sdk/testutil"
4544
"github.com/hashicorp/consul/sdk/testutil/retry"
@@ -234,9 +233,17 @@ func (m *MockSink) Close() error {
234233
// other blocking query tests reside in blockingquery_test.go in the blockingquery package.
235234
func TestServer_blockingQuery(t *testing.T) {
236235
t.Parallel()
237-
_, s := testServerWithConfig(t)
236+
_, s := testServerWithConfig(t, testServerACLConfig)
237+
delegate := ACLResolverTestDelegate{
238+
enabled: true,
239+
datacenter: "dc1",
240+
}
241+
delegate.tokenReadFn = delegate.defaultTokenReadFn(errRPC)
242+
r := newTestACLResolver(t, &delegate, nil)
243+
s.ACLResolver = r
238244

239245
t.Run("ResultsFilteredByACLs is reset for unauthenticated calls", func(t *testing.T) {
246+
// empty token
240247
opts := structs.QueryOptions{
241248
Token: "",
242249
}
@@ -249,24 +256,36 @@ func TestServer_blockingQuery(t *testing.T) {
249256
err := s.blockingQuery(&opts, &meta, fn)
250257
require.NoError(t, err)
251258
require.False(t, meta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be reset for unauthenticated calls")
252-
})
253259

254-
t.Run("ResultsFilteredByACLs is honored for authenticated calls", func(t *testing.T) {
255-
token, err := lib.GenerateUUID(nil)
260+
// anonymous token
261+
anonOpts := structs.QueryOptions{
262+
Token: "anonymous", // secret id of anonymous token
263+
}
264+
var anonMeta structs.QueryMeta
265+
anonFn := func(_ memdb.WatchSet, _ *state.Store) error {
266+
anonMeta.ResultsFilteredByACLs = true
267+
return nil
268+
}
269+
270+
err = s.blockingQuery(&anonOpts, &anonMeta, anonFn)
256271
require.NoError(t, err)
272+
require.False(t, anonMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be reset for unauthenticated calls")
273+
})
257274

258-
opts := structs.QueryOptions{
259-
Token: token,
275+
t.Run("ResultsFilteredByACLs is honored for authenticated calls", func(t *testing.T) {
276+
delegate.localTokens = true
277+
authOpts := structs.QueryOptions{
278+
Token: "authenticated",
260279
}
261-
var meta structs.QueryMeta
262-
fn := func(_ memdb.WatchSet, _ *state.Store) error {
263-
meta.ResultsFilteredByACLs = true
280+
var authMeta structs.QueryMeta
281+
authFn := func(_ memdb.WatchSet, _ *state.Store) error {
282+
authMeta.ResultsFilteredByACLs = true
264283
return nil
265284
}
266285

267-
err = s.blockingQuery(&opts, &meta, fn)
286+
err := s.blockingQuery(&authOpts, &authMeta, authFn)
268287
require.NoError(t, err)
269-
require.True(t, meta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be honored for authenticated calls")
288+
require.True(t, authMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be honored for authenticated calls")
270289
})
271290
}
272291

0 commit comments

Comments
 (0)