Only synthesize anonymous token in primary DC (#17231)

* Only synthesize anonymous token in primary DC
* Add integration test for wan fed issue
This commit is contained in:
Paul Glass 2023-05-23 09:38:04 -05:00 committed by GitHub
parent 4cae008559
commit 71992b9c3b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 162 additions and 2 deletions

3
.changelog/17231.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
acl: Fix an issue where the anonymous token was synthesized in non-primary datacenters which could cause permission errors when federating clusters with ACL replication enabled.
```

View File

@ -151,7 +151,7 @@ func (s *Server) ResolveIdentityFromToken(token string) (bool, structs.ACLIdenti
} else if aclToken != nil && !aclToken.IsExpired(time.Now()) {
return true, aclToken, nil
}
if aclToken == nil && token == acl.AnonymousTokenSecret {
if aclToken == nil && token == acl.AnonymousTokenSecret && s.InPrimaryDatacenter() {
// synthesize the anonymous token for early use, bootstrapping has not completed
s.insertAnonymousToken()
fallbackId := structs.ACLToken{

View File

@ -0,0 +1,157 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package wanfed
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/api"
"github.com/hashicorp/consul/sdk/testutil/retry"
libcluster "github.com/hashicorp/consul/test/integration/consul-container/libs/cluster"
)
const replicationPolicyRules = `
acl = "write"
operator = "write"
service_prefix "" {
policy = "read"
intentions = "read"
}
`
var retryFuncTimer = &retry.Timer{
Timeout: 60 * time.Second,
Wait: 2 * time.Second,
}
// retryFunc is a helper to retry the given function until it returns a nil
// error. It returns the value from the function on success.
func retryFunc[T any](t *testing.T, f func() (T, error)) T {
var result T
retry.RunWith(retryFuncTimer, t, func(r *retry.R) {
val, err := f()
require.NoError(r, err)
result = val
})
return result
}
// TestWanFed_ReplicationBootstrap checks the setup procedure of two federated
// datacenters with a manual ACL bootstrap step and ACL token replication enabled.
//
// - WAN join two clusters that are not ACL bootstrapped
// and have ACL replication enabled
// - ACL Bootstrap the primary datacenter
// - Configure the replication token in each datacenter
// - Validate ACL replication by creating a token
// and checking it is found in each datacenter.
//
// Added to validate this issue: https://github.com/hashicorp/consul/issues/16620
func TestWanFed_ReplicationBootstrap(t *testing.T) {
cfgFunc := func(c *libcluster.ConfigBuilder) {
c.Set("primary_datacenter", "primary")
c.Set("acl.enabled", true)
c.Set("acl.default_policy", "deny")
c.Set("acl.enable_token_persistence", true)
c.Set("acl.enable_token_replication", true)
}
_, primary := createNonACLBootstrappedCluster(t, "primary", cfgFunc)
_, secondary := createNonACLBootstrappedCluster(t, "secondary", func(c *libcluster.ConfigBuilder) {
cfgFunc(c)
c.Set("retry_join_wan", []string{primary.GetIP()})
})
// ACL bootstrap the primary
rootToken := retryFunc(t, func() (*api.ACLToken, error) {
t.Logf("ACL bootstrap the primary datacenter")
tok, _, err := primary.GetClient().ACL().Bootstrap()
return tok, err
})
agents := []libcluster.Agent{primary, secondary}
for _, agent := range agents {
// Make a new client with the bootstrap token.
_, err := agent.NewClient(rootToken.SecretID, true)
require.NoError(t, err)
t.Logf("Wait for members in %s datacenter", agent.GetDatacenter())
libcluster.WaitForMembers(t, agent.GetClient(), 1)
}
// Create and set the replication token
replicationPolicy := retryFunc(t, func() (*api.ACLPolicy, error) {
t.Logf("Create the replication policy")
p, _, err := primary.GetClient().ACL().PolicyCreate(
&api.ACLPolicy{
Name: "consul-server-replication",
Rules: replicationPolicyRules,
},
nil,
)
return p, err
})
replicationToken := retryFunc(t, func() (*api.ACLToken, error) {
t.Logf("Create the replication policy")
tok, _, err := primary.GetClient().ACL().TokenCreate(
&api.ACLToken{
Description: "Consul server replication token",
Policies: []*api.ACLLink{{ID: replicationPolicy.ID}},
},
nil,
)
return tok, err
})
// Set the replication token in the secondary
retryFunc(t, func() (any, error) {
t.Logf("Set the replication token in the %s datacenter", secondary.GetDatacenter())
return secondary.GetClient().Agent().UpdateReplicationACLToken(replicationToken.SecretID, nil)
})
// Double check that replication happens after setting the replication token.
// - Create a token
// - Check the token is found in each datacenter
createdToken := retryFunc(t, func() (*api.ACLToken, error) {
t.Logf("Create a test token to validate ACL replication in the secondary datacenter")
tok, _, err := secondary.GetClient().ACL().TokenCreate(&api.ACLToken{
ServiceIdentities: []*api.ACLServiceIdentity{{
ServiceName: "test-svc",
}},
}, nil)
return tok, err
})
for _, agent := range agents {
tok := retryFunc(t, func() (*api.ACLToken, error) {
t.Logf("Check the test token is found in the %s datacenter", agent.GetDatacenter())
tok, _, err := agent.GetClient().ACL().TokenRead(createdToken.AccessorID, nil)
return tok, err
})
require.NotNil(t, tok)
require.Equal(t, tok.AccessorID, createdToken.AccessorID)
require.Equal(t, tok.SecretID, createdToken.SecretID)
}
}
func createNonACLBootstrappedCluster(t *testing.T, dc string, f func(c *libcluster.ConfigBuilder)) (*libcluster.Cluster, libcluster.Agent) {
ctx := libcluster.NewBuildContext(t, libcluster.BuildOptions{Datacenter: dc})
conf := libcluster.NewConfigBuilder(ctx).Advanced(f)
cluster, err := libcluster.New(t, []libcluster.Config{*conf.ToAgentConfig(t)})
require.NoError(t, err)
client := cluster.Agents[0].GetClient()
libcluster.WaitForLeader(t, cluster, client)
// Note: Do not wait for members yet (not ACL bootstrapped so no perms with a default deny)
agent, err := cluster.Leader()
require.NoError(t, err)
return cluster, agent
}

View File

@ -1,7 +1,7 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package peering
package wanfed
import (
"context"