* dns token

fix whitespace for docs and comments

fix test cases

fix test cases

remove tabs in help text

Add changelog

Peering dns test

Peering dns test

Partial implementation of Peered DNS test

Swap to new topology lib

expose dns port for integration tests on client

remove partial test implementation

remove extra port exposure

remove changelog from the ent pr

Add dns token to set-agent-token switch

Add enterprise golden file

Use builtin/dns template in tests

Update ent dns policy

Update ent dns template test

remove local gen certs

fix templated policy specs

* add changelog

* go mod tidy
This commit is contained in:
John Landa 2023-09-20 15:50:06 -06:00 committed by GitHub
parent 0236c48369
commit 9eaa8eb026
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 305 additions and 108 deletions

3
.changelog/17936.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
acl: Add new `acl.tokens.dns` config field which specifies the token used implicitly during dns checks.
```

View File

@ -1531,6 +1531,9 @@ func (s *HTTPHandlers) AgentToken(resp http.ResponseWriter, req *http.Request) (
case "config_file_service_registration":
s.agent.tokens.UpdateConfigFileRegistrationToken(args.Token, token_store.TokenSourceAPI)
case "dns_token", "dns":
s.agent.tokens.UpdateDNSToken(args.Token, token_store.TokenSourceAPI)
default:
return HTTPError{StatusCode: http.StatusNotFound, Reason: fmt.Sprintf("Token %q is unknown", target)}
}

View File

@ -882,6 +882,7 @@ func (b *builder) build() (rt RuntimeConfig, err error) {
ACLAgentRecoveryToken: stringVal(c.ACL.Tokens.AgentRecovery),
ACLReplicationToken: stringVal(c.ACL.Tokens.Replication),
ACLConfigFileRegistrationToken: stringVal(c.ACL.Tokens.ConfigFileRegistration),
ACLDNSToken: stringVal(c.ACL.Tokens.DNS),
},
// Autopilot

View File

@ -778,6 +778,7 @@ type Tokens struct {
Default *string `mapstructure:"default"`
Agent *string `mapstructure:"agent"`
ConfigFileRegistration *string `mapstructure:"config_file_service_registration"`
DNS *string `mapstructure:"dns"`
// Enterprise Only
ManagedServiceProvider []ServiceProviderToken `mapstructure:"managed_service_provider"`

View File

@ -17,6 +17,7 @@
"ACLAgentRecoveryToken": "hidden",
"ACLAgentToken": "hidden",
"ACLConfigFileRegistrationToken": "hidden",
"ACLDNSToken": "hidden",
"ACLDefaultToken": "hidden",
"ACLReplicationToken": "hidden",
"DataDir": "",

View File

@ -416,7 +416,7 @@ func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) {
args := structs.DCSpecificRequest{
Datacenter: datacenter,
QueryOptions: structs.QueryOptions{
Token: d.agent.tokens.UserToken(),
Token: d.coalesceDNSToken(),
AllowStale: cfg.AllowStale,
},
}
@ -452,7 +452,7 @@ func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) {
sargs := structs.ServiceSpecificRequest{
Datacenter: datacenter,
QueryOptions: structs.QueryOptions{
Token: d.agent.tokens.UserToken(),
Token: d.coalesceDNSToken(),
AllowStale: cfg.AllowStale,
},
ServiceAddress: serviceAddress,
@ -513,7 +513,7 @@ func (d *DNSServer) handleQuery(resp dns.ResponseWriter, req *dns.Msg) {
cfg := d.config.Load().(*dnsConfig)
// Setup the message response
// Set up the message response
m := new(dns.Msg)
m.SetReply(req)
m.Compress = !cfg.DisableCompression
@ -875,7 +875,7 @@ func (d *DNSServer) dispatch(remoteAddr net.Addr, req, resp *dns.Msg, maxRecursi
ServiceName: queryParts[len(queryParts)-1],
EnterpriseMeta: locality.EnterpriseMeta,
QueryOptions: structs.QueryOptions{
Token: d.agent.tokens.UserToken(),
Token: d.coalesceDNSToken(),
},
}
if args.PeerName == "" {
@ -1093,7 +1093,7 @@ func (d *DNSServer) nodeLookup(cfg *dnsConfig, lookup nodeLookup, req, resp *dns
PeerName: lookup.PeerName,
Node: lookup.Node,
QueryOptions: structs.QueryOptions{
Token: d.agent.tokens.UserToken(),
Token: d.coalesceDNSToken(),
AllowStale: cfg.AllowStale,
},
EnterpriseMeta: lookup.EnterpriseMeta,
@ -1425,7 +1425,7 @@ func (d *DNSServer) lookupServiceNodes(cfg *dnsConfig, lookup serviceLookup) (st
ServiceTags: serviceTags,
TagFilter: lookup.Tag != "",
QueryOptions: structs.QueryOptions{
Token: d.agent.tokens.UserToken(),
Token: d.coalesceDNSToken(),
AllowStale: cfg.AllowStale,
MaxAge: cfg.CacheMaxAge,
UseCache: cfg.UseCache,
@ -1503,7 +1503,7 @@ func (d *DNSServer) preparedQueryLookup(cfg *dnsConfig, datacenter, query string
Datacenter: datacenter,
QueryIDOrName: query,
QueryOptions: structs.QueryOptions{
Token: d.agent.tokens.UserToken(),
Token: d.coalesceDNSToken(),
AllowStale: cfg.AllowStale,
MaxAge: cfg.CacheMaxAge,
},
@ -2172,3 +2172,11 @@ func (d *DNSServer) resolveCNAME(cfg *dnsConfig, name string, maxRecursionLevel
d.logger.Error("all resolvers failed for name", "name", name)
return nil
}
func (d *DNSServer) coalesceDNSToken() string {
if d.agent.tokens.DNSToken() != "" {
return d.agent.tokens.DNSToken()
} else {
return d.agent.tokens.UserToken()
}
}

View File

@ -10,11 +10,12 @@ import (
"context"
"testing"
"github.com/miekg/dns"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/structs"
"github.com/hashicorp/consul/testrpc"
"github.com/miekg/dns"
"github.com/stretchr/testify/require"
)
func TestDNS_CE_PeeredServices(t *testing.T) {

View File

@ -6310,6 +6310,22 @@ func TestDNS_ServiceLookup_SRV_RFC_TCP_Default(t *testing.T) {
}
func initDNSToken(t *testing.T, rpc RPC) {
t.Helper()
reqToken := structs.ACLTokenSetRequest{
Datacenter: "dc1",
ACLToken: structs.ACLToken{
SecretID: "279d4735-f8ca-4d48-b5cc-c00a9713bbf8",
Policies: nil,
TemplatedPolicies: []*structs.ACLTemplatedPolicy{{TemplateName: "builtin/dns"}},
},
WriteRequest: structs.WriteRequest{Token: "root"},
}
err := rpc.RPC(context.Background(), "ACL.TokenSet", &reqToken, &structs.ACLToken{})
require.NoError(t, err)
}
func TestDNS_ServiceLookup_FilterACL(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
@ -6322,10 +6338,11 @@ func TestDNS_ServiceLookup_FilterACL(t *testing.T) {
}{
{"root", 1},
{"anonymous", 0},
{"dns", 1},
}
for _, tt := range tests {
t.Run("ACLToken == "+tt.token, func(t *testing.T) {
a := NewTestAgent(t, `
hcl := `
primary_datacenter = "dc1"
acl {
@ -6335,13 +6352,34 @@ func TestDNS_ServiceLookup_FilterACL(t *testing.T) {
tokens {
initial_management = "root"
default = "`+tt.token+`"
`
if tt.token == "dns" {
// Create a UUID for dns token since it doesn't have an alias
dnsToken := "279d4735-f8ca-4d48-b5cc-c00a9713bbf8"
hcl = hcl + `
default = "anonymous"
dns = "` + dnsToken + `"
`
} else {
hcl = hcl + `
default = "` + tt.token + `"
`
}
hcl = hcl + `
}
}
`)
`
a := NewTestAgent(t, hcl)
defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1")
if tt.token == "dns" {
initDNSToken(t, a)
}
// Register a service
args := &structs.RegisterRequest{
Datacenter: "dc1",
@ -6373,6 +6411,7 @@ func TestDNS_ServiceLookup_FilterACL(t *testing.T) {
})
}
}
func TestDNS_ServiceLookup_MetaTXT(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")

View File

@ -529,6 +529,7 @@ type TestACLConfigParams struct {
DefaultToken string
AgentRecoveryToken string
ReplicationToken string
DNSToken string
EnableTokenReplication bool
}
@ -547,7 +548,8 @@ func (p *TestACLConfigParams) HasConfiguredTokens() bool {
p.AgentToken != "" ||
p.DefaultToken != "" ||
p.AgentRecoveryToken != "" ||
p.ReplicationToken != ""
p.ReplicationToken != "" ||
p.DNSToken != ""
}
func TestACLConfigNew() string {
@ -557,6 +559,7 @@ func TestACLConfigNew() string {
InitialManagementToken: "root",
AgentToken: "root",
AgentRecoveryToken: "towel",
DNSToken: "dns",
})
}

View File

@ -26,6 +26,7 @@ type Config struct {
ACLAgentRecoveryToken string
ACLReplicationToken string
ACLConfigFileRegistrationToken string
ACLDNSToken string
EnterpriseConfig
}
@ -77,6 +78,7 @@ type persistedTokens struct {
Default string `json:"default,omitempty"`
Agent string `json:"agent,omitempty"`
ConfigFileRegistration string `json:"config_file_service_registration,omitempty"`
DNS string `json:"dns,omitempty"`
}
type fileStore struct {
@ -144,6 +146,16 @@ func loadTokens(s *Store, cfg Config, tokens persistedTokens, logger Logger) {
s.UpdateConfigFileRegistrationToken(cfg.ACLConfigFileRegistrationToken, TokenSourceConfig)
}
if tokens.DNS != "" {
s.UpdateDNSToken(tokens.DNS, TokenSourceAPI)
if cfg.ACLDNSToken != "" {
logger.Warn("\"dns\" token present in both the configuration and persisted token store, using the persisted token")
}
} else {
s.UpdateDNSToken(cfg.ACLDNSToken, TokenSourceConfig)
}
loadEnterpriseTokens(s, cfg)
}
@ -206,6 +218,10 @@ func (p *fileStore) saveToFile(s *Store) error {
tokens.ConfigFileRegistration = tok
}
if tok, source := s.DNSTokenAndSource(); tok != "" && source == TokenSourceAPI {
tokens.DNS = tok
}
data, err := json.Marshal(tokens)
if err != nil {
p.logger.Warn("failed to persist tokens", "error", err)

View File

@ -8,9 +8,10 @@ import (
"path/filepath"
"testing"
"github.com/hashicorp/consul/sdk/testutil"
"github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/require"
"github.com/hashicorp/consul/sdk/testutil"
)
func TestStore_Load(t *testing.T) {
@ -27,6 +28,7 @@ func TestStore_Load(t *testing.T) {
ACLDefaultToken: "charlie",
ACLReplicationToken: "delta",
ACLConfigFileRegistrationToken: "echo",
ACLDNSToken: "foxtrot",
}
require.NoError(t, store.Load(cfg, logger))
require.Equal(t, "alfa", store.AgentToken())
@ -34,62 +36,69 @@ func TestStore_Load(t *testing.T) {
require.Equal(t, "charlie", store.UserToken())
require.Equal(t, "delta", store.ReplicationToken())
require.Equal(t, "echo", store.ConfigFileRegistrationToken())
require.Equal(t, "foxtrot", store.DNSToken())
})
t.Run("updated from Config", func(t *testing.T) {
cfg := Config{
DataDir: dataDir,
ACLDefaultToken: "echo",
ACLAgentToken: "foxtrot",
ACLAgentRecoveryToken: "golf",
ACLReplicationToken: "hotel",
ACLConfigFileRegistrationToken: "india",
ACLDefaultToken: "sierra",
ACLAgentToken: "tango",
ACLAgentRecoveryToken: "uniform",
ACLReplicationToken: "victor",
ACLConfigFileRegistrationToken: "xray",
ACLDNSToken: "zulu",
}
// ensures no error for missing persisted tokens file
require.NoError(t, store.Load(cfg, logger))
require.Equal(t, "echo", store.UserToken())
require.Equal(t, "foxtrot", store.AgentToken())
require.Equal(t, "golf", store.AgentRecoveryToken())
require.Equal(t, "hotel", store.ReplicationToken())
require.Equal(t, "india", store.ConfigFileRegistrationToken())
require.Equal(t, "sierra", store.UserToken())
require.Equal(t, "tango", store.AgentToken())
require.Equal(t, "uniform", store.AgentRecoveryToken())
require.Equal(t, "victor", store.ReplicationToken())
require.Equal(t, "xray", store.ConfigFileRegistrationToken())
require.Equal(t, "zulu", store.DNSToken())
})
t.Run("with persisted tokens", func(t *testing.T) {
cfg := Config{
DataDir: dataDir,
ACLDefaultToken: "echo",
ACLAgentToken: "foxtrot",
ACLAgentRecoveryToken: "golf",
ACLReplicationToken: "hotel",
ACLConfigFileRegistrationToken: "delta",
ACLDefaultToken: "alpha",
ACLAgentToken: "bravo",
ACLAgentRecoveryToken: "charlie",
ACLReplicationToken: "delta",
ACLConfigFileRegistrationToken: "echo",
ACLDNSToken: "foxtrot",
}
tokens := `{
"agent" : "india",
"agent_recovery" : "juliett",
"default": "kilo",
"replication": "lima",
"config_file_service_registration": "mike"
"agent" : "golf",
"agent_recovery" : "hotel",
"default": "india",
"replication": "juliet",
"config_file_service_registration": "kilo",
"dns": "lima"
}`
require.NoError(t, os.WriteFile(tokenFile, []byte(tokens), 0600))
require.NoError(t, store.Load(cfg, logger))
// no updates since token persistence is not enabled
require.Equal(t, "echo", store.UserToken())
require.Equal(t, "foxtrot", store.AgentToken())
require.Equal(t, "golf", store.AgentRecoveryToken())
require.Equal(t, "hotel", store.ReplicationToken())
require.Equal(t, "delta", store.ConfigFileRegistrationToken())
require.Equal(t, "alpha", store.UserToken())
require.Equal(t, "bravo", store.AgentToken())
require.Equal(t, "charlie", store.AgentRecoveryToken())
require.Equal(t, "delta", store.ReplicationToken())
require.Equal(t, "echo", store.ConfigFileRegistrationToken())
require.Equal(t, "foxtrot", store.DNSToken())
cfg.EnablePersistence = true
require.NoError(t, store.Load(cfg, logger))
require.Equal(t, "india", store.AgentToken())
require.Equal(t, "juliett", store.AgentRecoveryToken())
require.Equal(t, "kilo", store.UserToken())
require.Equal(t, "lima", store.ReplicationToken())
require.Equal(t, "mike", store.ConfigFileRegistrationToken())
require.Equal(t, "golf", store.AgentToken())
require.Equal(t, "hotel", store.AgentRecoveryToken())
require.Equal(t, "india", store.UserToken())
require.Equal(t, "juliet", store.ReplicationToken())
require.Equal(t, "kilo", store.ConfigFileRegistrationToken())
require.Equal(t, "lima", store.DNSToken())
// check store persistence was enabled
require.NotNil(t, store.persistence)
@ -115,7 +124,8 @@ func TestStore_Load(t *testing.T) {
"agent_recovery" : "november",
"default": "oscar",
"replication" : "papa",
"config_file_service_registration" : "lima"
"config_file_service_registration" : "lima",
"dns": "kilo"
}`
cfg := Config{
@ -126,6 +136,7 @@ func TestStore_Load(t *testing.T) {
ACLAgentRecoveryToken: "sierra",
ACLReplicationToken: "tango",
ACLConfigFileRegistrationToken: "uniform",
ACLDNSToken: "victor",
}
require.NoError(t, os.WriteFile(tokenFile, []byte(tokens), 0600))
@ -136,43 +147,48 @@ func TestStore_Load(t *testing.T) {
require.Equal(t, "oscar", store.UserToken())
require.Equal(t, "papa", store.ReplicationToken())
require.Equal(t, "lima", store.ConfigFileRegistrationToken())
require.Equal(t, "kilo", store.DNSToken())
})
t.Run("with some persisted tokens", func(t *testing.T) {
tokens := `{
"agent" : "uniform",
"agent_recovery" : "victor"
"agent" : "xray",
"agent_recovery" : "zulu"
}`
cfg := Config{
EnablePersistence: true,
DataDir: dataDir,
ACLDefaultToken: "whiskey",
ACLAgentToken: "xray",
ACLAgentRecoveryToken: "yankee",
ACLReplicationToken: "zulu",
ACLConfigFileRegistrationToken: "victor",
ACLDefaultToken: "alpha",
ACLAgentToken: "bravo",
ACLAgentRecoveryToken: "charlie",
ACLReplicationToken: "delta",
ACLConfigFileRegistrationToken: "echo",
ACLDNSToken: "foxtrot",
}
require.NoError(t, os.WriteFile(tokenFile, []byte(tokens), 0600))
require.NoError(t, store.Load(cfg, logger))
require.Equal(t, "uniform", store.AgentToken())
require.Equal(t, "victor", store.AgentRecoveryToken())
require.Equal(t, "whiskey", store.UserToken())
require.Equal(t, "zulu", store.ReplicationToken())
require.Equal(t, "victor", store.ConfigFileRegistrationToken())
require.Equal(t, "xray", store.AgentToken())
require.Equal(t, "zulu", store.AgentRecoveryToken())
require.Equal(t, "alpha", store.UserToken())
require.Equal(t, "delta", store.ReplicationToken())
require.Equal(t, "echo", store.ConfigFileRegistrationToken())
require.Equal(t, "foxtrot", store.DNSToken())
})
t.Run("persisted file contains invalid data", func(t *testing.T) {
cfg := Config{
EnablePersistence: true,
DataDir: dataDir,
ACLDefaultToken: "one",
ACLAgentToken: "two",
ACLAgentRecoveryToken: "three",
ACLReplicationToken: "four",
ACLConfigFileRegistrationToken: "five",
ACLDefaultToken: "alpha",
ACLAgentToken: "bravo",
ACLAgentRecoveryToken: "charlie",
ACLReplicationToken: "delta",
ACLConfigFileRegistrationToken: "echo",
ACLDNSToken: "foxtrot",
}
require.NoError(t, os.WriteFile(tokenFile, []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}, 0600))
@ -180,11 +196,12 @@ func TestStore_Load(t *testing.T) {
require.Error(t, err)
require.Contains(t, err.Error(), "failed to decode tokens file")
require.Equal(t, "one", store.UserToken())
require.Equal(t, "two", store.AgentToken())
require.Equal(t, "three", store.AgentRecoveryToken())
require.Equal(t, "four", store.ReplicationToken())
require.Equal(t, "five", store.ConfigFileRegistrationToken())
require.Equal(t, "alpha", store.UserToken())
require.Equal(t, "bravo", store.AgentToken())
require.Equal(t, "charlie", store.AgentRecoveryToken())
require.Equal(t, "delta", store.ReplicationToken())
require.Equal(t, "echo", store.ConfigFileRegistrationToken())
require.Equal(t, "foxtrot", store.DNSToken())
})
t.Run("persisted file contains invalid json", func(t *testing.T) {
@ -194,8 +211,9 @@ func TestStore_Load(t *testing.T) {
ACLDefaultToken: "alfa",
ACLAgentToken: "bravo",
ACLAgentRecoveryToken: "charlie",
ACLReplicationToken: "foxtrot",
ACLConfigFileRegistrationToken: "golf",
ACLReplicationToken: "delta",
ACLConfigFileRegistrationToken: "echo",
ACLDNSToken: "foxtrot",
}
require.NoError(t, os.WriteFile(tokenFile, []byte("[1,2,3]"), 0600))
@ -206,23 +224,31 @@ func TestStore_Load(t *testing.T) {
require.Equal(t, "alfa", store.UserToken())
require.Equal(t, "bravo", store.AgentToken())
require.Equal(t, "charlie", store.AgentRecoveryToken())
require.Equal(t, "foxtrot", store.ReplicationToken())
require.Equal(t, "golf", store.ConfigFileRegistrationToken())
require.Equal(t, "delta", store.ReplicationToken())
require.Equal(t, "echo", store.ConfigFileRegistrationToken())
require.Equal(t, "foxtrot", store.DNSToken())
})
}
func TestStore_WithPersistenceLock(t *testing.T) {
// ACLDefaultToken: alpha --> sierra
// ACLAgentToken: bravo --> tango
// ACLAgentRecoveryToken: charlie --> uniform
// ACLReplicationToken: delta --> victor
// ACLConfigFileRegistrationToken: echo --> xray
// ACLDNSToken: foxtrot --> zulu
setupStore := func() (string, *Store) {
dataDir := testutil.TempDir(t, "datadir")
store := new(Store)
cfg := Config{
EnablePersistence: true,
DataDir: dataDir,
ACLDefaultToken: "default-token",
ACLAgentToken: "agent-token",
ACLAgentRecoveryToken: "recovery-token",
ACLReplicationToken: "replication-token",
ACLConfigFileRegistrationToken: "registration-token",
ACLDefaultToken: "alpha",
ACLAgentToken: "bravo",
ACLAgentRecoveryToken: "charlie",
ACLReplicationToken: "delta",
ACLConfigFileRegistrationToken: "echo",
ACLDNSToken: "foxtrot",
}
err := store.Load(cfg, hclog.New(nil))
require.NoError(t, err)
@ -240,37 +266,39 @@ func TestStore_WithPersistenceLock(t *testing.T) {
t.Run("persist some tokens", func(t *testing.T) {
dataDir, store := setupStore()
err := store.WithPersistenceLock(func() error {
require.True(t, store.UpdateUserToken("the-new-default-token", TokenSourceAPI))
require.True(t, store.UpdateAgentRecoveryToken("the-new-recovery-token", TokenSourceAPI))
require.True(t, store.UpdateUserToken("sierra", TokenSourceAPI))
require.True(t, store.UpdateAgentRecoveryToken("tango", TokenSourceAPI))
return nil
})
require.NoError(t, err)
// Only API-sourced tokens are persisted.
requirePersistedTokens(t, dataDir, persistedTokens{
Default: "the-new-default-token",
AgentRecovery: "the-new-recovery-token",
Default: "sierra",
AgentRecovery: "tango",
})
})
t.Run("persist all tokens", func(t *testing.T) {
dataDir, store := setupStore()
err := store.WithPersistenceLock(func() error {
require.True(t, store.UpdateUserToken("the-new-default-token", TokenSourceAPI))
require.True(t, store.UpdateAgentToken("the-new-agent-token", TokenSourceAPI))
require.True(t, store.UpdateAgentRecoveryToken("the-new-recovery-token", TokenSourceAPI))
require.True(t, store.UpdateReplicationToken("the-new-replication-token", TokenSourceAPI))
require.True(t, store.UpdateConfigFileRegistrationToken("the-new-registration-token", TokenSourceAPI))
require.True(t, store.UpdateUserToken("sierra", TokenSourceAPI))
require.True(t, store.UpdateAgentToken("tango", TokenSourceAPI))
require.True(t, store.UpdateAgentRecoveryToken("uniform", TokenSourceAPI))
require.True(t, store.UpdateReplicationToken("victor", TokenSourceAPI))
require.True(t, store.UpdateConfigFileRegistrationToken("xray", TokenSourceAPI))
require.True(t, store.UpdateDNSToken("zulu", TokenSourceAPI))
return nil
})
require.NoError(t, err)
requirePersistedTokens(t, dataDir, persistedTokens{
Default: "the-new-default-token",
Agent: "the-new-agent-token",
AgentRecovery: "the-new-recovery-token",
Replication: "the-new-replication-token",
ConfigFileRegistration: "the-new-registration-token",
Default: "sierra",
Agent: "tango",
AgentRecovery: "uniform",
Replication: "victor",
ConfigFileRegistration: "xray",
DNS: "zulu",
})
})

View File

@ -24,6 +24,7 @@ const (
TokenKindUser
TokenKindReplication
TokenKindConfigFileRegistration
TokenKindDNS
)
type watcher struct {
@ -52,7 +53,7 @@ type Store struct {
// also be used for agent operations if the agent token isn't set.
userToken string
// userTokenSource indicates where this token originated from
// userTokenSource indicates where this token originated from.
userTokenSource TokenSource
// agentToken is used for internal agent operations like self-registering
@ -60,7 +61,7 @@ type Store struct {
// user-initiated operations.
agentToken string
// agentTokenSource indicates where this token originated from
// agentTokenSource indicates where this token originated from.
agentTokenSource TokenSource
// agentRecoveryToken is a special token that's only used locally for
@ -68,23 +69,30 @@ type Store struct {
// available.
agentRecoveryToken string
// agentRecoveryTokenSource indicates where this token originated from
// agentRecoveryTokenSource indicates where this token originated from.
agentRecoveryTokenSource TokenSource
// replicationToken is a special token that's used by servers to
// replicate data from the primary datacenter.
replicationToken string
// replicationTokenSource indicates where this token originated from
// replicationTokenSource indicates where this token originated from.
replicationTokenSource TokenSource
// configFileRegistrationToken is used to register services and checks
// that are defined in configuration files.
configFileRegistrationToken string
// configFileRegistrationTokenSource indicates where this token originated from
// configFileRegistrationTokenSource indicates where this token originated from.
configFileRegistrationTokenSource TokenSource
// dnsToken is a special token that is used as the implicit token for DNS requests
// as well as for DNS-specific RPC requests.
dnsToken string
// dnsTokenSource indicates where the dnsToken originated from.
dnsTokenSource TokenSource
watchers map[int]watcher
watcherIndex int
@ -204,6 +212,12 @@ func (t *Store) UpdateConfigFileRegistrationToken(token string, source TokenSour
&t.configFileRegistrationTokenSource, TokenKindConfigFileRegistration)
}
// UpdateDNSToken replaces the current DNS token in the store.
// Returns true if it was changed.
func (t *Store) UpdateDNSToken(token string, source TokenSource) bool {
return t.updateToken(token, source, &t.dnsToken, &t.dnsTokenSource, TokenKindDNS)
}
func (t *Store) updateToken(token string, source TokenSource, dstToken *string, dstSource *TokenSource, kind TokenKind) bool {
t.l.Lock()
changed := *dstToken != token || *dstSource != source
@ -261,6 +275,13 @@ func (t *Store) ConfigFileRegistrationToken() string {
return t.configFileRegistrationToken
}
func (t *Store) DNSToken() string {
t.l.RLock()
defer t.l.RUnlock()
return t.dnsToken
}
// UserToken returns the best token to use for user operations.
func (t *Store) UserTokenAndSource() (string, TokenSource) {
t.l.RLock()
@ -299,6 +320,14 @@ func (t *Store) ConfigFileRegistrationTokenAndSource() (string, TokenSource) {
return t.configFileRegistrationToken, t.configFileRegistrationTokenSource
}
// DNSTokenAndSource returns the best token to use for DNS-specific RPC requests and DNS requests
func (t *Store) DNSTokenAndSource() (string, TokenSource) {
t.l.RLock()
defer t.l.RUnlock()
return t.dnsToken, t.dnsTokenSource
}
// IsAgentRecoveryToken checks to see if a given token is the agent recovery token.
// This will never match an empty token for safety.
func (t *Store) IsAgentRecoveryToken(token string) bool {

View File

@ -21,6 +21,8 @@ func TestStore_RegularTokens(t *testing.T) {
replSource TokenSource
registration string
registrationSource TokenSource
dns string
dnsSource TokenSource
}
tests := []struct {
@ -95,11 +97,23 @@ func TestStore_RegularTokens(t *testing.T) {
raw: tokens{registration: "G", registrationSource: TokenSourceAPI},
effective: tokens{registration: "G"},
},
{
name: "set dns - config",
set: tokens{dns: "D", dnsSource: TokenSourceConfig},
raw: tokens{dns: "D", dnsSource: TokenSourceConfig},
effective: tokens{dns: "D"},
},
{
name: "set dns - api",
set: tokens{dns: "D", dnsSource: TokenSourceAPI},
raw: tokens{dns: "D", dnsSource: TokenSourceAPI},
effective: tokens{dns: "D"},
},
{
name: "set all",
set: tokens{user: "U", agent: "A", repl: "R", recovery: "M", registration: "G"},
raw: tokens{user: "U", agent: "A", repl: "R", recovery: "M", registration: "G"},
effective: tokens{user: "U", agent: "A", repl: "R", recovery: "M", registration: "G"},
set: tokens{user: "U", agent: "A", repl: "R", recovery: "M", registration: "G", dns: "D"},
raw: tokens{user: "U", agent: "A", repl: "R", recovery: "M", registration: "G", dns: "D"},
effective: tokens{user: "U", agent: "A", repl: "R", recovery: "M", registration: "G", dns: "D"},
},
}
for _, tt := range tests {
@ -125,18 +139,24 @@ func TestStore_RegularTokens(t *testing.T) {
require.True(t, s.UpdateConfigFileRegistrationToken(tt.set.registration, tt.set.registrationSource))
}
if tt.set.dns != "" {
require.True(t, s.UpdateDNSToken(tt.set.dns, tt.set.dnsSource))
}
// If they don't change then they return false.
require.False(t, s.UpdateUserToken(tt.set.user, tt.set.userSource))
require.False(t, s.UpdateAgentToken(tt.set.agent, tt.set.agentSource))
require.False(t, s.UpdateReplicationToken(tt.set.repl, tt.set.replSource))
require.False(t, s.UpdateAgentRecoveryToken(tt.set.recovery, tt.set.recoverySource))
require.False(t, s.UpdateConfigFileRegistrationToken(tt.set.registration, tt.set.registrationSource))
require.False(t, s.UpdateDNSToken(tt.set.dns, tt.set.dnsSource))
require.Equal(t, tt.effective.user, s.UserToken())
require.Equal(t, tt.effective.agent, s.AgentToken())
require.Equal(t, tt.effective.recovery, s.AgentRecoveryToken())
require.Equal(t, tt.effective.repl, s.ReplicationToken())
require.Equal(t, tt.effective.registration, s.ConfigFileRegistrationToken())
require.Equal(t, tt.effective.dns, s.DNSToken())
tok, src := s.UserTokenAndSource()
require.Equal(t, tt.raw.user, tok)
@ -157,6 +177,10 @@ func TestStore_RegularTokens(t *testing.T) {
tok, src = s.ConfigFileRegistrationTokenAndSource()
require.Equal(t, tt.raw.registration, tok)
require.Equal(t, tt.raw.registrationSource, src)
tok, src = s.DNSTokenAndSource()
require.Equal(t, tt.raw.dns, tok)
require.Equal(t, tt.raw.dnsSource, src)
})
}
}
@ -211,6 +235,7 @@ func TestStore_Notify(t *testing.T) {
replicationNotifier := newNotification(t, s, TokenKindReplication)
replicationNotifier2 := newNotification(t, s, TokenKindReplication)
registrationNotifier := newNotification(t, s, TokenKindConfigFileRegistration)
dnsNotifier := newNotification(t, s, TokenKindDNS)
// perform an update of the user token
require.True(t, s.UpdateUserToken("edcae2a2-3b51-4864-b412-c7a568f49cb1", TokenSourceConfig))
@ -224,6 +249,7 @@ func TestStore_Notify(t *testing.T) {
requireNotNotified(t, agentRecoveryNotifier.Ch)
requireNotNotified(t, replicationNotifier2.Ch)
requireNotNotified(t, registrationNotifier.Ch)
requireNotNotified(t, dnsNotifier.Ch)
// update the agent token which should send a notification to the agent notifier.
require.True(t, s.UpdateAgentToken("5d748ec2-d536-461f-8e2a-1f7eae98d559", TokenSourceAPI))
@ -234,6 +260,7 @@ func TestStore_Notify(t *testing.T) {
requireNotNotified(t, agentRecoveryNotifier.Ch)
requireNotNotified(t, replicationNotifier2.Ch)
requireNotNotified(t, registrationNotifier.Ch)
requireNotNotified(t, dnsNotifier.Ch)
// update the agent recovery token which should send a notification to the agent recovery notifier.
require.True(t, s.UpdateAgentRecoveryToken("789badc8-f850-43e1-8742-9b9f484957cc", TokenSourceAPI))
@ -244,6 +271,7 @@ func TestStore_Notify(t *testing.T) {
requireNotifiedOnce(t, agentRecoveryNotifier.Ch)
requireNotNotified(t, replicationNotifier2.Ch)
requireNotNotified(t, registrationNotifier.Ch)
requireNotNotified(t, dnsNotifier.Ch)
// update the replication token which should send a notification to the replication notifier.
require.True(t, s.UpdateReplicationToken("789badc8-f850-43e1-8742-9b9f484957cc", TokenSourceAPI))
@ -254,6 +282,7 @@ func TestStore_Notify(t *testing.T) {
requireNotNotified(t, agentRecoveryNotifier.Ch)
requireNotifiedOnce(t, replicationNotifier2.Ch)
requireNotNotified(t, registrationNotifier.Ch)
requireNotNotified(t, dnsNotifier.Ch)
s.StopNotify(replicationNotifier2)
@ -266,6 +295,7 @@ func TestStore_Notify(t *testing.T) {
requireNotNotified(t, agentRecoveryNotifier.Ch)
requireNotNotified(t, replicationNotifier2.Ch)
requireNotNotified(t, registrationNotifier.Ch)
requireNotNotified(t, dnsNotifier.Ch)
// update the config file registration token which should send a notification to the replication notifier.
require.True(t, s.UpdateConfigFileRegistrationToken("82fe7362-7d83-4f43-bb27-c35f1f15083c", TokenSourceAPI))
@ -276,6 +306,18 @@ func TestStore_Notify(t *testing.T) {
requireNotNotified(t, agentRecoveryNotifier.Ch)
requireNotNotified(t, replicationNotifier2.Ch)
requireNotifiedOnce(t, registrationNotifier.Ch)
requireNotNotified(t, dnsNotifier.Ch)
// update the dns token which should send a notification to the replication notifier.
require.True(t, s.UpdateDNSToken("ce8e829f-dc45-4ba7-9dd3-1dbbe070f573", TokenSourceAPI))
requireNotNotified(t, agentNotifier.Ch)
requireNotNotified(t, userNotifier.Ch)
requireNotNotified(t, replicationNotifier.Ch)
requireNotNotified(t, agentRecoveryNotifier.Ch)
requireNotNotified(t, replicationNotifier2.Ch)
requireNotNotified(t, registrationNotifier.Ch)
requireNotifiedOnce(t, dnsNotifier.Ch)
// request updates that are not changes
require.False(t, s.UpdateAgentToken("5d748ec2-d536-461f-8e2a-1f7eae98d559", TokenSourceAPI))
@ -283,6 +325,7 @@ func TestStore_Notify(t *testing.T) {
require.False(t, s.UpdateUserToken("47788919-f944-476a-bda5-446d64be1df8", TokenSourceAPI))
require.False(t, s.UpdateReplicationToken("eb0b56b9-fa65-4ae1-902a-c64457c62ac6", TokenSourceAPI))
require.False(t, s.UpdateConfigFileRegistrationToken("82fe7362-7d83-4f43-bb27-c35f1f15083c", TokenSourceAPI))
require.False(t, s.UpdateDNSToken("ce8e829f-dc45-4ba7-9dd3-1dbbe070f573", TokenSourceAPI))
// ensure that notifications were not sent
requireNotNotified(t, agentNotifier.Ch)
@ -290,4 +333,5 @@ func TestStore_Notify(t *testing.T) {
requireNotNotified(t, replicationNotifier.Ch)
requireNotNotified(t, agentRecoveryNotifier.Ch)
requireNotNotified(t, registrationNotifier.Ch)
requireNotNotified(t, dnsNotifier.Ch)
}

View File

@ -1379,6 +1379,10 @@ func (a *Agent) UpdateConfigFileRegistrationToken(token string, q *WriteOptions)
return a.updateToken("config_file_service_registration", token, q)
}
func (a *Agent) UpdateDNSToken(token string, q *WriteOptions) (*WriteMeta, error) {
return a.updateToken("dns", token, q)
}
// updateToken can be used to update one of an agent's ACL tokens after the agent has
// started. The tokens are may not be persisted, so will need to be updated again if
// the agent is restarted unless the agent is configured to persist them.

View File

@ -1635,6 +1635,10 @@ func TestAPI_AgentUpdateToken(t *testing.T) {
t.Fatalf("err: %v", err)
}
if _, err := agent.UpdateDNSToken("root", nil); err != nil {
t.Fatalf("err: %v", err)
}
})
t.Run("new with fallback", func(t *testing.T) {
@ -1723,6 +1727,9 @@ func TestAPI_AgentUpdateToken(t *testing.T) {
_, err = agent.UpdateConfigFileRegistrationToken("root", nil)
require.Error(t, err)
_, err = agent.UpdateDNSToken("root", nil)
require.Error(t, err)
})
}

View File

@ -8,9 +8,10 @@ import (
"fmt"
"io"
"github.com/mitchellh/cli"
"github.com/hashicorp/consul/command/flags"
"github.com/hashicorp/consul/command/helpers"
"github.com/mitchellh/cli"
)
func New(ui cli.Ui) *cmd {
@ -63,6 +64,8 @@ func (c *cmd) Run(args []string) int {
_, err = client.Agent().UpdateReplicationACLToken(token, nil)
case "config_file_service_registration":
_, err = client.Agent().UpdateConfigFileRegistrationToken(token, nil)
case "dns":
_, err = client.Agent().UpdateDNSToken(token, nil)
default:
c.UI.Error(fmt.Sprintf("Unknown token type"))
return 1
@ -140,6 +143,11 @@ Usage: consul acl set-agent-token [options] TYPE TOKEN
If a service or check definition contains a 'token'
field, then that token is used instead.
dns This is the token that the will be used in place of the default
token when specified for DNS requests and for DNS-specific RPCs.
If not provided the agent will attempt to use the default token
if one is present, then fallback to the anonymous token.
Example:
$ consul acl set-agent-token default c4d0f8df-3aba-4ab6-a7a0-35b760dc29a1

View File

@ -6,7 +6,7 @@ require (
github.com/hashicorp/consul/api v1.24.0
github.com/hashicorp/consul/sdk v0.14.1
github.com/hashicorp/consul/test/integration/consul-container v0.0.0-20230628201853-bdf4fad7c5a5
github.com/hashicorp/consul/testing/deployer v0.0.0-00010101000000-000000000000
github.com/hashicorp/consul/testing/deployer v0.0.0-20230811171106-4a0afb5d1373
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/itchyny/gojq v0.12.13
github.com/mitchellh/copystructure v1.2.0

View File

@ -14,7 +14,7 @@ require (
github.com/hashicorp/consul/envoyextensions v0.4.1
github.com/hashicorp/consul/proto-public v0.4.1
github.com/hashicorp/consul/sdk v0.14.1
github.com/hashicorp/consul/testing/deployer v0.0.0-00010101000000-000000000000
github.com/hashicorp/consul/testing/deployer v0.0.0-20230811171106-4a0afb5d1373
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-uuid v1.0.3

View File

@ -114,7 +114,7 @@ func AssertUpstreamEndpointStatusWithClient(
| length`,
clusterName, healthStatus)
results, err := utils.JQFilter(clusters, filter)
require.NoErrorf(r, err, "could not found cluster name %q: %v \n%s", clusterName, err, clusters)
require.NoErrorf(r, err, "could not find cluster name %q: %v \n%s", clusterName, err, clusters)
require.Len(r, results, 1) // the final part of the pipeline is "length" which only ever returns 1 result
result, err := strconv.Atoi(results[0])

View File

@ -614,6 +614,7 @@ func newContainerRequest(config Config, opts containerOpts, ports ...int) (podRe
"8500/tcp", // Consul HTTP API
"8501/tcp", // Consul HTTPs API
"8502/tcp", // Consul gRPC API
"8600/udp", // Consul DNS API
"8443/tcp", // Envoy Gateway Listener

View File

@ -43,7 +43,7 @@ type PeeringClusterSize struct {
//
// - an accepting cluster with 3 servers and 1 client agent. The client should be used to
// host a service for export: staticServerSvc.
// - an dialing cluster with 1 server and 1 client. The client should be used to host a
// - a dialing cluster with 1 server and 1 client. The client should be used to host a
// service connecting to staticServerSvc.
// - Create the peering, export the service from accepting cluster, and verify service
// connectivity.
@ -120,7 +120,7 @@ func BasicPeeringTwoClustersSetup(
libassert.PeeringStatus(t, acceptingClient, AcceptingPeerName, api.PeeringStateActive)
// libassert.PeeringExports(t, acceptingClient, acceptingPeerName, 1)
// Register an static-server service in acceptingCluster and export to dialing cluster
// Register a static-server service in acceptingCluster and export to dialing cluster
var serverService, serverSidecarService libservice.Service
{
clientNode := acceptingCluster.Clients()[0]
@ -144,7 +144,7 @@ func BasicPeeringTwoClustersSetup(
require.NoError(t, serverService.Export("default", AcceptingPeerName, acceptingClient))
}
// Register an static-client service in dialing cluster and set upstream to static-server service
// Register a static-client service in dialing cluster and set upstream to static-server service
var clientSidecarService *libservice.ConnectContainer
{
clientNode := dialingCluster.Clients()[0]
@ -268,11 +268,11 @@ func NewClusterWithConfig(
}
// Add numClients static clients to register the service
configbuiilder := libcluster.NewConfigBuilder(ctx).
configBuilder := libcluster.NewConfigBuilder(ctx).
Client().
Peering(true).
RetryJoin(retryJoin...)
clientConf := configbuiilder.ToAgentConfig(t)
clientConf := configBuilder.ToAgentConfig(t)
t.Logf("%s client config: \n%s", opts.Datacenter, clientConf.JSON)
if clientHclConfig != "" {
clientConf.MutatebyAgentConfig(clientHclConfig)

View File

@ -3,4 +3,4 @@
package topology
const DefaultDataplaneImage = "hashicorp/consul-dataplane:1.1.0"
const DefaultDataplaneImage = "hashicorp/consul-dataplane:1.2.1"