diff --git a/agent/consul/acl.go b/agent/consul/acl.go index 471a9b76fd..1dc2030d65 100644 --- a/agent/consul/acl.go +++ b/agent/consul/acl.go @@ -417,6 +417,9 @@ func (r *ACLResolver) fetchAndCacheIdentityFromToken(token string, cached *struc if resp.Token == nil { r.cache.PutIdentity(cacheID, nil) return nil, acl.ErrNotFound + } else if resp.Token.Local && r.config.Datacenter != resp.SourceDatacenter { + r.cache.PutIdentity(cacheID, nil) + return nil, acl.PermissionDeniedError{Cause: fmt.Sprintf("This is a local token in datacenter %q", resp.SourceDatacenter)} } else { r.cache.PutIdentity(cacheID, resp.Token) return resp.Token, nil diff --git a/agent/consul/acl_endpoint.go b/agent/consul/acl_endpoint.go index 1c15677bf2..835e7bf442 100644 --- a/agent/consul/acl_endpoint.go +++ b/agent/consul/acl_endpoint.go @@ -260,6 +260,7 @@ func (a *ACL) TokenRead(args *structs.ACLTokenGetRequest, reply *structs.ACLToke } reply.Index, reply.Token = index, token + reply.SourceDatacenter = args.Datacenter return nil }) } diff --git a/agent/consul/acl_test.go b/agent/consul/acl_test.go index 55020b36a4..c49ac1a057 100644 --- a/agent/consul/acl_test.go +++ b/agent/consul/acl_test.go @@ -3703,3 +3703,49 @@ func TestDedupeServiceIdentities(t *testing.T) { }) } } +func TestACL_LocalToken(t *testing.T) { + t.Run("local token in same dc", func(t *testing.T) { + d := &ACLResolverTestDelegate{ + datacenter: "dc1", + tokenReadFn: func(_ *structs.ACLTokenGetRequest, reply *structs.ACLTokenResponse) error { + reply.Token = &structs.ACLToken{Local: true} + // different dc + reply.SourceDatacenter = "dc1" + return nil + }, + } + r := newTestACLResolver(t, d, nil) + _, err := r.fetchAndCacheIdentityFromToken("", nil) + require.NoError(t, err) + }) + + t.Run("non local token in remote dc", func(t *testing.T) { + d := &ACLResolverTestDelegate{ + datacenter: "dc1", + tokenReadFn: func(_ *structs.ACLTokenGetRequest, reply *structs.ACLTokenResponse) error { + reply.Token = &structs.ACLToken{Local: false} + // different dc + reply.SourceDatacenter = "remote" + return nil + }, + } + r := newTestACLResolver(t, d, nil) + _, err := r.fetchAndCacheIdentityFromToken("", nil) + require.NoError(t, err) + }) + + t.Run("local token in remote dc", func(t *testing.T) { + d := &ACLResolverTestDelegate{ + datacenter: "dc1", + tokenReadFn: func(_ *structs.ACLTokenGetRequest, reply *structs.ACLTokenResponse) error { + reply.Token = &structs.ACLToken{Local: true} + // different dc + reply.SourceDatacenter = "remote" + return nil + }, + } + r := newTestACLResolver(t, d, nil) + _, err := r.fetchAndCacheIdentityFromToken("", nil) + require.Equal(t, acl.PermissionDeniedError{Cause: "This is a local token in datacenter \"remote\""}, err) + }) +} diff --git a/agent/structs/acl.go b/agent/structs/acl.go index 769f66e731..0045bef72d 100644 --- a/agent/structs/acl.go +++ b/agent/structs/acl.go @@ -1258,8 +1258,9 @@ type ACLTokenBootstrapRequest struct { // ACLTokenResponse returns a single Token + metadata type ACLTokenResponse struct { - Token *ACLToken - Redacted bool // whether the token's secret was redacted + Token *ACLToken + Redacted bool // whether the token's secret was redacted + SourceDatacenter string QueryMeta }