Merge branch 'docs/admin-partitions-rc-updates' of github.com:hashicorp/consul into docs/admin-partitions-rc-updates

applying feedback on admin partitions rc docs
This commit is contained in:
trujillo-adam 2021-12-14 11:44:15 -08:00
commit 4c073e0d9f
221 changed files with 4829 additions and 2450 deletions

3
.changelog/10895.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
grpc, xds: improved reliability of grpc and xds servers by adding recovery-middleware to return and log error in case of panic.
```

3
.changelog/11645.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
types: add TLSVersion and TLSCipherSuite
```

3
.changelog/11668.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
ui: Add documentation link to Partition empty state
```

3
.changelog/11670.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
ui: Fix visual issue with slight table header overflow
```

3
.changelog/11679.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
ui: Adds support for partitions to the Routing visualization.
```

3
.changelog/11680.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
server: block enterprise-specific partition-exports config entry from being used in OSS Consul.
```

3
.changelog/11696.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
ui: Adds support for partitions to Service and Node Identity template visuals.
```

3
.changelog/11702.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
ui: Adds basic support for showing Services exported from another partition.
```

11
.changelog/11720.txt Normal file
View File

@ -0,0 +1,11 @@
```release-note:improvement
raft: Use bbolt instead of the legacy boltdb implementation
```
```release-note:improvement
raft: Emit boltdb related performance metrics
```
```release-note:improvement
raft: Added a configuration to disable boltdb freelist syncing
```

3
.changelog/11737.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
partitions: **(Enterprise only)** rename APIs, commands, and public types to use "partition" rather than "admin partition".
```

3
.changelog/11738.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
connect: **(Enterprise only)** add support for cross-partition transparent proxying.
```

3
.changelog/11739.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
api: **(Enterprise Only)** rename partition-exports config entry to exported-services.
```

3
.changelog/11744.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:note
Renamed the `agent_master` field to `agent_recovery` in the `acl-tokens.json` file in which tokens are persisted on-disk (when `acl.enable_token_persistence` is enabled)
```

3
.changelog/11748.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:bug
areas: **(Enterprise only)** make the gRPC server tracker network area aware
```

3
.changelog/11757.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
connect: **(Enterprise only)** add support for targeting partitions in discovery chain routes, splits, and redirects.
```

3
.changelog/_1391.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:feature
partitions: **(Enterprise only)** Ensure partitions and serf-based WAN federation are mutually exclusive.
```

View File

@ -3,30 +3,55 @@ updates:
- package-ecosystem: gomod - package-ecosystem: gomod
open-pull-requests-limit: 5 open-pull-requests-limit: 5
directory: "/" directory: "/"
labels:
- "go"
- "dependencies"
- "pr/no-changelog"
schedule: schedule:
interval: daily interval: daily
- package-ecosystem: gomod - package-ecosystem: gomod
open-pull-requests-limit: 5 open-pull-requests-limit: 5
directory: "/api" directory: "/api"
labels:
- "go"
- "dependencies"
- "pr/no-changelog"
schedule: schedule:
interval: daily interval: daily
- package-ecosystem: gomod - package-ecosystem: gomod
open-pull-requests-limit: 5 open-pull-requests-limit: 5
directory: "/sdk" directory: "/sdk"
labels:
- "go"
- "dependencies"
- "pr/no-changelog"
schedule: schedule:
interval: daily interval: daily
- package-ecosystem: npm - package-ecosystem: npm
open-pull-requests-limit: 5 open-pull-requests-limit: 5
directory: "/ui" directory: "/ui"
labels:
- "javascript"
- "dependencies"
- "pr/no-changelog"
schedule: schedule:
interval: daily interval: daily
- package-ecosystem: npm - package-ecosystem: npm
open-pull-requests-limit: 5 open-pull-requests-limit: 5
directory: "/website" directory: "/website"
labels:
- "javascript"
- "dependencies"
- "type/docs-cherrypick"
- "pr/no-changelog"
schedule: schedule:
interval: daily interval: daily
- package-ecosystem: github-actions - package-ecosystem: github-actions
open-pull-requests-limit: 5 open-pull-requests-limit: 5
directory: / directory: /
labels:
- "github_actions"
- "dependencies"
- "pr/no-changelog"
schedule: schedule:
interval: daily interval: daily

View File

@ -1,4 +1,118 @@
## UNRELEASED ## 1.11.0-rc (December 08, 2021)
BREAKING CHANGES:
* cli: `consul acl set-agent-token master` has been replaced with `consul acl set-agent-token recovery` [[GH-11669](https://github.com/hashicorp/consul/issues/11669)]
FEATURES:
* partitions: **(Enterprise only)** Ensure partitions and serf-based WAN federation are mutually exclusive.
* ui: Add documentation link to Partition empty state [[GH-11668](https://github.com/hashicorp/consul/issues/11668)]
* ui: Adds basic support for showing Services exported from another partition. [[GH-11702](https://github.com/hashicorp/consul/issues/11702)]
* ui: Adds support for partitions to Service and Node Identity template visuals. [[GH-11696](https://github.com/hashicorp/consul/issues/11696)]
* ui: Adds support for partitions to the Routing visualization. [[GH-11679](https://github.com/hashicorp/consul/issues/11679)]
* ui: Don't offer a 'Valid Datacenters' option when editing policies for non-default partitions [[GH-11656](https://github.com/hashicorp/consul/issues/11656)]
* ui: Include `Service.Partition` into available variables for `dashboard_url_templates` [[GH-11654](https://github.com/hashicorp/consul/issues/11654)]
* ui: Upgrade Lock Sessions to use partitions [[GH-11666](https://github.com/hashicorp/consul/issues/11666)]
IMPROVEMENTS:
* agent: **(Enterprise only)** purge service/check registration files for incorrect partitions on reload [[GH-11607](https://github.com/hashicorp/consul/issues/11607)]
* agent: add variation of force-leave that exclusively works on the WAN [[GH-11722](https://github.com/hashicorp/consul/issues/11722)]
* api: **(Enterprise Only)** rename partition-exports config entry to exported-services. [[GH-11739](https://github.com/hashicorp/consul/issues/11739)]
* auto-config: ensure the feature works properly with partitions [[GH-11699](https://github.com/hashicorp/consul/issues/11699)]
* connect: **(Enterprise only)** add support for cross-partition transparent proxying. [[GH-11738](https://github.com/hashicorp/consul/issues/11738)]
* connect: **(Enterprise only)** add support for targeting partitions in discovery chain routes, splits, and redirects. [[GH-11757](https://github.com/hashicorp/consul/issues/11757)]
* connect: Consul will now generate a unique virtual IP for each connect-enabled service (this will also differ across namespace/partition in Enterprise). [[GH-11724](https://github.com/hashicorp/consul/issues/11724)]
* connect: Support Vault auth methods for the Connect CA Vault provider. Currently, we support any non-deprecated auth methods
the latest version of Vault supports (v1.8.5), which include AppRole, AliCloud, AWS, Azure, Cloud Foundry, GitHub, Google Cloud,
JWT/OIDC, Kerberos, Kubernetes, LDAP, Oracle Cloud Infrastructure, Okta, Radius, TLS Certificates, and Username & Password. [[GH-11573](https://github.com/hashicorp/consul/issues/11573)]
* dns: Added a `virtual` endpoint for querying the assigned virtual IP for a service. [[GH-11725](https://github.com/hashicorp/consul/issues/11725)]
* partitions: **(Enterprise only)** rename APIs, commands, and public types to use "partition" rather than "admin partition". [[GH-11737](https://github.com/hashicorp/consul/issues/11737)]
* raft: Added a configuration to disable boltdb freelist syncing [[GH-11720](https://github.com/hashicorp/consul/issues/11720)]
* raft: Emit boltdb related performance metrics [[GH-11720](https://github.com/hashicorp/consul/issues/11720)]
* raft: Use bbolt instead of the legacy boltdb implementation [[GH-11720](https://github.com/hashicorp/consul/issues/11720)]
* sentinel: **(Enterprise Only)** Sentinel now uses SHA256 to generate policy ids
* server: block enterprise-specific partition-exports config entry from being used in OSS Consul. [[GH-11680](https://github.com/hashicorp/consul/issues/11680)]
* types: add TLSVersion and TLSCipherSuite [[GH-11645](https://github.com/hashicorp/consul/issues/11645)]
* ui: Add partition support for SSO [[GH-11604](https://github.com/hashicorp/consul/issues/11604)]
* ui: Update global notification styling [[GH-11577](https://github.com/hashicorp/consul/issues/11577)]
DEPRECATIONS:
* api: `/v1/agent/token/agent_master` is deprecated and will be removed in a future major release - use `/v1/agent/token/agent_recovery` instead [[GH-11669](https://github.com/hashicorp/consul/issues/11669)]
* config: `acl.tokens.master` has been renamed to `acl.tokens.initial_management`, and `acl.tokens.agent_master` has been renamed to `acl.tokens.agent_recovery` - the old field names are now deprecated and will be removed in a future major release [[GH-11665](https://github.com/hashicorp/consul/issues/11665)]
BUG FIXES:
* areas: **(Enterprise Only)** Fixes a bug when using Yamux pool ( for servers version 1.7.3 and later), the entire pool was locked while connecting to a remote location, which could potentially take a long time. [[GH-1368](https://github.com/hashicorp/consul/issues/1368)]
* areas: **(Enterprise only)** make the gRPC server tracker network area aware [[GH-11748](https://github.com/hashicorp/consul/issues/11748)]
* ca: fixes a bug that caused non blocking leaf cert queries to return the same cached response regardless of ca rotation or leaf cert expiry [[GH-11693](https://github.com/hashicorp/consul/issues/11693)]
* ca: fixes a bug that caused the SigningKeyID to be wrong in the primary DC, when the Vault provider is used, after a CA config creates a new root. [[GH-11672](https://github.com/hashicorp/consul/issues/11672)]
* ca: fixes a bug that caused the intermediate cert used to sign leaf certs to be missing from the /connect/ca/roots API response when the Vault provider was used. [[GH-11671](https://github.com/hashicorp/consul/issues/11671)]
* ui: Fix inline-code brand styling [[GH-11578](https://github.com/hashicorp/consul/issues/11578)]
* ui: Fix visual issue with slight table header overflow [[GH-11670](https://github.com/hashicorp/consul/issues/11670)]
* ui: Fixes an issue where under some circumstances after logging we present the
data loaded previous to you logging in. [[GH-11681](https://github.com/hashicorp/consul/issues/11681)]
* ui: Include `Service.Namespace` into available variables for `dashboard_url_templates` [[GH-11640](https://github.com/hashicorp/consul/issues/11640)]
## 1.11.0-beta3 (November 17, 2021)
SECURITY:
* agent: Use SHA256 instead of MD5 to generate persistence file names. [[GH-11491](https://github.com/hashicorp/consul/issues/11491)]
* namespaces: **(Enterprise only)** Creating or editing namespaces that include default ACL policies or ACL roles now requires `acl:write` permission in the default namespace. This change fixes CVE-2021-41805.
FEATURES:
* ca: Add a configurable TTL for Connect CA root certificates. The configuration is supported by the Vault and Consul providers. [[GH-11428](https://github.com/hashicorp/consul/issues/11428)]
* ca: Add a configurable TTL to the AWS ACM Private CA provider root certificate. [[GH-11449](https://github.com/hashicorp/consul/issues/11449)]
* health-checks: add support for h2c in http2 ping health checks [[GH-10690](https://github.com/hashicorp/consul/issues/10690)]
* partitions: **(Enterprise only)** segment serf LAN gossip between nodes in different partitions
* ui: Adding support of Consul API Gateway as an external source. [[GH-11371](https://github.com/hashicorp/consul/issues/11371)]
* ui: Topology - New views for scenarios where no dependencies exist or ACLs are disabled [[GH-11280](https://github.com/hashicorp/consul/issues/11280)]
IMPROVEMENTS:
* ci: Artifact builds will now only run on merges to the release branches or to `main` [[GH-11417](https://github.com/hashicorp/consul/issues/11417)]
* ci: The Linux packages are now available for all supported Linux architectures including arm, arm64, 386, and amd64 [[GH-11417](https://github.com/hashicorp/consul/issues/11417)]
* ci: The Linux packaging service configs and pre/post install scripts are now available under [.release/linux] [[GH-11417](https://github.com/hashicorp/consul/issues/11417)]
* config: warn the user if client_addr is empty because client services won't be listening [[GH-11461](https://github.com/hashicorp/consul/issues/11461)]
* connect/ca: Return an error when querying roots from uninitialized CA. [[GH-11514](https://github.com/hashicorp/consul/issues/11514)]
* connect: **(Enterprise only)** Allow ingress gateways to target services in another partition [[GH-11566](https://github.com/hashicorp/consul/issues/11566)]
* connect: add Namespace configuration setting for Vault CA provider [[GH-11477](https://github.com/hashicorp/consul/issues/11477)]
* namespaces: **(Enterprise only)** policy and role defaults can reference policies in any namespace in the same partition by ID
* partitions: Prevent writing partition-exports entries to secondary DCs. [[GH-11541](https://github.com/hashicorp/consul/issues/11541)]
* sdk: Add support for iptable rules that allow DNS lookup redirection to Consul DNS. [[GH-11480](https://github.com/hashicorp/consul/issues/11480)]
* segments: **(Enterprise only)** ensure that the serf_lan_allowed_cidrs applies to network segments [[GH-11495](https://github.com/hashicorp/consul/issues/11495)]
* ui: Add upstream icons for upstreams and upstream instances [[GH-11556](https://github.com/hashicorp/consul/issues/11556)]
* ui: Update UI browser support to 'roughly ~2 years back' [[GH-11505](https://github.com/hashicorp/consul/issues/11505)]
* ui: When switching partitions reset the namespace back to the tokens default namespace or default [[GH-11479](https://github.com/hashicorp/consul/issues/11479)]
* ui: added copy to clipboard button in code editor toolbars [[GH-11474](https://github.com/hashicorp/consul/issues/11474)]
BUG FIXES:
* acl: **(Enterprise only)** fix namespace and namespace_prefix policy evaluation when both govern an authz request
* api: ensure new partition fields are omit empty for compatibility with older versions of consul [[GH-11585](https://github.com/hashicorp/consul/issues/11585)]
* connect/ca: Allow secondary initialization to resume after being deferred due to unreachable or incompatible primary DC servers. [[GH-11514](https://github.com/hashicorp/consul/issues/11514)]
* connect: fix issue with attempting to generate an invalid upstream cluster from UpstreamConfig.Defaults. [[GH-11245](https://github.com/hashicorp/consul/issues/11245)]
* macos: fixes building with a non-Apple LLVM (such as installed via Homebrew) [[GH-11586](https://github.com/hashicorp/consul/issues/11586)]
* namespaces: **(Enterprise only)** ensure the namespace replicator doesn't replicate deleted namespaces
* partitions: **(Enterprise only)** fix panic when forwarding delete operations to the leader
* snapshot: **(Enterprise only)** fixed a bug where the snapshot agent would ignore the `license_path` setting in config files
* snapshot: **(Enterprise only)** snapshot agent no longer attempts to refresh its license from the server when a local license is provided (i.e. via config or an environment variable)
* state: **(Enterprise Only)** ensure partition delete triggers namespace deletes
* ui: **(Enterprise only)** When no namespace is selected, make sure to default to the tokens default namespace when requesting permissions [[GH-11472](https://github.com/hashicorp/consul/issues/11472)]
* ui: Ensure the UI stores the default partition for the users token [[GH-11591](https://github.com/hashicorp/consul/issues/11591)]
* ui: Ensure we check intention permissions for specific services when deciding
whether to show action buttons for per service intention actions [[GH-11409](https://github.com/hashicorp/consul/issues/11409)]
* ui: Filter the global intentions list by the currently selected parition rather
than a wildcard [[GH-11475](https://github.com/hashicorp/consul/issues/11475)]
* ui: Revert to depending on the backend, 'post-user-action', to report
permissions errors rather than using UI capabilities 'pre-user-action' [[GH-11520](https://github.com/hashicorp/consul/issues/11520)]
* ui: code editor styling (layout consistency + wide screen support) [[GH-11474](https://github.com/hashicorp/consul/issues/11474)]
* windows: fixes arm and arm64 builds [[GH-11586](https://github.com/hashicorp/consul/issues/11586)]
* xds: fixes a bug where replacing a mesh gateway node used for WAN federation (with another that has a different IP) could leave gateways in the other DC unable to re-establish the connection [[GH-11522](https://github.com/hashicorp/consul/issues/11522)]
## 1.11.0-beta2 (November 02, 2021) ## 1.11.0-beta2 (November 02, 2021)

View File

@ -16,10 +16,10 @@ type Config struct {
type ExportFetcher interface { type ExportFetcher interface {
// ExportsForPartition returns the config entry defining exports for a partition // ExportsForPartition returns the config entry defining exports for a partition
ExportsForPartition(partition string) PartitionExports ExportsForPartition(partition string) ExportedServices
} }
type PartitionExports struct { type ExportedServices struct {
Data map[string]map[string][]string Data map[string]map[string][]string
} }

View File

@ -91,9 +91,14 @@ func TestACL_Bootstrap(t *testing.T) {
} }
t.Parallel() t.Parallel()
a := NewTestAgent(t, TestACLConfig()+` a := NewTestAgent(t, `
acl_master_token = "" primary_datacenter = "dc1"
`)
acl {
enabled = true
default_policy = "deny"
}
`)
defer a.Shutdown() defer a.Shutdown()
tests := []struct { tests := []struct {
@ -881,7 +886,7 @@ func TestACL_HTTP(t *testing.T) {
require.True(t, ok) require.True(t, ok)
require.Len(t, tokens, 1) require.Len(t, tokens, 1)
token := tokens[0] token := tokens[0]
require.Equal(t, "Master Token", token.Description) require.Equal(t, "Initial Management Token", token.Description)
require.Len(t, token.Policies, 1) require.Len(t, token.Policies, 1)
require.Equal(t, structs.ACLPolicyGlobalManagementID, token.Policies[0].ID) require.Equal(t, structs.ACLPolicyGlobalManagementID, token.Policies[0].ID)
}) })
@ -1689,7 +1694,7 @@ func TestACLEndpoint_LoginLogout_jwt(t *testing.T) {
for name, tc := range cases { for name, tc := range cases {
tc := tc tc := tc
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
method, err := upsertTestCustomizedAuthMethod(a.RPC, TestDefaultMasterToken, "dc1", func(method *structs.ACLAuthMethod) { method, err := upsertTestCustomizedAuthMethod(a.RPC, TestDefaultInitialManagementToken, "dc1", func(method *structs.ACLAuthMethod) {
method.Type = "jwt" method.Type = "jwt"
method.Config = map[string]interface{}{ method.Config = map[string]interface{}{
"JWTSupportedAlgs": []string{"ES256"}, "JWTSupportedAlgs": []string{"ES256"},
@ -1758,7 +1763,7 @@ func TestACLEndpoint_LoginLogout_jwt(t *testing.T) {
testutil.RequireErrorContains(t, err, "Permission denied") testutil.RequireErrorContains(t, err, "Permission denied")
}) })
_, err = upsertTestCustomizedBindingRule(a.RPC, TestDefaultMasterToken, "dc1", func(rule *structs.ACLBindingRule) { _, err = upsertTestCustomizedBindingRule(a.RPC, TestDefaultInitialManagementToken, "dc1", func(rule *structs.ACLBindingRule) {
rule.AuthMethod = method.Name rule.AuthMethod = method.Name
rule.BindType = structs.BindingRuleBindTypeService rule.BindType = structs.BindingRuleBindTypeService
rule.BindName = "test--${value.name}--${value.primary_org}" rule.BindName = "test--${value.name}--${value.primary_org}"
@ -1798,7 +1803,7 @@ func TestACLEndpoint_LoginLogout_jwt(t *testing.T) {
// verify the token was deleted // verify the token was deleted
req, _ = http.NewRequest("GET", "/v1/acl/token/"+token.AccessorID, nil) req, _ = http.NewRequest("GET", "/v1/acl/token/"+token.AccessorID, nil)
req.Header.Add("X-Consul-Token", TestDefaultMasterToken) req.Header.Add("X-Consul-Token", TestDefaultInitialManagementToken)
resp = httptest.NewRecorder() resp = httptest.NewRecorder()
// make the request // make the request
@ -1819,7 +1824,7 @@ func TestACL_Authorize(t *testing.T) {
a1 := NewTestAgent(t, TestACLConfigWithParams(nil)) a1 := NewTestAgent(t, TestACLConfigWithParams(nil))
defer a1.Shutdown() defer a1.Shutdown()
testrpc.WaitForTestAgent(t, a1.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken)) testrpc.WaitForTestAgent(t, a1.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))
policyReq := structs.ACLPolicySetRequest{ policyReq := structs.ACLPolicySetRequest{
Policy: structs.ACLPolicy{ Policy: structs.ACLPolicy{
@ -1827,7 +1832,7 @@ func TestACL_Authorize(t *testing.T) {
Rules: `acl = "read" operator = "write" service_prefix "" { policy = "read"} node_prefix "" { policy= "write" } key_prefix "/foo" { policy = "write" } `, Rules: `acl = "read" operator = "write" service_prefix "" { policy = "read"} node_prefix "" { policy= "write" } key_prefix "/foo" { policy = "write" } `,
}, },
Datacenter: "dc1", Datacenter: "dc1",
WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
} }
var policy structs.ACLPolicy var policy structs.ACLPolicy
require.NoError(t, a1.RPC("ACL.PolicySet", &policyReq, &policy)) require.NoError(t, a1.RPC("ACL.PolicySet", &policyReq, &policy))
@ -1841,15 +1846,15 @@ func TestACL_Authorize(t *testing.T) {
}, },
}, },
Datacenter: "dc1", Datacenter: "dc1",
WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
} }
var token structs.ACLToken var token structs.ACLToken
require.NoError(t, a1.RPC("ACL.TokenSet", &tokenReq, &token)) require.NoError(t, a1.RPC("ACL.TokenSet", &tokenReq, &token))
// secondary also needs to setup a replication token to pull tokens and policies // secondary also needs to setup a replication token to pull tokens and policies
secondaryParams := DefaulTestACLConfigParams() secondaryParams := DefaultTestACLConfigParams()
secondaryParams.ReplicationToken = secondaryParams.MasterToken secondaryParams.ReplicationToken = secondaryParams.InitialManagementToken
secondaryParams.EnableTokenReplication = true secondaryParams.EnableTokenReplication = true
a2 := NewTestAgent(t, `datacenter = "dc2" `+TestACLConfigWithParams(secondaryParams)) a2 := NewTestAgent(t, `datacenter = "dc2" `+TestACLConfigWithParams(secondaryParams))
@ -1859,7 +1864,7 @@ func TestACL_Authorize(t *testing.T) {
_, err := a2.JoinWAN([]string{addr}) _, err := a2.JoinWAN([]string{addr})
require.NoError(t, err) require.NoError(t, err)
testrpc.WaitForTestAgent(t, a2.RPC, "dc2", testrpc.WithToken(TestDefaultMasterToken)) testrpc.WaitForTestAgent(t, a2.RPC, "dc2", testrpc.WithToken(TestDefaultInitialManagementToken))
// this actually ensures a few things. First the dcs got connect okay, secondly that the policy we // this actually ensures a few things. First the dcs got connect okay, secondly that the policy we
// are about ready to use in our local token creation exists in the secondary DC // are about ready to use in our local token creation exists in the secondary DC
testrpc.WaitForACLReplication(t, a2.RPC, "dc2", structs.ACLReplicateTokens, policy.CreateIndex, 1, 0) testrpc.WaitForACLReplication(t, a2.RPC, "dc2", structs.ACLReplicateTokens, policy.CreateIndex, 1, 0)
@ -1874,7 +1879,7 @@ func TestACL_Authorize(t *testing.T) {
Local: true, Local: true,
}, },
Datacenter: "dc2", Datacenter: "dc2",
WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
} }
var localToken structs.ACLToken var localToken structs.ACLToken
@ -2004,7 +2009,7 @@ func TestACL_Authorize(t *testing.T) {
for _, dc := range []string{"dc1", "dc2"} { for _, dc := range []string{"dc1", "dc2"} {
t.Run(dc, func(t *testing.T) { t.Run(dc, func(t *testing.T) {
req, _ := http.NewRequest("POST", "/v1/internal/acl/authorize?dc="+dc, jsonBody(request)) req, _ := http.NewRequest("POST", "/v1/internal/acl/authorize?dc="+dc, jsonBody(request))
req.Header.Add("X-Consul-Token", TestDefaultMasterToken) req.Header.Add("X-Consul-Token", TestDefaultInitialManagementToken)
recorder := httptest.NewRecorder() recorder := httptest.NewRecorder()
raw, err := a1.srv.ACLAuthorize(recorder, req) raw, err := a1.srv.ACLAuthorize(recorder, req)
require.NoError(t, err) require.NoError(t, err)

View File

@ -1153,8 +1153,8 @@ func newConsulConfig(runtimeCfg *config.RuntimeConfig, logger hclog.Logger) (*co
if runtimeCfg.RaftTrailingLogs != 0 { if runtimeCfg.RaftTrailingLogs != 0 {
cfg.RaftConfig.TrailingLogs = uint64(runtimeCfg.RaftTrailingLogs) cfg.RaftConfig.TrailingLogs = uint64(runtimeCfg.RaftTrailingLogs)
} }
if runtimeCfg.ACLMasterToken != "" { if runtimeCfg.ACLInitialManagementToken != "" {
cfg.ACLMasterToken = runtimeCfg.ACLMasterToken cfg.ACLInitialManagementToken = runtimeCfg.ACLInitialManagementToken
} }
cfg.ACLTokenReplication = runtimeCfg.ACLTokenReplication cfg.ACLTokenReplication = runtimeCfg.ACLTokenReplication
cfg.ACLsEnabled = runtimeCfg.ACLsEnabled cfg.ACLsEnabled = runtimeCfg.ACLsEnabled
@ -1263,6 +1263,7 @@ func newConsulConfig(runtimeCfg *config.RuntimeConfig, logger hclog.Logger) (*co
} }
cfg.ConfigEntryBootstrap = runtimeCfg.ConfigEntryBootstrap cfg.ConfigEntryBootstrap = runtimeCfg.ConfigEntryBootstrap
cfg.RaftBoltDBConfig = runtimeCfg.RaftBoltDBConfig
// Duplicate our own serf config once to make sure that the duplication // Duplicate our own serf config once to make sure that the duplication
// function does not drift. // function does not drift.

View File

@ -1005,7 +1005,7 @@ func (s *HTTPHandlers) AgentHealthServiceByID(resp http.ResponseWriter, req *htt
} }
notFoundReason := fmt.Sprintf("ServiceId %s not found", sid.String()) notFoundReason := fmt.Sprintf("ServiceId %s not found", sid.String())
if returnTextPlain(req) { if returnTextPlain(req) {
return notFoundReason, CodeWithPayloadError{StatusCode: http.StatusNotFound, Reason: notFoundReason, ContentType: "application/json"} return notFoundReason, CodeWithPayloadError{StatusCode: http.StatusNotFound, Reason: notFoundReason, ContentType: "text/plain"}
} }
return &api.AgentServiceChecksInfo{ return &api.AgentServiceChecksInfo{
AggregatedStatus: api.HealthCritical, AggregatedStatus: api.HealthCritical,
@ -1510,7 +1510,7 @@ func (s *HTTPHandlers) AgentToken(resp http.ResponseWriter, req *http.Request) (
} }
case "acl_agent_master_token", "agent_master", "agent_recovery": case "acl_agent_master_token", "agent_master", "agent_recovery":
s.agent.tokens.UpdateAgentMasterToken(args.Token, token_store.TokenSourceAPI) s.agent.tokens.UpdateAgentRecoveryToken(args.Token, token_store.TokenSourceAPI)
case "acl_replication_token", "replication": case "acl_replication_token", "replication":
s.agent.tokens.UpdateReplicationToken(args.Token, token_store.TokenSourceAPI) s.agent.tokens.UpdateReplicationToken(args.Token, token_store.TokenSourceAPI)

File diff suppressed because it is too large Load Diff

View File

@ -214,10 +214,14 @@ func TestAgent_TokenStore(t *testing.T) {
t.Parallel() t.Parallel()
a := NewTestAgent(t, ` a := NewTestAgent(t, `
acl_token = "user" acl {
acl_agent_token = "agent" tokens {
acl_agent_master_token = "master"`, default = "user"
) agent = "agent"
agent_recovery = "recovery"
}
}
`)
defer a.Shutdown() defer a.Shutdown()
if got, want := a.tokens.UserToken(), "user"; got != want { if got, want := a.tokens.UserToken(), "user"; got != want {
@ -226,7 +230,7 @@ func TestAgent_TokenStore(t *testing.T) {
if got, want := a.tokens.AgentToken(), "agent"; got != want { if got, want := a.tokens.AgentToken(), "agent"; got != want {
t.Fatalf("got %q want %q", got, want) t.Fatalf("got %q want %q", got, want)
} }
if got, want := a.tokens.IsAgentMasterToken("master"), true; got != want { if got, want := a.tokens.IsAgentRecoveryToken("recovery"), true; got != want {
t.Fatalf("got %v want %v", got, want) t.Fatalf("got %v want %v", got, want)
} }
} }
@ -5037,7 +5041,7 @@ func TestAutoConfig_Integration(t *testing.T) {
srv := StartTestAgent(t, TestAgent{Name: "TestAgent-Server", HCL: hclConfig}) srv := StartTestAgent(t, TestAgent{Name: "TestAgent-Server", HCL: hclConfig})
defer srv.Shutdown() defer srv.Shutdown()
testrpc.WaitForTestAgent(t, srv.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken)) testrpc.WaitForTestAgent(t, srv.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))
// sign a JWT token // sign a JWT token
now := time.Now() now := time.Now()
@ -5084,7 +5088,7 @@ func TestAutoConfig_Integration(t *testing.T) {
// when this is successful we managed to get the gossip key and serf addresses to bind to // when this is successful we managed to get the gossip key and serf addresses to bind to
// and then connect. Additionally we would have to have certificates or else the // and then connect. Additionally we would have to have certificates or else the
// verify_incoming config on the server would not let it work. // verify_incoming config on the server would not let it work.
testrpc.WaitForTestAgent(t, client.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken)) testrpc.WaitForTestAgent(t, client.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))
// spot check that we now have an ACL token // spot check that we now have an ACL token
require.NotEmpty(t, client.tokens.AgentToken()) require.NotEmpty(t, client.tokens.AgentToken())
@ -5098,7 +5102,7 @@ func TestAutoConfig_Integration(t *testing.T) {
ca := connect.TestCA(t, nil) ca := connect.TestCA(t, nil)
req := &structs.CARequest{ req := &structs.CARequest{
Datacenter: "dc1", Datacenter: "dc1",
WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken}, WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
Config: &structs.CAConfiguration{ Config: &structs.CAConfiguration{
Provider: "consul", Provider: "consul",
Config: map[string]interface{}{ Config: map[string]interface{}{
@ -5170,7 +5174,7 @@ func TestAgent_AutoEncrypt(t *testing.T) {
srv := StartTestAgent(t, TestAgent{Name: "test-server", HCL: hclConfig}) srv := StartTestAgent(t, TestAgent{Name: "test-server", HCL: hclConfig})
defer srv.Shutdown() defer srv.Shutdown()
testrpc.WaitForTestAgent(t, srv.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken)) testrpc.WaitForTestAgent(t, srv.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))
client := StartTestAgent(t, TestAgent{Name: "test-client", HCL: TestACLConfigWithParams(nil) + ` client := StartTestAgent(t, TestAgent{Name: "test-client", HCL: TestACLConfigWithParams(nil) + `
bootstrap = false bootstrap = false
@ -5193,7 +5197,7 @@ func TestAgent_AutoEncrypt(t *testing.T) {
// when this is successful we managed to get a TLS certificate and are using it for // when this is successful we managed to get a TLS certificate and are using it for
// encrypted RPC connections. // encrypted RPC connections.
testrpc.WaitForTestAgent(t, client.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken)) testrpc.WaitForTestAgent(t, client.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))
// now we need to validate that our certificate has the correct CN // now we need to validate that our certificate has the correct CN
aeCert := client.tlsConfigurator.Cert() aeCert := client.tlsConfigurator.Cert()

View File

@ -65,14 +65,12 @@ func translateConfig(c *pbconfig.Config) config.Config {
} }
result.ACL.Tokens = config.Tokens{ result.ACL.Tokens = config.Tokens{
InitialManagement: stringPtrOrNil(t.InitialManagement),
AgentRecovery: stringPtrOrNil(t.AgentRecovery),
Replication: stringPtrOrNil(t.Replication), Replication: stringPtrOrNil(t.Replication),
Default: stringPtrOrNil(t.Default), Default: stringPtrOrNil(t.Default),
Agent: stringPtrOrNil(t.Agent), Agent: stringPtrOrNil(t.Agent),
ManagedServiceProvider: tokens, ManagedServiceProvider: tokens,
DeprecatedTokens: config.DeprecatedTokens{
Master: stringPtrOrNil(t.Master),
AgentMaster: stringPtrOrNil(t.AgentMaster),
},
} }
} }
} }

View File

@ -69,11 +69,11 @@ func TestTranslateConfig(t *testing.T) {
EnableTokenPersistence: true, EnableTokenPersistence: true,
MSPDisableBootstrap: false, MSPDisableBootstrap: false,
Tokens: &pbconfig.ACLTokens{ Tokens: &pbconfig.ACLTokens{
Master: "99e7e490-6baf-43fc-9010-78b6aa9a6813", InitialManagement: "99e7e490-6baf-43fc-9010-78b6aa9a6813",
Replication: "51308d40-465c-4ac6-a636-7c0747edec89", Replication: "51308d40-465c-4ac6-a636-7c0747edec89",
AgentMaster: "e012e1ea-78a2-41cc-bc8b-231a44196f39", AgentRecovery: "e012e1ea-78a2-41cc-bc8b-231a44196f39",
Default: "8781a3f5-de46-4b45-83e1-c92f4cfd0332", Default: "8781a3f5-de46-4b45-83e1-c92f4cfd0332",
Agent: "ddb8f1b0-8a99-4032-b601-87926bce244e", Agent: "ddb8f1b0-8a99-4032-b601-87926bce244e",
ManagedServiceProvider: []*pbconfig.ACLServiceProviderToken{ ManagedServiceProvider: []*pbconfig.ACLServiceProviderToken{
{ {
AccessorID: "23f37987-7b9e-4e5b-acae-dbc9bc137bae", AccessorID: "23f37987-7b9e-4e5b-acae-dbc9bc137bae",
@ -129,19 +129,17 @@ func TestTranslateConfig(t *testing.T) {
EnableKeyListPolicy: boolPointer(true), EnableKeyListPolicy: boolPointer(true),
EnableTokenPersistence: boolPointer(true), EnableTokenPersistence: boolPointer(true),
Tokens: config.Tokens{ Tokens: config.Tokens{
Replication: stringPointer("51308d40-465c-4ac6-a636-7c0747edec89"), InitialManagement: stringPointer("99e7e490-6baf-43fc-9010-78b6aa9a6813"),
Default: stringPointer("8781a3f5-de46-4b45-83e1-c92f4cfd0332"), AgentRecovery: stringPointer("e012e1ea-78a2-41cc-bc8b-231a44196f39"),
Agent: stringPointer("ddb8f1b0-8a99-4032-b601-87926bce244e"), Replication: stringPointer("51308d40-465c-4ac6-a636-7c0747edec89"),
Default: stringPointer("8781a3f5-de46-4b45-83e1-c92f4cfd0332"),
Agent: stringPointer("ddb8f1b0-8a99-4032-b601-87926bce244e"),
ManagedServiceProvider: []config.ServiceProviderToken{ ManagedServiceProvider: []config.ServiceProviderToken{
{ {
AccessorID: stringPointer("23f37987-7b9e-4e5b-acae-dbc9bc137bae"), AccessorID: stringPointer("23f37987-7b9e-4e5b-acae-dbc9bc137bae"),
SecretID: stringPointer("e28b820a-438e-4e2b-ad24-fe59e6a4914f"), SecretID: stringPointer("e28b820a-438e-4e2b-ad24-fe59e6a4914f"),
}, },
}, },
DeprecatedTokens: config.DeprecatedTokens{
Master: stringPointer("99e7e490-6baf-43fc-9010-78b6aa9a6813"),
AgentMaster: stringPointer("e012e1ea-78a2-41cc-bc8b-231a44196f39"),
},
}, },
}, },
AutoEncrypt: config.AutoEncrypt{ AutoEncrypt: config.AutoEncrypt{

View File

@ -860,18 +860,18 @@ func (b *builder) build() (rt RuntimeConfig, err error) {
ACLDefaultPolicy: stringVal(c.ACL.DefaultPolicy), ACLDefaultPolicy: stringVal(c.ACL.DefaultPolicy),
}, },
ACLEnableKeyListPolicy: boolVal(c.ACL.EnableKeyListPolicy), ACLEnableKeyListPolicy: boolVal(c.ACL.EnableKeyListPolicy),
ACLMasterToken: stringVal(c.ACL.Tokens.InitialManagement), ACLInitialManagementToken: stringVal(c.ACL.Tokens.InitialManagement),
ACLTokenReplication: boolVal(c.ACL.TokenReplication), ACLTokenReplication: boolVal(c.ACL.TokenReplication),
ACLTokens: token.Config{ ACLTokens: token.Config{
DataDir: dataDir, DataDir: dataDir,
EnablePersistence: boolValWithDefault(c.ACL.EnableTokenPersistence, false), EnablePersistence: boolValWithDefault(c.ACL.EnableTokenPersistence, false),
ACLDefaultToken: stringVal(c.ACL.Tokens.Default), ACLDefaultToken: stringVal(c.ACL.Tokens.Default),
ACLAgentToken: stringVal(c.ACL.Tokens.Agent), ACLAgentToken: stringVal(c.ACL.Tokens.Agent),
ACLAgentMasterToken: stringVal(c.ACL.Tokens.AgentRecovery), ACLAgentRecoveryToken: stringVal(c.ACL.Tokens.AgentRecovery),
ACLReplicationToken: stringVal(c.ACL.Tokens.Replication), ACLReplicationToken: stringVal(c.ACL.Tokens.Replication),
}, },
// Autopilot // Autopilot
@ -1094,6 +1094,10 @@ func (b *builder) build() (rt RuntimeConfig, err error) {
rt.UseStreamingBackend = boolValWithDefault(c.UseStreamingBackend, true) rt.UseStreamingBackend = boolValWithDefault(c.UseStreamingBackend, true)
if c.RaftBoltDBConfig != nil {
rt.RaftBoltDBConfig = *c.RaftBoltDBConfig
}
if rt.Cache.EntryFetchMaxBurst <= 0 { if rt.Cache.EntryFetchMaxBurst <= 0 {
return RuntimeConfig{}, fmt.Errorf("cache.entry_fetch_max_burst must be strictly positive, was: %v", rt.Cache.EntryFetchMaxBurst) return RuntimeConfig{}, fmt.Errorf("cache.entry_fetch_max_burst must be strictly positive, was: %v", rt.Cache.EntryFetchMaxBurst)
} }

View File

@ -4,6 +4,8 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/hashicorp/consul/agent/consul"
"github.com/hashicorp/hcl" "github.com/hashicorp/hcl"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
@ -256,6 +258,8 @@ type Config struct {
RPC RPC `mapstructure:"rpc"` RPC RPC `mapstructure:"rpc"`
RaftBoltDBConfig *consul.RaftBoltDBConfig `mapstructure:"raft_boltdb"`
// UseStreamingBackend instead of blocking queries for service health and // UseStreamingBackend instead of blocking queries for service health and
// any other endpoints which support streaming. // any other endpoints which support streaming.
UseStreamingBackend *bool `mapstructure:"use_streaming_backend"` UseStreamingBackend *bool `mapstructure:"use_streaming_backend"`

View File

@ -110,8 +110,8 @@ func TestLoad_DeprecatedConfig_ACLMasterTokens(t *testing.T) {
require.ElementsMatch(expectWarns, result.Warnings) require.ElementsMatch(expectWarns, result.Warnings)
rt := result.RuntimeConfig rt := result.RuntimeConfig
require.Equal("token1", rt.ACLMasterToken) require.Equal("token1", rt.ACLInitialManagementToken)
require.Equal("token2", rt.ACLTokens.ACLAgentMasterToken) require.Equal("token2", rt.ACLTokens.ACLAgentRecoveryToken)
}) })
t.Run("embedded in tokens struct", func(t *testing.T) { t.Run("embedded in tokens struct", func(t *testing.T) {
@ -141,8 +141,8 @@ func TestLoad_DeprecatedConfig_ACLMasterTokens(t *testing.T) {
require.ElementsMatch(expectWarns, result.Warnings) require.ElementsMatch(expectWarns, result.Warnings)
rt := result.RuntimeConfig rt := result.RuntimeConfig
require.Equal("token1", rt.ACLMasterToken) require.Equal("token1", rt.ACLInitialManagementToken)
require.Equal("token2", rt.ACLTokens.ACLAgentMasterToken) require.Equal("token2", rt.ACLTokens.ACLAgentRecoveryToken)
}) })
t.Run("both", func(t *testing.T) { t.Run("both", func(t *testing.T) {
@ -169,7 +169,7 @@ func TestLoad_DeprecatedConfig_ACLMasterTokens(t *testing.T) {
require.NoError(err) require.NoError(err)
rt := result.RuntimeConfig rt := result.RuntimeConfig
require.Equal("token3", rt.ACLMasterToken) require.Equal("token3", rt.ACLInitialManagementToken)
require.Equal("token4", rt.ACLTokens.ACLAgentMasterToken) require.Equal("token4", rt.ACLTokens.ACLAgentRecoveryToken)
}) })
} }

View File

@ -73,12 +73,12 @@ type RuntimeConfig struct {
// hcl: acl.enable_key_list_policy = (true|false) // hcl: acl.enable_key_list_policy = (true|false)
ACLEnableKeyListPolicy bool ACLEnableKeyListPolicy bool
// ACLMasterToken is used to bootstrap the ACL system. It should be specified // ACLInitialManagementToken is used to bootstrap the ACL system. It should be specified
// on the servers in the PrimaryDatacenter. When the leader comes online, it ensures // on the servers in the PrimaryDatacenter. When the leader comes online, it ensures
// that the Master token is available. This provides the initial token. // that the initial management token is available. This provides the initial token.
// //
// hcl: acl.tokens.initial_management = string // hcl: acl.tokens.initial_management = string
ACLMasterToken string ACLInitialManagementToken string
// ACLtokenReplication is used to indicate that both tokens and policies // ACLtokenReplication is used to indicate that both tokens and policies
// should be replicated instead of just policies // should be replicated instead of just policies
@ -943,6 +943,8 @@ type RuntimeConfig struct {
// hcl: raft_trailing_logs = int // hcl: raft_trailing_logs = int
RaftTrailingLogs int RaftTrailingLogs int
RaftBoltDBConfig consul.RaftBoltDBConfig
// ReconnectTimeoutLAN specifies the amount of time to wait to reconnect with // ReconnectTimeoutLAN specifies the amount of time to wait to reconnect with
// another agent before deciding it's permanently gone. This can be used to // another agent before deciding it's permanently gone. This can be used to
// control the time it takes to reap failed nodes from the cluster. // control the time it takes to reap failed nodes from the cluster.

View File

@ -23,6 +23,7 @@ import (
"github.com/google/go-cmp/cmp/cmpopts" "github.com/google/go-cmp/cmp/cmpopts"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/hashicorp/consul/acl"
"github.com/hashicorp/consul/agent/cache" "github.com/hashicorp/consul/agent/cache"
"github.com/hashicorp/consul/agent/checks" "github.com/hashicorp/consul/agent/checks"
"github.com/hashicorp/consul/agent/consul" "github.com/hashicorp/consul/agent/consul"
@ -4085,6 +4086,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
Service: "carrot", Service: "carrot",
ServiceSubset: "kale", ServiceSubset: "kale",
Namespace: "leek", Namespace: "leek",
Partition: acl.DefaultPartitionName,
PrefixRewrite: "/alternate", PrefixRewrite: "/alternate",
RequestTimeout: 99 * time.Second, RequestTimeout: 99 * time.Second,
NumRetries: 12345, NumRetries: 12345,
@ -5339,12 +5341,12 @@ func TestLoad_FullConfig(t *testing.T) {
// user configurable values // user configurable values
ACLTokens: token.Config{ ACLTokens: token.Config{
EnablePersistence: true, EnablePersistence: true,
DataDir: dataDir, DataDir: dataDir,
ACLDefaultToken: "418fdff1", ACLDefaultToken: "418fdff1",
ACLAgentToken: "bed2377c", ACLAgentToken: "bed2377c",
ACLAgentMasterToken: "1dba6aba", ACLAgentRecoveryToken: "1dba6aba",
ACLReplicationToken: "5795983a", ACLReplicationToken: "5795983a",
}, },
ACLsEnabled: true, ACLsEnabled: true,
@ -5361,7 +5363,7 @@ func TestLoad_FullConfig(t *testing.T) {
ACLRoleTTL: 9876 * time.Second, ACLRoleTTL: 9876 * time.Second,
}, },
ACLEnableKeyListPolicy: true, ACLEnableKeyListPolicy: true,
ACLMasterToken: "3820e09a", ACLInitialManagementToken: "3820e09a",
ACLTokenReplication: true, ACLTokenReplication: true,
AdvertiseAddrLAN: ipAddr("17.99.29.16"), AdvertiseAddrLAN: ipAddr("17.99.29.16"),
AdvertiseAddrWAN: ipAddr("78.63.37.19"), AdvertiseAddrWAN: ipAddr("78.63.37.19"),
@ -6015,6 +6017,7 @@ func TestLoad_FullConfig(t *testing.T) {
"args": []interface{}{"dltjDJ2a", "flEa7C2d"}, "args": []interface{}{"dltjDJ2a", "flEa7C2d"},
}, },
}, },
RaftBoltDBConfig: consul.RaftBoltDBConfig{NoFreelistSync: true},
} }
entFullRuntimeConfig(expected) entFullRuntimeConfig(expected)

View File

@ -1,6 +1,6 @@
{ {
"ACLEnableKeyListPolicy": false, "ACLEnableKeyListPolicy": false,
"ACLMasterToken": "hidden", "ACLInitialManagementToken": "hidden",
"ACLResolverSettings": { "ACLResolverSettings": {
"ACLDefaultPolicy": "", "ACLDefaultPolicy": "",
"ACLDownPolicy": "", "ACLDownPolicy": "",
@ -14,7 +14,7 @@
}, },
"ACLTokenReplication": false, "ACLTokenReplication": false,
"ACLTokens": { "ACLTokens": {
"ACLAgentMasterToken": "hidden", "ACLAgentRecoveryToken": "hidden",
"ACLAgentToken": "hidden", "ACLAgentToken": "hidden",
"ACLDefaultToken": "hidden", "ACLDefaultToken": "hidden",
"ACLReplicationToken": "hidden", "ACLReplicationToken": "hidden",
@ -252,6 +252,9 @@
"RPCMaxConnsPerClient": 0, "RPCMaxConnsPerClient": 0,
"RPCProtocol": 0, "RPCProtocol": 0,
"RPCRateLimit": 0, "RPCRateLimit": 0,
"RaftBoltDBConfig": {
"NoFreelistSync": false
},
"RaftProtocol": 3, "RaftProtocol": 3,
"RaftSnapshotInterval": "0s", "RaftSnapshotInterval": "0s",
"RaftSnapshotThreshold": 0, "RaftSnapshotThreshold": 0,
@ -421,4 +424,4 @@
"Version": "", "Version": "",
"VersionPrerelease": "", "VersionPrerelease": "",
"Watches": [] "Watches": []
} }

View File

@ -328,6 +328,9 @@ raft_protocol = 3
raft_snapshot_threshold = 16384 raft_snapshot_threshold = 16384
raft_snapshot_interval = "30s" raft_snapshot_interval = "30s"
raft_trailing_logs = 83749 raft_trailing_logs = 83749
raft_boltdb {
NoFreelistSync = true
}
read_replica = true read_replica = true
reconnect_timeout = "23739s" reconnect_timeout = "23739s"
reconnect_timeout_wan = "26694s" reconnect_timeout_wan = "26694s"

View File

@ -326,6 +326,9 @@
"raft_snapshot_threshold": 16384, "raft_snapshot_threshold": 16384,
"raft_snapshot_interval": "30s", "raft_snapshot_interval": "30s",
"raft_trailing_logs": 83749, "raft_trailing_logs": 83749,
"raft_boltdb": {
"NoFreelistSync": true
},
"read_replica": true, "read_replica": true,
"reconnect_timeout": "23739s", "reconnect_timeout": "23739s",
"reconnect_timeout_wan": "26694s", "reconnect_timeout_wan": "26694s",

View File

@ -1053,7 +1053,7 @@ func (r *ACLResolver) resolveLocallyManagedToken(token string) (structs.ACLIdent
return nil, nil, false return nil, nil, false
} }
if r.tokens.IsAgentMasterToken(token) { if r.tokens.IsAgentRecoveryToken(token) {
return structs.NewAgentMasterTokenIdentity(r.config.NodeName, token), r.agentMasterAuthz, true return structs.NewAgentMasterTokenIdentity(r.config.NodeName, token), r.agentMasterAuthz, true
} }
@ -1465,11 +1465,13 @@ func (f *aclFilter) filterIntentions(ixns *structs.Intentions) bool {
} }
// filterNodeDump is used to filter through all parts of a node dump and // filterNodeDump is used to filter through all parts of a node dump and
// remove elements the provided ACL token cannot access. // remove elements the provided ACL token cannot access. Returns true if
func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) { // any elements were removed.
func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) bool {
nd := *dump nd := *dump
var authzContext acl.AuthorizerContext var authzContext acl.AuthorizerContext
var removed bool
for i := 0; i < len(nd); i++ { for i := 0; i < len(nd); i++ {
info := nd[i] info := nd[i]
@ -1477,6 +1479,7 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
info.FillAuthzContext(&authzContext) info.FillAuthzContext(&authzContext)
if node := info.Node; !f.allowNode(node, &authzContext) { if node := info.Node; !f.allowNode(node, &authzContext) {
f.logger.Debug("dropping node from result due to ACLs", "node", structs.NodeNameString(node, info.GetEnterpriseMeta())) f.logger.Debug("dropping node from result due to ACLs", "node", structs.NodeNameString(node, info.GetEnterpriseMeta()))
removed = true
nd = append(nd[:i], nd[i+1:]...) nd = append(nd[:i], nd[i+1:]...)
i-- i--
continue continue
@ -1490,6 +1493,7 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
continue continue
} }
f.logger.Debug("dropping service from result due to ACLs", "service", svc) f.logger.Debug("dropping service from result due to ACLs", "service", svc)
removed = true
info.Services = append(info.Services[:j], info.Services[j+1:]...) info.Services = append(info.Services[:j], info.Services[j+1:]...)
j-- j--
} }
@ -1502,17 +1506,21 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
continue continue
} }
f.logger.Debug("dropping check from result due to ACLs", "check", chk.CheckID) f.logger.Debug("dropping check from result due to ACLs", "check", chk.CheckID)
removed = true
info.Checks = append(info.Checks[:j], info.Checks[j+1:]...) info.Checks = append(info.Checks[:j], info.Checks[j+1:]...)
j-- j--
} }
} }
*dump = nd *dump = nd
return removed
} }
// filterServiceDump is used to filter nodes based on ACL rules. // filterServiceDump is used to filter nodes based on ACL rules. Returns true
func (f *aclFilter) filterServiceDump(services *structs.ServiceDump) { // if any elements were removed.
func (f *aclFilter) filterServiceDump(services *structs.ServiceDump) bool {
svcs := *services svcs := *services
var authzContext acl.AuthorizerContext var authzContext acl.AuthorizerContext
var removed bool
for i := 0; i < len(svcs); i++ { for i := 0; i < len(svcs); i++ {
service := svcs[i] service := svcs[i]
@ -1530,10 +1538,12 @@ func (f *aclFilter) filterServiceDump(services *structs.ServiceDump) {
} }
f.logger.Debug("dropping service from result due to ACLs", "service", service.GatewayService.Service) f.logger.Debug("dropping service from result due to ACLs", "service", service.GatewayService.Service)
removed = true
svcs = append(svcs[:i], svcs[i+1:]...) svcs = append(svcs[:i], svcs[i+1:]...)
i-- i--
} }
*services = svcs *services = svcs
return removed
} }
// filterNodes is used to filter through all parts of a node list and remove // filterNodes is used to filter through all parts of a node list and remove
@ -1592,8 +1602,10 @@ func (f *aclFilter) redactPreparedQueryTokens(query **structs.PreparedQuery) {
// filterPreparedQueries is used to filter prepared queries based on ACL rules. // filterPreparedQueries is used to filter prepared queries based on ACL rules.
// We prune entries the user doesn't have access to, and we redact any tokens // We prune entries the user doesn't have access to, and we redact any tokens
// if the user doesn't have a management token. // if the user doesn't have a management token. Returns true if any (named)
func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) { // queries were removed - un-named queries are meant to be ephemeral and can
// only be enumerated by a management token
func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) bool {
var authzContext acl.AuthorizerContext var authzContext acl.AuthorizerContext
structs.DefaultEnterpriseMetaInDefaultPartition().FillAuthzContext(&authzContext) structs.DefaultEnterpriseMetaInDefaultPartition().FillAuthzContext(&authzContext)
// Management tokens can see everything with no filtering. // Management tokens can see everything with no filtering.
@ -1601,17 +1613,22 @@ func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
// the 1.4 ACL rewrite. The global-management token will provide unrestricted query privileges // the 1.4 ACL rewrite. The global-management token will provide unrestricted query privileges
// so asking for ACLWrite should be unnecessary. // so asking for ACLWrite should be unnecessary.
if f.authorizer.ACLWrite(&authzContext) == acl.Allow { if f.authorizer.ACLWrite(&authzContext) == acl.Allow {
return return false
} }
// Otherwise, we need to see what the token has access to. // Otherwise, we need to see what the token has access to.
var namedQueriesRemoved bool
ret := make(structs.PreparedQueries, 0, len(*queries)) ret := make(structs.PreparedQueries, 0, len(*queries))
for _, query := range *queries { for _, query := range *queries {
// If no prefix ACL applies to this query then filter it, since // If no prefix ACL applies to this query then filter it, since
// we know at this point the user doesn't have a management // we know at this point the user doesn't have a management
// token, otherwise see what the policy says. // token, otherwise see what the policy says.
prefix, ok := query.GetACLPrefix() prefix, hasName := query.GetACLPrefix()
if !ok || f.authorizer.PreparedQueryRead(prefix, &authzContext) != acl.Allow { switch {
case hasName && f.authorizer.PreparedQueryRead(prefix, &authzContext) != acl.Allow:
namedQueriesRemoved = true
fallthrough
case !hasName:
f.logger.Debug("dropping prepared query from result due to ACLs", "query", query.ID) f.logger.Debug("dropping prepared query from result due to ACLs", "query", query.ID)
continue continue
} }
@ -1623,6 +1640,7 @@ func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
ret = append(ret, final) ret = append(ret, final)
} }
*queries = ret *queries = ret
return namedQueriesRemoved
} }
func (f *aclFilter) filterToken(token **structs.ACLToken) { func (f *aclFilter) filterToken(token **structs.ACLToken) {
@ -1815,8 +1833,8 @@ func (f *aclFilter) filterServiceList(services *structs.ServiceList) bool {
// filterGatewayServices is used to filter gateway to service mappings based on ACL rules. // filterGatewayServices is used to filter gateway to service mappings based on ACL rules.
// Returns true if any elements were removed. // Returns true if any elements were removed.
func (f *aclFilter) filterGatewayServices(mappings *structs.GatewayServices) bool { func (f *aclFilter) filterGatewayServices(mappings *structs.GatewayServices) bool {
var removed bool
ret := make(structs.GatewayServices, 0, len(*mappings)) ret := make(structs.GatewayServices, 0, len(*mappings))
var removed bool
for _, s := range *mappings { for _, s := range *mappings {
// This filter only checks ServiceRead on the linked service. // This filter only checks ServiceRead on the linked service.
// ServiceRead on the gateway is checked in the GatewayServices endpoint before filtering. // ServiceRead on the gateway is checked in the GatewayServices endpoint before filtering.
@ -1847,6 +1865,9 @@ func filterACLWithAuthorizer(logger hclog.Logger, authorizer acl.Authorizer, sub
case *structs.IndexedCheckServiceNodes: case *structs.IndexedCheckServiceNodes:
v.QueryMeta.ResultsFilteredByACLs = filt.filterCheckServiceNodes(&v.Nodes) v.QueryMeta.ResultsFilteredByACLs = filt.filterCheckServiceNodes(&v.Nodes)
case *structs.PreparedQueryExecuteResponse:
v.QueryMeta.ResultsFilteredByACLs = filt.filterCheckServiceNodes(&v.Nodes)
case *structs.IndexedServiceTopology: case *structs.IndexedServiceTopology:
filtered := filt.filterServiceTopology(v.ServiceTopology) filtered := filt.filterServiceTopology(v.ServiceTopology)
if filtered { if filtered {
@ -1867,10 +1888,10 @@ func filterACLWithAuthorizer(logger hclog.Logger, authorizer acl.Authorizer, sub
v.QueryMeta.ResultsFilteredByACLs = filt.filterIntentions(&v.Intentions) v.QueryMeta.ResultsFilteredByACLs = filt.filterIntentions(&v.Intentions)
case *structs.IndexedNodeDump: case *structs.IndexedNodeDump:
filt.filterNodeDump(&v.Dump) v.QueryMeta.ResultsFilteredByACLs = filt.filterNodeDump(&v.Dump)
case *structs.IndexedServiceDump: case *structs.IndexedServiceDump:
filt.filterServiceDump(&v.Dump) v.QueryMeta.ResultsFilteredByACLs = filt.filterServiceDump(&v.Dump)
case *structs.IndexedNodes: case *structs.IndexedNodes:
v.QueryMeta.ResultsFilteredByACLs = filt.filterNodes(&v.Nodes) v.QueryMeta.ResultsFilteredByACLs = filt.filterNodes(&v.Nodes)
@ -1891,7 +1912,7 @@ func filterACLWithAuthorizer(logger hclog.Logger, authorizer acl.Authorizer, sub
v.QueryMeta.ResultsFilteredByACLs = filt.filterSessions(&v.Sessions) v.QueryMeta.ResultsFilteredByACLs = filt.filterSessions(&v.Sessions)
case *structs.IndexedPreparedQueries: case *structs.IndexedPreparedQueries:
filt.filterPreparedQueries(&v.Queries) v.QueryMeta.ResultsFilteredByACLs = filt.filterPreparedQueries(&v.Queries)
case **structs.PreparedQuery: case **structs.PreparedQuery:
filt.redactPreparedQueryTokens(v) filt.redactPreparedQueryTokens(v)
@ -1959,6 +1980,6 @@ func filterACL(r *ACLResolver, token string, subj interface{}) error {
type partitionInfoNoop struct{} type partitionInfoNoop struct{}
func (p *partitionInfoNoop) ExportsForPartition(partition string) acl.PartitionExports { func (p *partitionInfoNoop) ExportsForPartition(partition string) acl.ExportedServices {
return acl.PartitionExports{} return acl.ExportedServices{}
} }

View File

@ -32,7 +32,7 @@ func TestACLEndpoint_BootstrapTokens(t *testing.T) {
t.Parallel() t.Parallel()
dir, srv, codec := testACLServerWithConfig(t, func(c *Config) { dir, srv, codec := testACLServerWithConfig(t, func(c *Config) {
// remove this as we are bootstrapping // remove this as we are bootstrapping
c.ACLMasterToken = "" c.ACLInitialManagementToken = ""
}, false) }, false)
waitForLeaderEstablishment(t, srv) waitForLeaderEstablishment(t, srv)

View File

@ -301,7 +301,7 @@ func TestACLReplication_Tokens(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
defer s1.Shutdown() defer s1.Shutdown()
@ -513,7 +513,7 @@ func TestACLReplication_Policies(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
defer s1.Shutdown() defer s1.Shutdown()
@ -633,7 +633,7 @@ func TestACLReplication_TokensRedacted(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
defer s1.Shutdown() defer s1.Shutdown()
@ -783,7 +783,7 @@ func TestACLReplication_AllTypes(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
defer s1.Shutdown() defer s1.Shutdown()

View File

@ -2752,6 +2752,108 @@ func TestACL_filterCheckServiceNodes(t *testing.T) {
}) })
} }
func TestACL_filterPreparedQueryExecuteResponse(t *testing.T) {
t.Parallel()
logger := hclog.NewNullLogger()
makeList := func() *structs.PreparedQueryExecuteResponse {
return &structs.PreparedQueryExecuteResponse{
Nodes: structs.CheckServiceNodes{
{
Node: &structs.Node{
Node: "node1",
},
Service: &structs.NodeService{
ID: "foo",
Service: "foo",
},
Checks: structs.HealthChecks{
{
Node: "node1",
CheckID: "check1",
ServiceName: "foo",
},
},
},
},
}
}
t.Run("allowed", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
service "foo" {
policy = "read"
}
node "node1" {
policy = "read"
}
`, acl.SyntaxLegacy, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
require.Len(list.Nodes, 1)
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
})
t.Run("allowed to read the service, but not the node", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
service "foo" {
policy = "read"
}
`, acl.SyntaxLegacy, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
require.Empty(list.Nodes)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("allowed to read the node, but not the service", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
node "node1" {
policy = "read"
}
`, acl.SyntaxLegacy, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
require.Empty(list.Nodes)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("denied", func(t *testing.T) {
require := require.New(t)
list := makeList()
filterACLWithAuthorizer(logger, acl.DenyAll(), list)
require.Empty(list.Nodes)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
}
func TestACL_filterServiceTopology(t *testing.T) { func TestACL_filterServiceTopology(t *testing.T) {
t.Parallel() t.Parallel()
// Create some nodes. // Create some nodes.
@ -3024,107 +3126,105 @@ func TestACL_filterSessions(t *testing.T) {
func TestACL_filterNodeDump(t *testing.T) { func TestACL_filterNodeDump(t *testing.T) {
t.Parallel() t.Parallel()
// Create a node dump.
fill := func() structs.NodeDump { logger := hclog.NewNullLogger()
return structs.NodeDump{
&structs.NodeInfo{ makeList := func() *structs.IndexedNodeDump {
Node: "node1", return &structs.IndexedNodeDump{
Services: []*structs.NodeService{ Dump: structs.NodeDump{
{ {
ID: "foo", Node: "node1",
Service: "foo", Services: []*structs.NodeService{
{
ID: "foo",
Service: "foo",
},
}, },
}, Checks: []*structs.HealthCheck{
Checks: []*structs.HealthCheck{ {
{ Node: "node1",
Node: "node1", CheckID: "check1",
CheckID: "check1", ServiceName: "foo",
ServiceName: "foo", },
}, },
}, },
}, },
} }
} }
// Try permissive filtering. t.Run("allowed", func(t *testing.T) {
{ require := require.New(t)
dump := fill()
filt := newACLFilter(acl.AllowAll(), nil)
filt.filterNodeDump(&dump)
if len(dump) != 1 {
t.Fatalf("bad: %#v", dump)
}
if len(dump[0].Services) != 1 {
t.Fatalf("bad: %#v", dump[0].Services)
}
if len(dump[0].Checks) != 1 {
t.Fatalf("bad: %#v", dump[0].Checks)
}
}
// Try restrictive filtering. policy, err := acl.NewPolicyFromSource(`
{ service "foo" {
dump := fill() policy = "read"
filt := newACLFilter(acl.DenyAll(), nil) }
filt.filterNodeDump(&dump) node "node1" {
if len(dump) != 0 { policy = "read"
t.Fatalf("bad: %#v", dump) }
} `, acl.SyntaxLegacy, nil, nil)
} require.NoError(err)
// Allowed to see the service but not the node. authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
policy, err := acl.NewPolicyFromSource(` require.NoError(err)
service "foo" {
policy = "read"
}
`, acl.SyntaxLegacy, nil, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
// But the node will block it. list := makeList()
{ filterACLWithAuthorizer(logger, authz, list)
dump := fill()
filt := newACLFilter(perms, nil)
filt.filterNodeDump(&dump)
if len(dump) != 0 {
t.Fatalf("bad: %#v", dump)
}
}
// Chain on access to the node. require.Len(list.Dump, 1)
policy, err = acl.NewPolicyFromSource(` require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
node "node1" { })
policy = "read"
}
`, acl.SyntaxLegacy, nil, nil)
if err != nil {
t.Fatalf("err %v", err)
}
perms, err = acl.NewPolicyAuthorizerWithDefaults(perms, []*acl.Policy{policy}, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
// Now it should go through. t.Run("allowed to read the service, but not the node", func(t *testing.T) {
{ require := require.New(t)
dump := fill()
filt := newACLFilter(perms, nil) policy, err := acl.NewPolicyFromSource(`
filt.filterNodeDump(&dump) service "foo" {
if len(dump) != 1 { policy = "read"
t.Fatalf("bad: %#v", dump) }
} `, acl.SyntaxLegacy, nil, nil)
if len(dump[0].Services) != 1 { require.NoError(err)
t.Fatalf("bad: %#v", dump[0].Services)
} authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
if len(dump[0].Checks) != 1 { require.NoError(err)
t.Fatalf("bad: %#v", dump[0].Checks)
} list := makeList()
} filterACLWithAuthorizer(logger, authz, list)
require.Empty(list.Dump)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("allowed to read the node, but not the service", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
node "node1" {
policy = "read"
}
`, acl.SyntaxLegacy, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
require.Len(list.Dump, 1)
require.Empty(list.Dump[0].Services)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("denied", func(t *testing.T) {
require := require.New(t)
list := makeList()
filterACLWithAuthorizer(logger, acl.DenyAll(), list)
require.Empty(list.Dump)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
} }
func TestACL_filterNodes(t *testing.T) { func TestACL_filterNodes(t *testing.T) {
@ -3155,6 +3255,277 @@ func TestACL_filterNodes(t *testing.T) {
require.Len(nodes, 0) require.Len(nodes, 0)
} }
func TestACL_filterIndexedNodesWithGateways(t *testing.T) {
t.Parallel()
logger := hclog.NewNullLogger()
makeList := func() *structs.IndexedNodesWithGateways {
return &structs.IndexedNodesWithGateways{
Nodes: structs.CheckServiceNodes{
{
Node: &structs.Node{
Node: "node1",
},
Service: &structs.NodeService{
ID: "foo",
Service: "foo",
},
Checks: structs.HealthChecks{
{
Node: "node1",
CheckID: "check1",
ServiceName: "foo",
},
},
},
},
Gateways: structs.GatewayServices{
{Service: structs.ServiceNameFromString("foo")},
{Service: structs.ServiceNameFromString("bar")},
},
}
}
t.Run("allowed", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
service "foo" {
policy = "read"
}
service "bar" {
policy = "read"
}
node "node1" {
policy = "read"
}
`, acl.SyntaxLegacy, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
require.Len(list.Nodes, 1)
require.Len(list.Gateways, 2)
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
})
t.Run("not allowed to read the node", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
service "foo" {
policy = "read"
}
service "bar" {
policy = "read"
}
`, acl.SyntaxLegacy, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
require.Empty(list.Nodes)
require.Len(list.Gateways, 2)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("allowed to read the node, but not the service", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
node "node1" {
policy = "read"
}
service "bar" {
policy = "read"
}
`, acl.SyntaxLegacy, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
require.Empty(list.Nodes)
require.Len(list.Gateways, 1)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("not allowed to read the other gatway service", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
service "foo" {
policy = "read"
}
node "node1" {
policy = "read"
}
`, acl.SyntaxLegacy, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
require.Len(list.Nodes, 1)
require.Len(list.Gateways, 1)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("denied", func(t *testing.T) {
require := require.New(t)
list := makeList()
filterACLWithAuthorizer(logger, acl.DenyAll(), list)
require.Empty(list.Nodes)
require.Empty(list.Gateways)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
}
func TestACL_filterIndexedServiceDump(t *testing.T) {
t.Parallel()
logger := hclog.NewNullLogger()
makeList := func() *structs.IndexedServiceDump {
return &structs.IndexedServiceDump{
Dump: structs.ServiceDump{
{
Node: &structs.Node{
Node: "node1",
},
Service: &structs.NodeService{
Service: "foo",
},
GatewayService: &structs.GatewayService{
Service: structs.ServiceNameFromString("foo"),
Gateway: structs.ServiceNameFromString("foo-gateway"),
},
},
// No node information.
{
GatewayService: &structs.GatewayService{
Service: structs.ServiceNameFromString("bar"),
Gateway: structs.ServiceNameFromString("bar-gateway"),
},
},
},
}
}
t.Run("allowed", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
node "node1" {
policy = "read"
}
service_prefix "foo" {
policy = "read"
}
service_prefix "bar" {
policy = "read"
}
`, acl.SyntaxCurrent, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
require.Len(list.Dump, 2)
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
})
t.Run("not allowed to access node", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
service_prefix "foo" {
policy = "read"
}
service_prefix "bar" {
policy = "read"
}
`, acl.SyntaxCurrent, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
require.Len(list.Dump, 1)
require.Equal("bar", list.Dump[0].GatewayService.Service.Name)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("not allowed to access service", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
node "node1" {
policy = "read"
}
service "foo-gateway" {
policy = "read"
}
`, acl.SyntaxCurrent, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
require.Empty(list.Dump)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("not allowed to access gateway", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
node "node1" {
policy = "read"
}
service "foo" {
policy = "read"
}
`, acl.SyntaxCurrent, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
require.Empty(list.Dump)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
}
func TestACL_filterDatacenterCheckServiceNodes(t *testing.T) { func TestACL_filterDatacenterCheckServiceNodes(t *testing.T) {
t.Parallel() t.Parallel()
@ -3353,70 +3724,97 @@ func TestFilterACL_redactTokenSecrets(t *testing.T) {
func TestACL_filterPreparedQueries(t *testing.T) { func TestACL_filterPreparedQueries(t *testing.T) {
t.Parallel() t.Parallel()
queries := structs.PreparedQueries{
&structs.PreparedQuery{ logger := hclog.NewNullLogger()
ID: "f004177f-2c28-83b7-4229-eacc25fe55d1",
}, makeList := func() *structs.IndexedPreparedQueries {
&structs.PreparedQuery{ return &structs.IndexedPreparedQueries{
ID: "f004177f-2c28-83b7-4229-eacc25fe55d2", Queries: structs.PreparedQueries{
Name: "query-with-no-token", {ID: "f004177f-2c28-83b7-4229-eacc25fe55d1"},
}, {
&structs.PreparedQuery{ ID: "f004177f-2c28-83b7-4229-eacc25fe55d2",
ID: "f004177f-2c28-83b7-4229-eacc25fe55d3", Name: "query-with-no-token",
Name: "query-with-a-token", },
Token: "root", {
}, ID: "f004177f-2c28-83b7-4229-eacc25fe55d3",
Name: "query-with-a-token",
Token: "root",
},
},
}
} }
expected := structs.PreparedQueries{ t.Run("management token", func(t *testing.T) {
&structs.PreparedQuery{ require := require.New(t)
ID: "f004177f-2c28-83b7-4229-eacc25fe55d1",
},
&structs.PreparedQuery{
ID: "f004177f-2c28-83b7-4229-eacc25fe55d2",
Name: "query-with-no-token",
},
&structs.PreparedQuery{
ID: "f004177f-2c28-83b7-4229-eacc25fe55d3",
Name: "query-with-a-token",
Token: "root",
},
}
// Try permissive filtering with a management token. This will allow the list := makeList()
// embedded token to be seen. filterACLWithAuthorizer(logger, acl.ManageAll(), list)
filt := newACLFilter(acl.ManageAll(), nil)
filt.filterPreparedQueries(&queries)
if !reflect.DeepEqual(queries, expected) {
t.Fatalf("bad: %#v", queries)
}
// Hang on to the entry with a token, which needs to survive the next // Check we get the un-named query.
// operation. require.Len(list.Queries, 3)
original := queries[2]
// Now try permissive filtering with a client token, which should cause // Check we get the un-redacted token.
// the embedded token to get redacted, and the query with no name to get require.Equal("root", list.Queries[2].Token)
// filtered out.
filt = newACLFilter(acl.AllowAll(), nil)
filt.filterPreparedQueries(&queries)
expected[2].Token = redactedToken
expected = append(structs.PreparedQueries{}, expected[1], expected[2])
if !reflect.DeepEqual(queries, expected) {
t.Fatalf("bad: %#v", queries)
}
// Make sure that the original object didn't lose its token. require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
if original.Token != "root" { })
t.Fatalf("bad token: %s", original.Token)
}
// Now try restrictive filtering. t.Run("permissive filtering", func(t *testing.T) {
filt = newACLFilter(acl.DenyAll(), nil) require := require.New(t)
filt.filterPreparedQueries(&queries)
if len(queries) != 0 { list := makeList()
t.Fatalf("bad: %#v", queries) queryWithToken := list.Queries[2]
}
filterACLWithAuthorizer(logger, acl.AllowAll(), list)
// Check the un-named query is filtered out.
require.Len(list.Queries, 2)
// Check the token is redacted.
require.Equal(redactedToken, list.Queries[1].Token)
// Check the original object is unmodified.
require.Equal("root", queryWithToken.Token)
// ResultsFilteredByACLs should not include un-named queries, which are only
// readable by a management token.
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
})
t.Run("limited access", func(t *testing.T) {
require := require.New(t)
policy, err := acl.NewPolicyFromSource(`
query "query-with-a-token" {
policy = "read"
}
`, acl.SyntaxLegacy, nil, nil)
require.NoError(err)
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
require.NoError(err)
list := makeList()
filterACLWithAuthorizer(logger, authz, list)
// Check we only get the query we have access to.
require.Len(list.Queries, 1)
// Check the token is redacted.
require.Equal(redactedToken, list.Queries[0].Token)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("restrictive filtering", func(t *testing.T) {
require := require.New(t)
list := makeList()
filterACLWithAuthorizer(logger, acl.DenyAll(), list)
require.Empty(list.Queries)
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
} }
func TestACL_filterServiceList(t *testing.T) { func TestACL_filterServiceList(t *testing.T) {
@ -3622,7 +4020,7 @@ func TestACLResolver_AgentMaster(t *testing.T) {
cfg.DisableDuration = 0 cfg.DisableDuration = 0
}) })
tokens.UpdateAgentMasterToken("9a184a11-5599-459e-b71a-550e5f9a5a23", token.TokenSourceConfig) tokens.UpdateAgentRecoveryToken("9a184a11-5599-459e-b71a-550e5f9a5a23", token.TokenSourceConfig)
ident, authz, err := r.ResolveTokenToIdentityAndAuthorizer("9a184a11-5599-459e-b71a-550e5f9a5a23") ident, authz, err := r.ResolveTokenToIdentityAndAuthorizer("9a184a11-5599-459e-b71a-550e5f9a5a23")
require.NoError(t, err) require.NoError(t, err)

View File

@ -44,7 +44,7 @@ func testACLTokenReap_Primary(t *testing.T, local, global bool) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
c.ACLTokenMaxExpirationTTL = 8 * time.Second c.ACLTokenMaxExpirationTTL = 8 * time.Second
}) })

View File

@ -71,76 +71,8 @@ type Catalog struct {
logger hclog.Logger logger hclog.Logger
} }
// nodePreApply does the verification of a node before it is applied to Raft. // Register a service and/or check(s) in a node, creating the node if it doesn't exist.
func nodePreApply(nodeName, nodeID string) error { // It is valid to pass no service or checks to simply create the node itself.
if nodeName == "" {
return fmt.Errorf("Must provide node")
}
if nodeID != "" {
if _, err := uuid.ParseUUID(nodeID); err != nil {
return fmt.Errorf("Bad node ID: %v", err)
}
}
return nil
}
func servicePreApply(service *structs.NodeService, authz acl.Authorizer, authzCtxFill func(*acl.AuthorizerContext)) error {
// Validate the service. This is in addition to the below since
// the above just hasn't been moved over yet. We should move it over
// in time.
if err := service.Validate(); err != nil {
return err
}
// If no service id, but service name, use default
if service.ID == "" && service.Service != "" {
service.ID = service.Service
}
// Verify ServiceName provided if ID.
if service.ID != "" && service.Service == "" {
return fmt.Errorf("Must provide service name with ID")
}
// Check the service address here and in the agent endpoint
// since service registration isn't synchronous.
if ipaddr.IsAny(service.Address) {
return fmt.Errorf("Invalid service address")
}
var authzContext acl.AuthorizerContext
authzCtxFill(&authzContext)
// Apply the ACL policy if any. The 'consul' service is excluded
// since it is managed automatically internally (that behavior
// is going away after version 0.8). We check this same policy
// later if version 0.8 is enabled, so we can eventually just
// delete this and do all the ACL checks down there.
if service.Service != structs.ConsulServiceName {
if authz.ServiceWrite(service.Service, &authzContext) != acl.Allow {
return acl.ErrPermissionDenied
}
}
// Proxies must have write permission on their destination
if service.Kind == structs.ServiceKindConnectProxy {
if authz.ServiceWrite(service.Proxy.DestinationServiceName, &authzContext) != acl.Allow {
return acl.ErrPermissionDenied
}
}
return nil
}
// checkPreApply does the verification of a check before it is applied to Raft.
func checkPreApply(check *structs.HealthCheck) {
if check.CheckID == "" && check.Name != "" {
check.CheckID = types.CheckID(check.Name)
}
}
// Register is used register that a node is providing a given service.
func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error { func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error {
if done, err := c.srv.ForwardRPC("Catalog.Register", args, reply); done { if done, err := c.srv.ForwardRPC("Catalog.Register", args, reply); done {
return err return err
@ -212,6 +144,75 @@ func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error
return err return err
} }
// nodePreApply does the verification of a node before it is applied to Raft.
func nodePreApply(nodeName, nodeID string) error {
if nodeName == "" {
return fmt.Errorf("Must provide node")
}
if nodeID != "" {
if _, err := uuid.ParseUUID(nodeID); err != nil {
return fmt.Errorf("Bad node ID: %v", err)
}
}
return nil
}
func servicePreApply(service *structs.NodeService, authz acl.Authorizer, authzCtxFill func(*acl.AuthorizerContext)) error {
// Validate the service. This is in addition to the below since
// the above just hasn't been moved over yet. We should move it over
// in time.
if err := service.Validate(); err != nil {
return err
}
// If no service id, but service name, use default
if service.ID == "" && service.Service != "" {
service.ID = service.Service
}
// Verify ServiceName provided if ID.
if service.ID != "" && service.Service == "" {
return fmt.Errorf("Must provide service name with ID")
}
// Check the service address here and in the agent endpoint
// since service registration isn't synchronous.
if ipaddr.IsAny(service.Address) {
return fmt.Errorf("Invalid service address")
}
var authzContext acl.AuthorizerContext
authzCtxFill(&authzContext)
// Apply the ACL policy if any. The 'consul' service is excluded
// since it is managed automatically internally (that behavior
// is going away after version 0.8). We check this same policy
// later if version 0.8 is enabled, so we can eventually just
// delete this and do all the ACL checks down there.
if service.Service != structs.ConsulServiceName {
if authz.ServiceWrite(service.Service, &authzContext) != acl.Allow {
return acl.ErrPermissionDenied
}
}
// Proxies must have write permission on their destination
if service.Kind == structs.ServiceKindConnectProxy {
if authz.ServiceWrite(service.Proxy.DestinationServiceName, &authzContext) != acl.Allow {
return acl.ErrPermissionDenied
}
}
return nil
}
// checkPreApply does the verification of a check before it is applied to Raft.
func checkPreApply(check *structs.HealthCheck) {
if check.CheckID == "" && check.Name != "" {
check.CheckID = types.CheckID(check.Name)
}
}
// vetRegisterWithACL applies the given ACL's policy to the catalog update and // vetRegisterWithACL applies the given ACL's policy to the catalog update and
// determines if it is allowed. Since the catalog register request is so // determines if it is allowed. Since the catalog register request is so
// dynamic, this is a pretty complex algorithm and was worth breaking out of the // dynamic, this is a pretty complex algorithm and was worth breaking out of the
@ -330,7 +331,13 @@ func vetRegisterWithACL(
return nil return nil
} }
// Deregister is used to remove a service registration for a given node. // Deregister a service or check in a node, or the entire node itself.
//
// If a ServiceID is provided in the request, any associated Checks
// with that service are also deregistered.
//
// If a ServiceID or CheckID is not provided in the request, the entire
// node is deregistered.
func (c *Catalog) Deregister(args *structs.DeregisterRequest, reply *struct{}) error { func (c *Catalog) Deregister(args *structs.DeregisterRequest, reply *struct{}) error {
if done, err := c.srv.ForwardRPC("Catalog.Deregister", args, reply); done { if done, err := c.srv.ForwardRPC("Catalog.Deregister", args, reply); done {
return err return err
@ -458,7 +465,7 @@ func (c *Catalog) ListDatacenters(args *structs.DatacentersRequest, reply *[]str
return nil return nil
} }
// ListNodes is used to query the nodes in a DC // ListNodes is used to query the nodes in a DC.
func (c *Catalog) ListNodes(args *structs.DCSpecificRequest, reply *structs.IndexedNodes) error { func (c *Catalog) ListNodes(args *structs.DCSpecificRequest, reply *structs.IndexedNodes) error {
if done, err := c.srv.ForwardRPC("Catalog.ListNodes", args, reply); done { if done, err := c.srv.ForwardRPC("Catalog.ListNodes", args, reply); done {
return err return err
@ -509,7 +516,8 @@ func isUnmodified(opts structs.QueryOptions, index uint64) bool {
return opts.AllowNotModifiedResponse && opts.MinQueryIndex > 0 && opts.MinQueryIndex == index return opts.AllowNotModifiedResponse && opts.MinQueryIndex > 0 && opts.MinQueryIndex == index
} }
// ListServices is used to query the services in a DC // ListServices is used to query the services in a DC.
// Returns services as a map of service names to available tags.
func (c *Catalog) ListServices(args *structs.DCSpecificRequest, reply *structs.IndexedServices) error { func (c *Catalog) ListServices(args *structs.DCSpecificRequest, reply *structs.IndexedServices) error {
if done, err := c.srv.ForwardRPC("Catalog.ListServices", args, reply); done { if done, err := c.srv.ForwardRPC("Catalog.ListServices", args, reply); done {
return err return err
@ -552,6 +560,8 @@ func (c *Catalog) ListServices(args *structs.DCSpecificRequest, reply *structs.I
}) })
} }
// ServiceList is used to query the services in a DC.
// Returns services as a list of ServiceNames.
func (c *Catalog) ServiceList(args *structs.DCSpecificRequest, reply *structs.IndexedServiceList) error { func (c *Catalog) ServiceList(args *structs.DCSpecificRequest, reply *structs.IndexedServiceList) error {
if done, err := c.srv.ForwardRPC("Catalog.ServiceList", args, reply); done { if done, err := c.srv.ForwardRPC("Catalog.ServiceList", args, reply); done {
return err return err
@ -570,7 +580,7 @@ func (c *Catalog) ServiceList(args *structs.DCSpecificRequest, reply *structs.In
&args.QueryOptions, &args.QueryOptions,
&reply.QueryMeta, &reply.QueryMeta,
func(ws memdb.WatchSet, state *state.Store) error { func(ws memdb.WatchSet, state *state.Store) error {
index, services, err := state.ServiceList(ws, nil, &args.EnterpriseMeta) index, services, err := state.ServiceList(ws, &args.EnterpriseMeta)
if err != nil { if err != nil {
return err return err
} }
@ -581,7 +591,7 @@ func (c *Catalog) ServiceList(args *structs.DCSpecificRequest, reply *structs.In
}) })
} }
// ServiceNodes returns all the nodes registered as part of a service // ServiceNodes returns all the nodes registered as part of a service.
func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *structs.IndexedServiceNodes) error { func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *structs.IndexedServiceNodes) error {
if done, err := c.srv.ForwardRPC("Catalog.ServiceNodes", args, reply); done { if done, err := c.srv.ForwardRPC("Catalog.ServiceNodes", args, reply); done {
return err return err
@ -721,7 +731,8 @@ func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *stru
return err return err
} }
// NodeServices returns all the services registered as part of a node // NodeServices returns all the services registered as part of a node.
// Returns NodeServices as a map of service IDs to services.
func (c *Catalog) NodeServices(args *structs.NodeSpecificRequest, reply *structs.IndexedNodeServices) error { func (c *Catalog) NodeServices(args *structs.NodeSpecificRequest, reply *structs.IndexedNodeServices) error {
if done, err := c.srv.ForwardRPC("Catalog.NodeServices", args, reply); done { if done, err := c.srv.ForwardRPC("Catalog.NodeServices", args, reply); done {
return err return err
@ -776,6 +787,8 @@ func (c *Catalog) NodeServices(args *structs.NodeSpecificRequest, reply *structs
}) })
} }
// NodeServiceList returns all the services registered as part of a node.
// Returns NodeServices as a list of services.
func (c *Catalog) NodeServiceList(args *structs.NodeSpecificRequest, reply *structs.IndexedNodeServiceList) error { func (c *Catalog) NodeServiceList(args *structs.NodeSpecificRequest, reply *structs.IndexedNodeServiceList) error {
if done, err := c.srv.ForwardRPC("Catalog.NodeServiceList", args, reply); done { if done, err := c.srv.ForwardRPC("Catalog.NodeServiceList", args, reply); done {
return err return err

View File

@ -184,7 +184,7 @@ func TestCatalog_Register_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -452,7 +452,7 @@ func TestCatalog_Register_ConnectProxy_ACLDestinationServiceName(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -570,7 +570,7 @@ func TestCatalog_Deregister_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1297,7 +1297,7 @@ func TestCatalog_ListNodes_ACLFilter(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -2409,7 +2409,7 @@ func TestCatalog_ListServiceNodes_ConnectProxy_ACL(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -2699,7 +2699,7 @@ func testACLFilterServer(t *testing.T) (dir, token string, srv *Server, codec rp
dir, srv = testServerWithConfig(t, func(c *Config) { dir, srv = testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
@ -2855,7 +2855,7 @@ func TestCatalog_NodeServices_ACL(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -3258,7 +3258,7 @@ func TestCatalog_GatewayServices_ACLFiltering(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -3970,7 +3970,7 @@ func TestCatalog_VirtualIPForService_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
c.Build = "1.11.0" c.Build = "1.11.0"
}) })

View File

@ -180,10 +180,10 @@ type Config struct {
// ACLEnabled is used to enable ACLs // ACLEnabled is used to enable ACLs
ACLsEnabled bool ACLsEnabled bool
// ACLMasterToken is used to bootstrap the ACL system. It should be specified // ACLInitialManagementToken is used to bootstrap the ACL system. It should be specified
// on the servers in the PrimaryDatacenter. When the leader comes online, it ensures // on the servers in the PrimaryDatacenter. When the leader comes online, it ensures
// that the Master token is available. This provides the initial token. // that the initial management token is available. This provides the initial token.
ACLMasterToken string ACLInitialManagementToken string
// ACLTokenReplication is used to enabled token replication. // ACLTokenReplication is used to enabled token replication.
// //
@ -391,6 +391,8 @@ type Config struct {
RPCConfig RPCConfig RPCConfig RPCConfig
RaftBoltDBConfig RaftBoltDBConfig
// Embedded Consul Enterprise specific configuration // Embedded Consul Enterprise specific configuration
*EnterpriseConfig *EnterpriseConfig
} }
@ -603,3 +605,7 @@ type ReloadableConfig struct {
RaftSnapshotInterval time.Duration RaftSnapshotInterval time.Duration
RaftTrailingLogs int RaftTrailingLogs int
} }
type RaftBoltDBConfig struct {
NoFreelistSync bool
}

View File

@ -594,10 +594,10 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
} }
func gateWriteToSecondary(targetDC, localDC, primaryDC, kind string) error { func gateWriteToSecondary(targetDC, localDC, primaryDC, kind string) error {
// Partition exports are gated from interactions from secondary DCs // ExportedServices entries are gated from interactions from secondary DCs
// because non-default partitions cannot be created in secondaries // because non-default partitions cannot be created in secondaries
// and services cannot be exported to another datacenter. // and services cannot be exported to another datacenter.
if kind != structs.PartitionExports { if kind != structs.ExportedServices {
return nil return nil
} }
if localDC == "" { if localDC == "" {
@ -611,10 +611,10 @@ func gateWriteToSecondary(targetDC, localDC, primaryDC, kind string) error {
switch { switch {
case targetDC == "" && localDC != primaryDC: case targetDC == "" && localDC != primaryDC:
return fmt.Errorf("partition-exports writes in secondary datacenters must target the primary datacenter explicitly.") return fmt.Errorf("exported-services writes in secondary datacenters must target the primary datacenter explicitly.")
case targetDC != "" && targetDC != primaryDC: case targetDC != "" && targetDC != primaryDC:
return fmt.Errorf("partition-exports writes must not target secondary datacenters.") return fmt.Errorf("exported-services writes must not target secondary datacenters.")
} }
return nil return nil

View File

@ -155,7 +155,7 @@ func TestConfigEntry_Apply_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -271,7 +271,7 @@ func TestConfigEntry_Get_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -471,7 +471,7 @@ func TestConfigEntry_List_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -545,7 +545,7 @@ func TestConfigEntry_ListAll_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -750,7 +750,7 @@ func TestConfigEntry_Delete_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1959,7 +1959,7 @@ func TestConfigEntry_ResolveServiceConfig_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -2093,7 +2093,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
targetDC: "", targetDC: "",
localDC: "dc1", localDC: "dc1",
primaryDC: "", primaryDC: "",
kind: structs.PartitionExports, kind: structs.ExportedServices,
}, },
}, },
{ {
@ -2102,7 +2102,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
targetDC: "", targetDC: "",
localDC: "dc1", localDC: "dc1",
primaryDC: "dc1", primaryDC: "dc1",
kind: structs.PartitionExports, kind: structs.ExportedServices,
}, },
}, },
{ {
@ -2111,7 +2111,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
targetDC: "dc1", targetDC: "dc1",
localDC: "dc1", localDC: "dc1",
primaryDC: "dc1", primaryDC: "dc1",
kind: structs.PartitionExports, kind: structs.ExportedServices,
}, },
}, },
{ {
@ -2120,7 +2120,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
targetDC: "dc2", targetDC: "dc2",
localDC: "dc1", localDC: "dc1",
primaryDC: "", primaryDC: "",
kind: structs.PartitionExports, kind: structs.ExportedServices,
}, },
wantErr: "writes must not target secondary datacenters", wantErr: "writes must not target secondary datacenters",
}, },
@ -2130,7 +2130,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
targetDC: "dc2", targetDC: "dc2",
localDC: "dc1", localDC: "dc1",
primaryDC: "dc1", primaryDC: "dc1",
kind: structs.PartitionExports, kind: structs.ExportedServices,
}, },
wantErr: "writes must not target secondary datacenters", wantErr: "writes must not target secondary datacenters",
}, },
@ -2140,7 +2140,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
targetDC: "dc2", targetDC: "dc2",
localDC: "dc2", localDC: "dc2",
primaryDC: "dc1", primaryDC: "dc1",
kind: structs.PartitionExports, kind: structs.ExportedServices,
}, },
wantErr: "writes must not target secondary datacenters", wantErr: "writes must not target secondary datacenters",
}, },
@ -2150,7 +2150,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
targetDC: "", targetDC: "",
localDC: "dc2", localDC: "dc2",
primaryDC: "dc1", primaryDC: "dc1",
kind: structs.PartitionExports, kind: structs.ExportedServices,
}, },
wantErr: "must target the primary datacenter explicitly", wantErr: "must target the primary datacenter explicitly",
}, },
@ -2158,7 +2158,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
name: "empty local DC", name: "empty local DC",
args: args{ args: args{
localDC: "", localDC: "",
kind: structs.PartitionExports, kind: structs.ExportedServices,
}, },
wantErr: "unknown local datacenter", wantErr: "unknown local datacenter",
}, },
@ -2179,7 +2179,7 @@ func Test_gateWriteToSecondary_AllowedKinds(t *testing.T) {
} }
for _, kind := range structs.AllConfigEntryKinds { for _, kind := range structs.AllConfigEntryKinds {
if kind == structs.PartitionExports { if kind == structs.ExportedServices {
continue continue
} }

View File

@ -92,8 +92,8 @@ func (s *Server) reconcileLocalConfig(ctx context.Context, configs []structs.Con
defer ticker.Stop() defer ticker.Stop()
for i, entry := range configs { for i, entry := range configs {
// Partition exports only apply to the primary datacenter. // Exported services only apply to the primary datacenter.
if entry.GetKind() == structs.PartitionExports { if entry.GetKind() == structs.ExportedServices {
continue continue
} }
req := structs.ConfigEntryRequest{ req := structs.ConfigEntryRequest{

View File

@ -92,107 +92,6 @@ func TestReplication_ConfigSort(t *testing.T) {
} }
} }
func TestReplication_DisallowedConfigEntries(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1"
})
defer os.RemoveAll(dir1)
defer s1.Shutdown()
testrpc.WaitForLeader(t, s1.RPC, "dc1")
client := rpcClient(t, s1)
defer client.Close()
dir2, s2 := testServerWithConfig(t, func(c *Config) {
c.Datacenter = "dc2"
c.PrimaryDatacenter = "dc1"
c.ConfigReplicationRate = 100
c.ConfigReplicationBurst = 100
c.ConfigReplicationApplyLimit = 1000000
})
testrpc.WaitForLeader(t, s2.RPC, "dc2")
defer os.RemoveAll(dir2)
defer s2.Shutdown()
// Try to join.
joinWAN(t, s2, s1)
testrpc.WaitForLeader(t, s1.RPC, "dc1")
testrpc.WaitForLeader(t, s1.RPC, "dc2")
args := []structs.ConfigEntryRequest{
{
Datacenter: "dc1",
Op: structs.ConfigEntryUpsert,
Entry: &structs.ServiceConfigEntry{
Kind: structs.ServiceDefaults,
Name: "foo",
Protocol: "http2",
},
},
{
Datacenter: "dc1",
Op: structs.ConfigEntryUpsert,
Entry: &structs.PartitionExportsConfigEntry{
Name: "default",
Services: []structs.ExportedService{
{
Name: structs.WildcardSpecifier,
Consumers: []structs.ServiceConsumer{
{
Partition: "non-default",
},
},
},
},
},
},
{
Datacenter: "dc1",
Op: structs.ConfigEntryUpsert,
Entry: &structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults,
Name: "global",
Config: map[string]interface{}{
"Protocol": "http",
},
},
},
{
Datacenter: "dc1",
Op: structs.ConfigEntryUpsert,
Entry: &structs.MeshConfigEntry{
TransparentProxy: structs.TransparentProxyMeshConfig{
MeshDestinationsOnly: true,
},
},
},
}
for _, arg := range args {
out := false
require.NoError(t, s1.RPC("ConfigEntry.Apply", &arg, &out))
}
retry.Run(t, func(r *retry.R) {
_, local, err := s2.fsm.State().ConfigEntries(nil, structs.ReplicationEnterpriseMeta())
require.NoError(r, err)
require.Len(r, local, 3)
localKinds := make([]string, 0)
for _, entry := range local {
localKinds = append(localKinds, entry.GetKind())
}
// Should have all inserted kinds except for partition-exports.
expectKinds := []string{
structs.ProxyDefaults, structs.ServiceDefaults, structs.MeshConfig,
}
require.ElementsMatch(r, expectKinds, localKinds)
})
}
func TestReplication_ConfigEntries(t *testing.T) { func TestReplication_ConfigEntries(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("too slow for testing.Short") t.Skip("too slow for testing.Short")

View File

@ -163,7 +163,7 @@ func TestConnectCAConfig_GetSet_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = TestDefaultMasterToken c.ACLInitialManagementToken = TestDefaultMasterToken
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1108,7 +1108,7 @@ func TestConnectCASignValidation(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)

View File

@ -191,7 +191,7 @@ func TestCoordinate_Update_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -349,7 +349,7 @@ func TestCoordinate_ListNodes_ACLFilter(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -524,7 +524,7 @@ func TestCoordinate_Node_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)

View File

@ -26,7 +26,7 @@ func TestDiscoveryChainEndpoint_Get(t *testing.T) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)

View File

@ -185,7 +185,7 @@ type customizationMarkers struct {
// the String() method on the type itself. It is this way to be more // the String() method on the type itself. It is this way to be more
// consistent with other string ids within the discovery chain. // consistent with other string ids within the discovery chain.
func serviceIDString(sid structs.ServiceID) string { func serviceIDString(sid structs.ServiceID) string {
return fmt.Sprintf("%s.%s", sid.ID, sid.NamespaceOrDefault()) return fmt.Sprintf("%s.%s.%s", sid.ID, sid.NamespaceOrDefault(), sid.PartitionOrDefault())
} }
func (m *customizationMarkers) IsZero() bool { func (m *customizationMarkers) IsZero() bool {
@ -213,10 +213,10 @@ func (c *compiler) recordServiceProtocol(sid structs.ServiceID) error {
if serviceDefault := c.entries.GetService(sid); serviceDefault != nil { if serviceDefault := c.entries.GetService(sid); serviceDefault != nil {
return c.recordProtocol(sid, serviceDefault.Protocol) return c.recordProtocol(sid, serviceDefault.Protocol)
} }
if c.entries.GlobalProxy != nil { if proxyDefault := c.entries.GetProxyDefaults(sid.PartitionOrDefault()); proxyDefault != nil {
var cfg proxyConfig var cfg proxyConfig
// Ignore errors and fallback on defaults if it does happen. // Ignore errors and fallback on defaults if it does happen.
_ = mapstructure.WeakDecode(c.entries.GlobalProxy.Config, &cfg) _ = mapstructure.WeakDecode(proxyDefault.Config, &cfg)
if cfg.Protocol != "" { if cfg.Protocol != "" {
return c.recordProtocol(sid, cfg.Protocol) return c.recordProtocol(sid, cfg.Protocol)
} }
@ -567,11 +567,12 @@ func (c *compiler) assembleChain() error {
dest = &structs.ServiceRouteDestination{ dest = &structs.ServiceRouteDestination{
Service: c.serviceName, Service: c.serviceName,
Namespace: router.NamespaceOrDefault(), Namespace: router.NamespaceOrDefault(),
Partition: router.PartitionOrDefault(),
} }
} }
svc := defaultIfEmpty(dest.Service, c.serviceName) svc := defaultIfEmpty(dest.Service, c.serviceName)
destNamespace := defaultIfEmpty(dest.Namespace, router.NamespaceOrDefault()) destNamespace := defaultIfEmpty(dest.Namespace, router.NamespaceOrDefault())
destPartition := router.PartitionOrDefault() destPartition := defaultIfEmpty(dest.Partition, router.PartitionOrDefault())
// Check to see if the destination is eligible for splitting. // Check to see if the destination is eligible for splitting.
var ( var (
@ -602,7 +603,7 @@ func (c *compiler) assembleChain() error {
} }
defaultRoute := &structs.DiscoveryRoute{ defaultRoute := &structs.DiscoveryRoute{
Definition: newDefaultServiceRoute(router.Name, router.NamespaceOrDefault()), Definition: newDefaultServiceRoute(router.Name, router.NamespaceOrDefault(), router.PartitionOrDefault()),
NextNode: defaultDestinationNode.MapKey(), NextNode: defaultDestinationNode.MapKey(),
} }
routeNode.Routes = append(routeNode.Routes, defaultRoute) routeNode.Routes = append(routeNode.Routes, defaultRoute)
@ -613,7 +614,7 @@ func (c *compiler) assembleChain() error {
return nil return nil
} }
func newDefaultServiceRoute(serviceName string, namespace string) *structs.ServiceRoute { func newDefaultServiceRoute(serviceName, namespace, partition string) *structs.ServiceRoute {
return &structs.ServiceRoute{ return &structs.ServiceRoute{
Match: &structs.ServiceRouteMatch{ Match: &structs.ServiceRouteMatch{
HTTP: &structs.ServiceRouteHTTPMatch{ HTTP: &structs.ServiceRouteHTTPMatch{
@ -623,6 +624,7 @@ func newDefaultServiceRoute(serviceName string, namespace string) *structs.Servi
Destination: &structs.ServiceRouteDestination{ Destination: &structs.ServiceRouteDestination{
Service: serviceName, Service: serviceName,
Namespace: namespace, Namespace: namespace,
Partition: partition,
}, },
} }
} }
@ -836,7 +838,7 @@ RESOLVE_AGAIN:
target, target,
redirect.Service, redirect.Service,
redirect.ServiceSubset, redirect.ServiceSubset,
target.Partition, redirect.Partition,
redirect.Namespace, redirect.Namespace,
redirect.Datacenter, redirect.Datacenter,
) )
@ -940,9 +942,9 @@ RESOLVE_AGAIN:
if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil { if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil {
target.MeshGateway = serviceDefault.MeshGateway target.MeshGateway = serviceDefault.MeshGateway
} }
proxyDefault := c.entries.GetProxyDefaults(targetID.PartitionOrDefault())
if c.entries.GlobalProxy != nil && target.MeshGateway.Mode == structs.MeshGatewayModeDefault { if proxyDefault != nil && target.MeshGateway.Mode == structs.MeshGatewayModeDefault {
target.MeshGateway.Mode = c.entries.GlobalProxy.MeshGateway.Mode target.MeshGateway.Mode = proxyDefault.MeshGateway.Mode
} }
if c.overrideMeshGateway.Mode != structs.MeshGatewayModeDefault { if c.overrideMeshGateway.Mode != structs.MeshGatewayModeDefault {

View File

@ -158,14 +158,14 @@ func testcase_JustRouterWithDefaults() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "resolver:main.default.default.dc1", NextNode: "resolver:main.default.default.dc1",
}, },
}, },
@ -210,11 +210,11 @@ func testcase_JustRouterWithNoDestination() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: &structs.ServiceRoute{ Definition: &structs.ServiceRoute{
@ -227,7 +227,7 @@ func testcase_JustRouterWithNoDestination() compileTestCase {
NextNode: "resolver:main.default.default.dc1", NextNode: "resolver:main.default.default.dc1",
}, },
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "resolver:main.default.default.dc1", NextNode: "resolver:main.default.default.dc1",
}, },
}, },
@ -270,14 +270,14 @@ func testcase_RouterWithDefaults_NoSplit_WithResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "resolver:main.default.default.dc1", NextNode: "resolver:main.default.default.dc1",
}, },
}, },
@ -321,21 +321,21 @@ func testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver() compileTestCase
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "splitter:main.default", NextNode: "splitter:main.default.default",
}, },
}, },
}, },
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -386,21 +386,21 @@ func testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults() compileTestC
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "splitter:main.default", NextNode: "splitter:main.default.default",
}, },
}, },
}, },
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -458,21 +458,21 @@ func testcase_RouterWithDefaults_WithNoopSplit_WithResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "splitter:main.default", NextNode: "splitter:main.default.default",
}, },
}, },
}, },
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -542,18 +542,18 @@ func testcase_RouteBypassesSplit() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: &router.Routes[0], Definition: &router.Routes[0],
NextNode: "resolver:bypass.other.default.default.dc1", NextNode: "resolver:bypass.other.default.default.dc1",
}, },
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "resolver:main.default.default.dc1", NextNode: "resolver:main.default.default.dc1",
}, },
}, },
@ -605,11 +605,11 @@ func testcase_NoopSplit_DefaultResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -661,11 +661,11 @@ func testcase_NoopSplit_WithResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -724,11 +724,11 @@ func testcase_SubsetSplit() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -801,11 +801,11 @@ func testcase_ServiceSplit() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -898,11 +898,11 @@ func testcase_SplitBypassesSplit() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -1053,13 +1053,14 @@ func testcase_DatacenterRedirect() compileTestCase {
func testcase_DatacenterRedirect_WithMeshGateways() compileTestCase { func testcase_DatacenterRedirect_WithMeshGateways() compileTestCase {
entries := newEntries() entries := newEntries()
entries.GlobalProxy = &structs.ProxyConfigEntry{ entries.AddProxyDefaults(&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults, Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal, Name: structs.ProxyConfigGlobal,
MeshGateway: structs.MeshGatewayConfig{ MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote, Mode: structs.MeshGatewayModeRemote,
}, },
} })
entries.AddResolvers( entries.AddResolvers(
&structs.ServiceResolverConfigEntry{ &structs.ServiceResolverConfigEntry{
Kind: "service-resolver", Kind: "service-resolver",
@ -1300,13 +1301,15 @@ func testcase_DatacenterFailover() compileTestCase {
func testcase_DatacenterFailover_WithMeshGateways() compileTestCase { func testcase_DatacenterFailover_WithMeshGateways() compileTestCase {
entries := newEntries() entries := newEntries()
entries.GlobalProxy = &structs.ProxyConfigEntry{
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults, Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal, Name: structs.ProxyConfigGlobal,
MeshGateway: structs.MeshGatewayConfig{ MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote, Mode: structs.MeshGatewayModeRemote,
}, },
} })
entries.AddResolvers( entries.AddResolvers(
&structs.ServiceResolverConfigEntry{ &structs.ServiceResolverConfigEntry{
Kind: "service-resolver", Kind: "service-resolver",
@ -1384,11 +1387,11 @@ func testcase_NoopSplit_WithDefaultSubset() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -1446,7 +1449,8 @@ func testcase_DefaultResolver() compileTestCase {
func testcase_DefaultResolver_WithProxyDefaults() compileTestCase { func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
entries := newEntries() entries := newEntries()
entries.GlobalProxy = &structs.ProxyConfigEntry{
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults, Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal, Name: structs.ProxyConfigGlobal,
Config: map[string]interface{}{ Config: map[string]interface{}{
@ -1455,7 +1459,7 @@ func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
MeshGateway: structs.MeshGatewayConfig{ MeshGateway: structs.MeshGatewayConfig{
Mode: structs.MeshGatewayModeRemote, Mode: structs.MeshGatewayModeRemote,
}, },
} })
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "grpc", Protocol: "grpc",
@ -1699,11 +1703,11 @@ func testcase_MultiDatacenterCanary() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -1880,11 +1884,11 @@ func testcase_AllBellsAndWhistles() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "router:main.default", StartNode: "router:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"router:main.default": { "router:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeRouter, Type: structs.DiscoveryGraphNodeTypeRouter,
Name: "main.default", Name: "main.default.default",
Routes: []*structs.DiscoveryRoute{ Routes: []*structs.DiscoveryRoute{
{ {
Definition: &router.Routes[0], Definition: &router.Routes[0],
@ -1892,17 +1896,17 @@ func testcase_AllBellsAndWhistles() compileTestCase {
}, },
{ {
Definition: &router.Routes[1], Definition: &router.Routes[1],
NextNode: "splitter:svc-split.default", NextNode: "splitter:svc-split.default.default",
}, },
{ {
Definition: newDefaultServiceRoute("main", "default"), Definition: newDefaultServiceRoute("main", "default", "default"),
NextNode: "resolver:default-subset.main.default.default.dc1", NextNode: "resolver:default-subset.main.default.default.dc1",
}, },
}, },
}, },
"splitter:svc-split.default": { "splitter:svc-split.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "svc-split.default", Name: "svc-split.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -2455,11 +2459,11 @@ func testcase_LBSplitterAndResolver() compileTestCase {
expect := &structs.CompiledDiscoveryChain{ expect := &structs.CompiledDiscoveryChain{
Protocol: "http", Protocol: "http",
StartNode: "splitter:main.default", StartNode: "splitter:main.default.default",
Nodes: map[string]*structs.DiscoveryGraphNode{ Nodes: map[string]*structs.DiscoveryGraphNode{
"splitter:main.default": { "splitter:main.default.default": {
Type: structs.DiscoveryGraphNodeTypeSplitter, Type: structs.DiscoveryGraphNodeTypeSplitter,
Name: "main.default", Name: "main.default.default",
Splits: []*structs.DiscoverySplit{ Splits: []*structs.DiscoverySplit{
{ {
Definition: &structs.ServiceSplit{ Definition: &structs.ServiceSplit{
@ -2642,13 +2646,13 @@ func newSimpleRoute(name string, muts ...func(*structs.ServiceRoute)) structs.Se
} }
func setGlobalProxyProtocol(entries *structs.DiscoveryChainConfigEntries, protocol string) { func setGlobalProxyProtocol(entries *structs.DiscoveryChainConfigEntries, protocol string) {
entries.GlobalProxy = &structs.ProxyConfigEntry{ entries.AddProxyDefaults(&structs.ProxyConfigEntry{
Kind: structs.ProxyDefaults, Kind: structs.ProxyDefaults,
Name: structs.ProxyConfigGlobal, Name: structs.ProxyConfigGlobal,
Config: map[string]interface{}{ Config: map[string]interface{}{
"protocol": protocol, "protocol": protocol,
}, },
} })
} }
func setServiceProtocol(entries *structs.DiscoveryChainConfigEntries, name, protocol string) { func setServiceProtocol(entries *structs.DiscoveryChainConfigEntries, name, protocol string) {

View File

@ -88,7 +88,7 @@ func (s *Server) validateEnterpriseIntentionNamespace(ns string, _ bool) error {
func (s *Server) setupSerfLAN(config *Config) error { func (s *Server) setupSerfLAN(config *Config) error {
var err error var err error
// Initialize the LAN Serf for the default network segment. // Initialize the LAN Serf for the default network segment.
s.serfLAN, err = s.setupSerf(setupSerfOptions{ s.serfLAN, _, err = s.setupSerf(setupSerfOptions{
Config: config.SerfLANConfig, Config: config.SerfLANConfig,
EventCh: s.eventChLAN, EventCh: s.eventChLAN,
SnapshotPath: serfLANSnapshot, SnapshotPath: serfLANSnapshot,

View File

@ -116,7 +116,7 @@ func TestFederationState_Apply_Upsert_ACLDeny(t *testing.T) {
c.DisableFederationStateAntiEntropy = true c.DisableFederationStateAntiEntropy = true
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -237,7 +237,7 @@ func TestFederationState_Get_ACLDeny(t *testing.T) {
c.DisableFederationStateAntiEntropy = true c.DisableFederationStateAntiEntropy = true
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -409,7 +409,7 @@ func TestFederationState_List_ACLDeny(t *testing.T) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -425,7 +425,7 @@ func TestFederationState_List_ACLDeny(t *testing.T) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir2) defer os.RemoveAll(dir2)
@ -695,7 +695,7 @@ func TestFederationState_Apply_Delete_ACLDeny(t *testing.T) {
c.DisableFederationStateAntiEntropy = true c.DisableFederationStateAntiEntropy = true
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)

View File

@ -464,6 +464,14 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, vip, "240.0.0.2") require.Equal(t, vip, "240.0.0.2")
_, serviceNames, err := fsm.state.ServiceNamesOfKind(nil, structs.ServiceKindTypical)
require.NoError(t, err)
expect := []string{"backend", "db", "frontend", "web"}
for i, sn := range serviceNames {
require.Equal(t, expect[i], sn.Service.Name)
}
// Snapshot // Snapshot
snap, err := fsm.Snapshot() snap, err := fsm.Snapshot()
require.NoError(t, err) require.NoError(t, err)
@ -690,10 +698,10 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
require.Len(t, roots, 2) require.Len(t, roots, 2)
// Verify provider state is restored. // Verify provider state is restored.
_, state, err := fsm2.state.CAProviderState("asdf") _, provider, err := fsm2.state.CAProviderState("asdf")
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, "foo", state.PrivateKey) require.Equal(t, "foo", provider.PrivateKey)
require.Equal(t, "bar", state.RootCert) require.Equal(t, "bar", provider.RootCert)
// Verify CA configuration is restored. // Verify CA configuration is restored.
_, caConf, err := fsm2.state.CAConfig(nil) _, caConf, err := fsm2.state.CAConfig(nil)
@ -751,6 +759,14 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, meshConfig, meshConfigEntry) require.Equal(t, meshConfig, meshConfigEntry)
_, restoredServiceNames, err := fsm2.state.ServiceNamesOfKind(nil, structs.ServiceKindTypical)
require.NoError(t, err)
expect = []string{"backend", "db", "frontend", "web"}
for i, sn := range restoredServiceNames {
require.Equal(t, expect[i], sn.Service.Name)
}
// Snapshot // Snapshot
snap, err = fsm2.Snapshot() snap, err = fsm2.Snapshot()
require.NoError(t, err) require.NoError(t, err)

View File

@ -983,7 +983,7 @@ func TestHealth_ServiceNodes_ConnectProxy_ACL(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1289,7 +1289,7 @@ func TestHealth_ServiceNodes_Ingress_ACL(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)

View File

@ -867,7 +867,7 @@ func TestIntentionApply_aclDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1257,7 +1257,7 @@ func TestIntentionApply_aclDelete(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1323,7 +1323,7 @@ func TestIntentionApply_aclUpdate(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1377,7 +1377,7 @@ func TestIntentionApply_aclManagement(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1422,7 +1422,7 @@ func TestIntentionApply_aclUpdateChange(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1472,7 +1472,7 @@ func TestIntentionGet_acl(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1879,7 +1879,7 @@ func TestIntentionCheck_defaultACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1915,7 +1915,7 @@ func TestIntentionCheck_defaultACLAllow(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "allow" c.ACLResolverSettings.ACLDefaultPolicy = "allow"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1951,7 +1951,7 @@ func TestIntentionCheck_aclDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)

View File

@ -72,18 +72,21 @@ func (m *Internal) NodeDump(args *structs.DCSpecificRequest,
if err != nil { if err != nil {
return err return err
} }
reply.Index, reply.Dump = index, dump reply.Index, reply.Dump = index, dump
if err := m.srv.filterACL(args.Token, reply); err != nil {
return err
}
raw, err := filter.Execute(reply.Dump) raw, err := filter.Execute(reply.Dump)
if err != nil { if err != nil {
return err return err
} }
reply.Dump = raw.(structs.NodeDump) reply.Dump = raw.(structs.NodeDump)
// Note: we filter the results with ACLs *after* applying the user-supplied
// bexpr filter, to ensure QueryMeta.ResultsFilteredByACLs does not include
// results that would be filtered out even if the user did have permission.
if err := m.srv.filterACL(args.Token, reply); err != nil {
return err
}
return nil return nil
}) })
} }
@ -114,10 +117,6 @@ func (m *Internal) ServiceDump(args *structs.ServiceDumpRequest, reply *structs.
} }
reply.Nodes = nodes reply.Nodes = nodes
if err := m.srv.filterACL(args.Token, &reply.Nodes); err != nil {
return err
}
// Get, store, and filter gateway services // Get, store, and filter gateway services
idx, gatewayServices, err := state.DumpGatewayServices(ws) idx, gatewayServices, err := state.DumpGatewayServices(ws)
if err != nil { if err != nil {

View File

@ -461,7 +461,7 @@ func TestInternal_NodeInfo_FilterACL(t *testing.T) {
QueryOptions: structs.QueryOptions{Token: token}, QueryOptions: structs.QueryOptions{Token: token},
} }
reply := structs.IndexedNodeDump{} reply := structs.IndexedNodeDump{}
if err := msgpackrpc.CallWithCodec(codec, "Health.NodeChecks", &opt, &reply); err != nil { if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeInfo", &opt, &reply); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
for _, info := range reply.Dump { for _, info := range reply.Dump {
@ -492,6 +492,10 @@ func TestInternal_NodeInfo_FilterACL(t *testing.T) {
} }
} }
if !reply.QueryMeta.ResultsFilteredByACLs {
t.Fatal("ResultsFilteredByACLs should be true")
}
// We've already proven that we call the ACL filtering function so we // We've already proven that we call the ACL filtering function so we
// test node filtering down in acl.go for node cases. This also proves // test node filtering down in acl.go for node cases. This also proves
// that we respect the version 8 ACL flag, since the test server sets // that we respect the version 8 ACL flag, since the test server sets
@ -515,7 +519,7 @@ func TestInternal_NodeDump_FilterACL(t *testing.T) {
QueryOptions: structs.QueryOptions{Token: token}, QueryOptions: structs.QueryOptions{Token: token},
} }
reply := structs.IndexedNodeDump{} reply := structs.IndexedNodeDump{}
if err := msgpackrpc.CallWithCodec(codec, "Health.NodeChecks", &opt, &reply); err != nil { if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeDump", &opt, &reply); err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
for _, info := range reply.Dump { for _, info := range reply.Dump {
@ -546,6 +550,10 @@ func TestInternal_NodeDump_FilterACL(t *testing.T) {
} }
} }
if !reply.QueryMeta.ResultsFilteredByACLs {
t.Fatal("ResultsFilteredByACLs should be true")
}
// We've already proven that we call the ACL filtering function so we // We've already proven that we call the ACL filtering function so we
// test node filtering down in acl.go for node cases. This also proves // test node filtering down in acl.go for node cases. This also proves
// that we respect the version 8 ACL flag, since the test server sets // that we respect the version 8 ACL flag, since the test server sets
@ -562,7 +570,7 @@ func TestInternal_EventFire_Token(t *testing.T) {
dir, srv := testServerWithConfig(t, func(c *Config) { dir, srv := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDownPolicy = "deny" c.ACLResolverSettings.ACLDownPolicy = "deny"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
@ -750,6 +758,217 @@ func TestInternal_ServiceDump_Kind(t *testing.T) {
}) })
} }
func TestInternal_ServiceDump_ACL(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
dir, s := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true
c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
})
defer os.RemoveAll(dir)
defer s.Shutdown()
codec := rpcClient(t, s)
defer codec.Close()
testrpc.WaitForLeader(t, s.RPC, "dc1")
registrations := []*structs.RegisterRequest{
// Service `redis` on `node1`
{
Datacenter: "dc1",
Node: "node1",
ID: types.NodeID("e0155642-135d-4739-9853-a1ee6c9f945b"),
Address: "192.18.1.1",
Service: &structs.NodeService{
Kind: structs.ServiceKindTypical,
ID: "redis",
Service: "redis",
Port: 5678,
},
Check: &structs.HealthCheck{
Name: "redis check",
Status: api.HealthPassing,
ServiceID: "redis",
},
},
// Ingress gateway `igw` on `node2`
{
Datacenter: "dc1",
Node: "node2",
ID: types.NodeID("3a9d7530-20d4-443a-98d3-c10fe78f09f4"),
Address: "192.18.1.2",
Service: &structs.NodeService{
Kind: structs.ServiceKindIngressGateway,
ID: "igw",
Service: "igw",
},
Check: &structs.HealthCheck{
Name: "igw check",
Status: api.HealthPassing,
ServiceID: "igw",
},
},
}
for _, reg := range registrations {
reg.Token = "root"
err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", reg, nil)
require.NoError(t, err)
}
{
req := structs.ConfigEntryRequest{
Datacenter: "dc1",
Entry: &structs.IngressGatewayConfigEntry{
Kind: structs.IngressGateway,
Name: "igw",
Listeners: []structs.IngressListener{
{
Port: 8765,
Protocol: "tcp",
Services: []structs.IngressService{
{Name: "redis"},
},
},
},
},
}
req.Token = "root"
var out bool
err := msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &out)
require.NoError(t, err)
}
tokenWithRules := func(t *testing.T, rules string) string {
t.Helper()
tok, err := upsertTestTokenWithPolicyRules(codec, "root", "dc1", rules)
require.NoError(t, err)
return tok.SecretID
}
t.Run("can read all", func(t *testing.T) {
require := require.New(t)
token := tokenWithRules(t, `
node_prefix "" {
policy = "read"
}
service_prefix "" {
policy = "read"
}
`)
args := structs.DCSpecificRequest{
Datacenter: "dc1",
QueryOptions: structs.QueryOptions{Token: token},
}
var out structs.IndexedNodesWithGateways
err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
require.NoError(err)
require.NotEmpty(out.Nodes)
require.NotEmpty(out.Gateways)
require.False(out.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
})
t.Run("cannot read service node", func(t *testing.T) {
require := require.New(t)
token := tokenWithRules(t, `
node "node1" {
policy = "deny"
}
service "redis" {
policy = "read"
}
`)
args := structs.DCSpecificRequest{
Datacenter: "dc1",
QueryOptions: structs.QueryOptions{Token: token},
}
var out structs.IndexedNodesWithGateways
err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
require.NoError(err)
require.Empty(out.Nodes)
require.True(out.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("cannot read service", func(t *testing.T) {
require := require.New(t)
token := tokenWithRules(t, `
node "node1" {
policy = "read"
}
service "redis" {
policy = "deny"
}
`)
args := structs.DCSpecificRequest{
Datacenter: "dc1",
QueryOptions: structs.QueryOptions{Token: token},
}
var out structs.IndexedNodesWithGateways
err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
require.NoError(err)
require.Empty(out.Nodes)
require.True(out.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("cannot read gateway node", func(t *testing.T) {
require := require.New(t)
token := tokenWithRules(t, `
node "node2" {
policy = "deny"
}
service "mgw" {
policy = "read"
}
`)
args := structs.DCSpecificRequest{
Datacenter: "dc1",
QueryOptions: structs.QueryOptions{Token: token},
}
var out structs.IndexedNodesWithGateways
err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
require.NoError(err)
require.Empty(out.Gateways)
require.True(out.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
t.Run("cannot read gateway", func(t *testing.T) {
require := require.New(t)
token := tokenWithRules(t, `
node "node2" {
policy = "read"
}
service "mgw" {
policy = "deny"
}
`)
args := structs.DCSpecificRequest{
Datacenter: "dc1",
QueryOptions: structs.QueryOptions{Token: token},
}
var out structs.IndexedNodesWithGateways
err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
require.NoError(err)
require.Empty(out.Gateways)
require.True(out.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
}
func TestInternal_GatewayServiceDump_Terminating(t *testing.T) { func TestInternal_GatewayServiceDump_Terminating(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("too slow for testing.Short") t.Skip("too slow for testing.Short")
@ -963,7 +1182,7 @@ func TestInternal_GatewayServiceDump_Terminating_ACL(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1082,6 +1301,7 @@ func TestInternal_GatewayServiceDump_Terminating_ACL(t *testing.T) {
require.Equal(t, nodes[0].Node.Node, "bar") require.Equal(t, nodes[0].Node.Node, "bar")
require.Equal(t, nodes[0].Service.Service, "db") require.Equal(t, nodes[0].Service.Service, "db")
require.Equal(t, nodes[0].Checks[0].Status, api.HealthWarning) require.Equal(t, nodes[0].Checks[0].Status, api.HealthWarning)
require.True(t, out.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
} }
func TestInternal_GatewayServiceDump_Ingress(t *testing.T) { func TestInternal_GatewayServiceDump_Ingress(t *testing.T) {
@ -1308,7 +1528,7 @@ func TestInternal_GatewayServiceDump_Ingress_ACL(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1972,7 +2192,7 @@ func TestInternal_ServiceTopology_ACL(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = TestDefaultMasterToken c.ACLInitialManagementToken = TestDefaultMasterToken
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -2111,7 +2331,7 @@ func TestInternal_IntentionUpstreams_ACL(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = TestDefaultMasterToken c.ACLInitialManagementToken = TestDefaultMasterToken
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)

View File

@ -84,7 +84,7 @@ func TestKVS_Apply_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -189,7 +189,7 @@ func TestKVS_Get_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -413,7 +413,7 @@ func TestKVSEndpoint_List_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -492,7 +492,7 @@ func TestKVSEndpoint_List_ACLEnableKeyListPolicy(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
c.ACLEnableKeyListPolicy = true c.ACLEnableKeyListPolicy = true
}) })
@ -684,7 +684,7 @@ func TestKVSEndpoint_ListKeys_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)

View File

@ -431,28 +431,28 @@ func (s *Server) initializeACLs(ctx context.Context) error {
s.logger.Info("Created ACL 'global-management' policy") s.logger.Info("Created ACL 'global-management' policy")
} }
// Check for configured master token. // Check for configured initial management token.
if master := s.config.ACLMasterToken; len(master) > 0 { if initialManagement := s.config.ACLInitialManagementToken; len(initialManagement) > 0 {
state := s.fsm.State() state := s.fsm.State()
if _, err := uuid.ParseUUID(master); err != nil { if _, err := uuid.ParseUUID(initialManagement); err != nil {
s.logger.Warn("Configuring a non-UUID master token is deprecated") s.logger.Warn("Configuring a non-UUID initial management token is deprecated")
} }
_, token, err := state.ACLTokenGetBySecret(nil, master, nil) _, token, err := state.ACLTokenGetBySecret(nil, initialManagement, nil)
if err != nil { if err != nil {
return fmt.Errorf("failed to get master token: %v", err) return fmt.Errorf("failed to get initial management token: %v", err)
} }
// Ignoring expiration times to avoid an insertion collision. // Ignoring expiration times to avoid an insertion collision.
if token == nil { if token == nil {
accessor, err := lib.GenerateUUID(s.checkTokenUUID) accessor, err := lib.GenerateUUID(s.checkTokenUUID)
if err != nil { if err != nil {
return fmt.Errorf("failed to generate the accessor ID for the master token: %v", err) return fmt.Errorf("failed to generate the accessor ID for the initial management token: %v", err)
} }
token := structs.ACLToken{ token := structs.ACLToken{
AccessorID: accessor, AccessorID: accessor,
SecretID: master, SecretID: initialManagement,
Description: "Master Token", Description: "Initial Management Token",
Policies: []structs.ACLTokenPolicyLink{ Policies: []structs.ACLTokenPolicyLink{
{ {
ID: structs.ACLPolicyGlobalManagementID, ID: structs.ACLPolicyGlobalManagementID,
@ -472,12 +472,12 @@ func (s *Server) initializeACLs(ctx context.Context) error {
ResetIndex: 0, ResetIndex: 0,
} }
if _, err := s.raftApply(structs.ACLBootstrapRequestType, &req); err == nil { if _, err := s.raftApply(structs.ACLBootstrapRequestType, &req); err == nil {
s.logger.Info("Bootstrapped ACL master token from configuration") s.logger.Info("Bootstrapped ACL initial management token from configuration")
done = true done = true
} else { } else {
if err.Error() != structs.ACLBootstrapNotAllowedErr.Error() && if err.Error() != structs.ACLBootstrapNotAllowedErr.Error() &&
err.Error() != structs.ACLBootstrapInvalidResetIndexErr.Error() { err.Error() != structs.ACLBootstrapInvalidResetIndexErr.Error() {
return fmt.Errorf("failed to bootstrap master token: %v", err) return fmt.Errorf("failed to bootstrap initial management token: %v", err)
} }
} }
} }
@ -489,10 +489,10 @@ func (s *Server) initializeACLs(ctx context.Context) error {
CAS: false, CAS: false,
} }
if _, err := s.raftApply(structs.ACLTokenSetRequestType, &req); err != nil { if _, err := s.raftApply(structs.ACLTokenSetRequestType, &req); err != nil {
return fmt.Errorf("failed to create master token: %v", err) return fmt.Errorf("failed to create initial management token: %v", err)
} }
s.logger.Info("Created ACL master token from configuration") s.logger.Info("Created ACL initial management token from configuration")
} }
} }
} }

View File

@ -204,7 +204,7 @@ func TestCAManager_Initialize_Secondary(t *testing.T) {
c.PrimaryDatacenter = "primary" c.PrimaryDatacenter = "primary"
c.Build = "1.6.0" c.Build = "1.6.0"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = masterToken c.ACLInitialManagementToken = masterToken
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
c.CAConfig.Config["PrivateKeyType"] = tc.keyType c.CAConfig.Config["PrivateKeyType"] = tc.keyType
c.CAConfig.Config["PrivateKeyBits"] = tc.keyBits c.CAConfig.Config["PrivateKeyBits"] = tc.keyBits

View File

@ -359,7 +359,7 @@ func TestLeader_FederationStateAntiEntropyPruning_ACLDeny(t *testing.T) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -373,7 +373,7 @@ func TestLeader_FederationStateAntiEntropyPruning_ACLDeny(t *testing.T) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir2) defer os.RemoveAll(dir2)

View File

@ -29,7 +29,7 @@ func TestLeader_ReplicateIntentions(t *testing.T) {
c.Datacenter = "dc1" c.Datacenter = "dc1"
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
c.Build = "1.6.0" c.Build = "1.6.0"
c.OverrideInitialSerfTags = func(tags map[string]string) { c.OverrideInitialSerfTags = func(tags map[string]string) {

View File

@ -31,7 +31,7 @@ func TestLeader_RegisterMember(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -106,7 +106,7 @@ func TestLeader_FailedMember(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -171,7 +171,7 @@ func TestLeader_LeftMember(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -221,7 +221,7 @@ func TestLeader_ReapMember(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -286,7 +286,7 @@ func TestLeader_CheckServersMeta(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "allow" c.ACLResolverSettings.ACLDefaultPolicy = "allow"
c.Bootstrap = true c.Bootstrap = true
}) })
@ -296,7 +296,7 @@ func TestLeader_CheckServersMeta(t *testing.T) {
dir2, s2 := testServerWithConfig(t, func(c *Config) { dir2, s2 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "allow" c.ACLResolverSettings.ACLDefaultPolicy = "allow"
c.Bootstrap = false c.Bootstrap = false
}) })
@ -306,7 +306,7 @@ func TestLeader_CheckServersMeta(t *testing.T) {
dir3, s3 := testServerWithConfig(t, func(c *Config) { dir3, s3 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "allow" c.ACLResolverSettings.ACLDefaultPolicy = "allow"
c.Bootstrap = false c.Bootstrap = false
}) })
@ -394,7 +394,7 @@ func TestLeader_ReapServer(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "allow" c.ACLResolverSettings.ACLDefaultPolicy = "allow"
c.Bootstrap = true c.Bootstrap = true
}) })
@ -404,7 +404,7 @@ func TestLeader_ReapServer(t *testing.T) {
dir2, s2 := testServerWithConfig(t, func(c *Config) { dir2, s2 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "allow" c.ACLResolverSettings.ACLDefaultPolicy = "allow"
c.Bootstrap = false c.Bootstrap = false
}) })
@ -414,7 +414,7 @@ func TestLeader_ReapServer(t *testing.T) {
dir3, s3 := testServerWithConfig(t, func(c *Config) { dir3, s3 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "allow" c.ACLResolverSettings.ACLDefaultPolicy = "allow"
c.Bootstrap = false c.Bootstrap = false
}) })
@ -473,7 +473,7 @@ func TestLeader_Reconcile_ReapMember(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -526,7 +526,7 @@ func TestLeader_Reconcile(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -875,7 +875,7 @@ func TestLeader_ReapTombstones(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
c.TombstoneTTL = 50 * time.Millisecond c.TombstoneTTL = 50 * time.Millisecond
c.TombstoneTTLGranularity = 10 * time.Millisecond c.TombstoneTTLGranularity = 10 * time.Millisecond
@ -1180,7 +1180,7 @@ func TestLeader_ACL_Initialization(t *testing.T) {
c.Datacenter = "dc1" c.Datacenter = "dc1"
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = tt.master c.ACLInitialManagementToken = tt.master
} }
dir1, s1 := testServerWithConfig(t, conf) dir1, s1 := testServerWithConfig(t, conf)
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1225,7 +1225,7 @@ func TestLeader_ACLUpgrade_IsStickyEvenIfSerfTagsRegress(t *testing.T) {
c.Datacenter = "dc1" c.Datacenter = "dc1"
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
defer s1.Shutdown() defer s1.Shutdown()

View File

@ -2,6 +2,7 @@ package consul
import ( import (
"fmt" "fmt"
"sync"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
"github.com/hashicorp/serf/serf" "github.com/hashicorp/serf/serf"
@ -86,14 +87,41 @@ func (md *lanMergeDelegate) NotifyMerge(members []*serf.Member) error {
// ring. We check that the peers are server nodes and abort the merge // ring. We check that the peers are server nodes and abort the merge
// otherwise. // otherwise.
type wanMergeDelegate struct { type wanMergeDelegate struct {
localDatacenter string
federationDisabledLock sync.Mutex
federationDisabled bool
}
// SetWANFederationDisabled selectively disables the wan pool from accepting
// non-local members. If the toggle changed the current value it returns true.
func (md *wanMergeDelegate) SetWANFederationDisabled(disabled bool) bool {
md.federationDisabledLock.Lock()
prior := md.federationDisabled
md.federationDisabled = disabled
md.federationDisabledLock.Unlock()
return prior != disabled
} }
func (md *wanMergeDelegate) NotifyMerge(members []*serf.Member) error { func (md *wanMergeDelegate) NotifyMerge(members []*serf.Member) error {
// Deliberately hold this lock during the entire merge so calls to
// SetWANFederationDisabled returning immediately imply that the flag takes
// effect for all future merges.
md.federationDisabledLock.Lock()
defer md.federationDisabledLock.Unlock()
for _, m := range members { for _, m := range members {
ok, _ := metadata.IsConsulServer(*m) ok, srv := metadata.IsConsulServer(*m)
if !ok { if !ok {
return fmt.Errorf("Member '%s' is not a server", m.Name) return fmt.Errorf("Member '%s' is not a server", m.Name)
} }
if md.federationDisabled {
if srv.Datacenter != md.localDatacenter {
return fmt.Errorf("Member '%s' part of wrong datacenter '%s'; WAN federation is disabled", m.Name, srv.Datacenter)
}
}
} }
return nil return nil
} }

View File

@ -138,10 +138,16 @@ func TestMerge_WAN(t *testing.T) {
type testcase struct { type testcase struct {
members []*serf.Member members []*serf.Member
expect string expect string
setupFn func(t *testing.T, delegate *wanMergeDelegate)
} }
run := func(t *testing.T, tc testcase) { run := func(t *testing.T, tc testcase) {
delegate := &wanMergeDelegate{} delegate := &wanMergeDelegate{
localDatacenter: "dc1",
}
if tc.setupFn != nil {
tc.setupFn(t, delegate)
}
err := delegate.NotifyMerge(tc.members) err := delegate.NotifyMerge(tc.members)
if tc.expect == "" { if tc.expect == "" {
require.NoError(t, err) require.NoError(t, err)
@ -177,7 +183,33 @@ func TestMerge_WAN(t *testing.T) {
build: "0.7.5", build: "0.7.5",
}), }),
}, },
expect: "", },
"federation disabled and local join allowed": {
setupFn: func(t *testing.T, delegate *wanMergeDelegate) {
delegate.SetWANFederationDisabled(true)
},
members: []*serf.Member{
makeTestNode(t, testMember{
dc: "dc1",
name: "node1",
server: true,
build: "0.7.5",
}),
},
},
"federation disabled and remote join blocked": {
setupFn: func(t *testing.T, delegate *wanMergeDelegate) {
delegate.SetWANFederationDisabled(true)
},
members: []*serf.Member{
makeTestNode(t, testMember{
dc: "dc2",
name: "node1",
server: true,
build: "0.7.5",
}),
},
expect: `WAN federation is disabled`,
}, },
} }

View File

@ -54,7 +54,7 @@ func TestOperator_Autopilot_GetConfiguration_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
c.AutopilotConfig.CleanupDeadServers = false c.AutopilotConfig.CleanupDeadServers = false
}) })
@ -138,7 +138,7 @@ func TestOperator_Autopilot_SetConfiguration_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
c.AutopilotConfig.CleanupDeadServers = false c.AutopilotConfig.CleanupDeadServers = false
}) })

View File

@ -72,7 +72,7 @@ func TestOperator_RaftGetConfiguration_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -199,7 +199,7 @@ func TestOperator_RaftRemovePeerByAddress_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -305,7 +305,7 @@ func TestOperator_RaftRemovePeerByID_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
c.RaftConfig.ProtocolVersion = 3 c.RaftConfig.ProtocolVersion = 3
}) })

View File

@ -373,7 +373,7 @@ func (p *PreparedQuery) Execute(args *structs.PreparedQueryExecuteRequest,
if query.Token != "" { if query.Token != "" {
token = query.Token token = query.Token
} }
if err := p.srv.filterACL(token, &reply.Nodes); err != nil { if err := p.srv.filterACL(token, reply); err != nil {
return err return err
} }
@ -500,7 +500,7 @@ func (p *PreparedQuery) ExecuteRemote(args *structs.PreparedQueryExecuteRemoteRe
if args.Query.Token != "" { if args.Query.Token != "" {
token = args.Query.Token token = args.Query.Token
} }
if err := p.srv.filterACL(token, &reply.Nodes); err != nil { if err := p.srv.filterACL(token, reply); err != nil {
return err return err
} }

View File

@ -200,7 +200,7 @@ func TestPreparedQuery_Apply_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -629,7 +629,7 @@ func TestPreparedQuery_ACLDeny_Catchall_Template(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -831,7 +831,7 @@ func TestPreparedQuery_Get(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1072,7 +1072,7 @@ func TestPreparedQuery_List(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1167,6 +1167,31 @@ func TestPreparedQuery_List(t *testing.T) {
} }
} }
// Same for a token without access to the query.
{
token := createTokenWithPolicyName(t, codec, "deny-queries", `
query_prefix "" {
policy = "deny"
}
`, "root")
req := &structs.DCSpecificRequest{
Datacenter: "dc1",
QueryOptions: structs.QueryOptions{Token: token},
}
var resp structs.IndexedPreparedQueries
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.List", req, &resp); err != nil {
t.Fatalf("err: %v", err)
}
if len(resp.Queries) != 0 {
t.Fatalf("bad: %v", resp)
}
if !resp.QueryMeta.ResultsFilteredByACLs {
t.Fatal("ResultsFilteredByACLs should be true")
}
}
// But a management token should work, and be able to see the captured // But a management token should work, and be able to see the captured
// token. // token.
query.Query.Token = "le-token" query.Query.Token = "le-token"
@ -1268,7 +1293,7 @@ func TestPreparedQuery_Explain(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -1392,7 +1417,7 @@ func TestPreparedQuery_Execute(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -2124,6 +2149,7 @@ func TestPreparedQuery_Execute(t *testing.T) {
require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Execute", &req, &reply)) require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Execute", &req, &reply))
expectNodes(t, &query, &reply, 0) expectNodes(t, &query, &reply, 0)
require.True(t, reply.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
}) })
t.Run("normal operation again with exec token", func(t *testing.T) { t.Run("normal operation again with exec token", func(t *testing.T) {
@ -2246,6 +2272,20 @@ func TestPreparedQuery_Execute(t *testing.T) {
expectFailoverNodes(t, &query, &reply, 0) expectFailoverNodes(t, &query, &reply, 0)
}) })
t.Run("nodes in response from dc2 are filtered by ACL token", func(t *testing.T) {
req := structs.PreparedQueryExecuteRequest{
Datacenter: "dc1",
QueryIDOrName: query.Query.ID,
QueryOptions: structs.QueryOptions{Token: execNoNodesToken},
}
var reply structs.PreparedQueryExecuteResponse
require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Execute", &req, &reply))
expectFailoverNodes(t, &query, &reply, 0)
require.True(t, reply.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
})
// Bake the exec token into the query. // Bake the exec token into the query.
query.Query.Token = execToken query.Query.Token = execToken
require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Apply", &query, &query.Query.ID)) require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Apply", &query, &query.Query.ID))
@ -2659,7 +2699,7 @@ func TestPreparedQuery_Wrapper(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -2669,7 +2709,7 @@ func TestPreparedQuery_Wrapper(t *testing.T) {
c.Datacenter = "dc2" c.Datacenter = "dc2"
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir2) defer os.RemoveAll(dir2)

View File

@ -882,7 +882,7 @@ func TestRPC_LocalTokenStrippedOnForward(t *testing.T) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
defer s1.Shutdown() defer s1.Shutdown()
@ -1010,7 +1010,7 @@ func TestRPC_LocalTokenStrippedOnForward_GRPC(t *testing.T) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.RPCConfig.EnableStreaming = true c.RPCConfig.EnableStreaming = true
}) })
s1.tokens.UpdateAgentToken("root", tokenStore.TokenSourceConfig) s1.tokens.UpdateAgentToken("root", tokenStore.TokenSourceConfig)

View File

@ -18,6 +18,7 @@ import (
"time" "time"
"github.com/hashicorp/go-version" "github.com/hashicorp/go-version"
"go.etcd.io/bbolt"
"github.com/armon/go-metrics" "github.com/armon/go-metrics"
connlimit "github.com/hashicorp/go-connlimit" connlimit "github.com/hashicorp/go-connlimit"
@ -25,7 +26,7 @@ import (
"github.com/hashicorp/go-memdb" "github.com/hashicorp/go-memdb"
"github.com/hashicorp/raft" "github.com/hashicorp/raft"
autopilot "github.com/hashicorp/raft-autopilot" autopilot "github.com/hashicorp/raft-autopilot"
raftboltdb "github.com/hashicorp/raft-boltdb" raftboltdb "github.com/hashicorp/raft-boltdb/v2"
"github.com/hashicorp/serf/serf" "github.com/hashicorp/serf/serf"
"golang.org/x/time/rate" "golang.org/x/time/rate"
"google.golang.org/grpc" "google.golang.org/grpc"
@ -188,6 +189,12 @@ type Server struct {
// serf cluster that spans datacenters // serf cluster that spans datacenters
eventChWAN chan serf.Event eventChWAN chan serf.Event
// wanMembershipNotifyCh is used to receive notifications that the the
// serfWAN wan pool may have changed.
//
// If this is nil, notification is skipped.
wanMembershipNotifyCh chan struct{}
// fsm is the state machine used with Raft to provide // fsm is the state machine used with Raft to provide
// strong consistency. // strong consistency.
fsm *fsm.FSM fsm *fsm.FSM
@ -265,6 +272,7 @@ type Server struct {
// serfWAN is the Serf cluster maintained between DC's // serfWAN is the Serf cluster maintained between DC's
// which SHOULD only consist of Consul servers // which SHOULD only consist of Consul servers
serfWAN *serf.Serf serfWAN *serf.Serf
serfWANConfig *serf.Config
memberlistTransportWAN wanfed.IngestionAwareTransport memberlistTransportWAN wanfed.IngestionAwareTransport
gatewayLocator *GatewayLocator gatewayLocator *GatewayLocator
@ -492,7 +500,7 @@ func NewServer(config *Config, flat Deps) (*Server, error) {
// Initialize the WAN Serf if enabled // Initialize the WAN Serf if enabled
if config.SerfWANConfig != nil { if config.SerfWANConfig != nil {
s.serfWAN, err = s.setupSerf(setupSerfOptions{ s.serfWAN, s.serfWANConfig, err = s.setupSerf(setupSerfOptions{
Config: config.SerfWANConfig, Config: config.SerfWANConfig,
EventCh: s.eventChWAN, EventCh: s.eventChWAN,
SnapshotPath: serfWANSnapshot, SnapshotPath: serfWANSnapshot,
@ -547,7 +555,7 @@ func NewServer(config *Config, flat Deps) (*Server, error) {
s.Shutdown() s.Shutdown()
return nil, fmt.Errorf("Failed to add WAN serf route: %v", err) return nil, fmt.Errorf("Failed to add WAN serf route: %v", err)
} }
go router.HandleSerfEvents(s.logger, s.router, types.AreaWAN, s.serfWAN.ShutdownCh(), s.eventChWAN) go router.HandleSerfEvents(s.logger, s.router, types.AreaWAN, s.serfWAN.ShutdownCh(), s.eventChWAN, s.wanMembershipNotifyCh)
// Fire up the LAN <-> WAN join flooder. // Fire up the LAN <-> WAN join flooder.
addrFn := func(s *metadata.Server) (string, error) { addrFn := func(s *metadata.Server) (string, error) {
@ -646,7 +654,7 @@ func newGRPCHandlerFromConfig(deps Deps, config *Config, s *Server) connHandler
s.registerEnterpriseGRPCServices(deps, srv) s.registerEnterpriseGRPCServices(deps, srv)
} }
return agentgrpc.NewHandler(config.RPCAddr, register) return agentgrpc.NewHandler(deps.Logger, config.RPCAddr, register)
} }
func (s *Server) connectCARootsMonitor(ctx context.Context) { func (s *Server) connectCARootsMonitor(ctx context.Context) {
@ -729,13 +737,21 @@ func (s *Server) setupRaft() error {
} }
// Create the backend raft store for logs and stable storage. // Create the backend raft store for logs and stable storage.
store, err := raftboltdb.NewBoltStore(filepath.Join(path, "raft.db")) store, err := raftboltdb.New(raftboltdb.Options{
BoltOptions: &bbolt.Options{
NoFreelistSync: s.config.RaftBoltDBConfig.NoFreelistSync,
},
Path: filepath.Join(path, "raft.db"),
})
if err != nil { if err != nil {
return err return err
} }
s.raftStore = store s.raftStore = store
stable = store stable = store
// start publishing boltdb metrics
go store.RunMetrics(&lib.StopChannelContext{StopCh: s.shutdownCh}, 0)
// Wrap the store in a LogCache to improve performance. // Wrap the store in a LogCache to improve performance.
cacheStore, err := raft.NewLogCache(raftLogCacheSize, store) cacheStore, err := raft.NewLogCache(raftLogCacheSize, store)
if err != nil { if err != nil {
@ -1115,6 +1131,11 @@ func (s *Server) JoinWAN(addrs []string) (int, error) {
if s.serfWAN == nil { if s.serfWAN == nil {
return 0, ErrWANFederationDisabled return 0, ErrWANFederationDisabled
} }
if err := s.enterpriseValidateJoinWAN(); err != nil {
return 0, err
}
return s.serfWAN.Join(addrs, true) return s.serfWAN.Join(addrs, true)
} }

View File

@ -19,6 +19,10 @@ import (
func (s *Server) registerEnterpriseGRPCServices(deps Deps, srv *grpc.Server) {} func (s *Server) registerEnterpriseGRPCServices(deps Deps, srv *grpc.Server) {}
func (s *Server) enterpriseValidateJoinWAN() error {
return nil // no-op
}
// JoinLAN is used to have Consul join the inner-DC pool The target address // JoinLAN is used to have Consul join the inner-DC pool The target address
// should be another node inside the DC listening on the Serf LAN address // should be another node inside the DC listening on the Serf LAN address
func (s *Server) JoinLAN(addrs []string, entMeta *structs.EnterpriseMeta) (int, error) { func (s *Server) JoinLAN(addrs []string, entMeta *structs.EnterpriseMeta) (int, error) {

View File

@ -48,12 +48,18 @@ type setupSerfOptions struct {
} }
// setupSerf is used to setup and initialize a Serf // setupSerf is used to setup and initialize a Serf
func (s *Server) setupSerf(opts setupSerfOptions) (*serf.Serf, error) { func (s *Server) setupSerf(opts setupSerfOptions) (*serf.Serf, *serf.Config, error) {
conf, err := s.setupSerfConfig(opts) conf, err := s.setupSerfConfig(opts)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
return serf.Create(conf)
cluster, err := serf.Create(conf)
if err != nil {
return nil, nil, err
}
return cluster, conf, nil
} }
func (s *Server) setupSerfConfig(opts setupSerfOptions) (*serf.Config, error) { func (s *Server) setupSerfConfig(opts setupSerfOptions) (*serf.Config, error) {
@ -152,7 +158,9 @@ func (s *Server) setupSerfConfig(opts setupSerfOptions) (*serf.Config, error) {
conf.ProtocolVersion = protocolVersionMap[s.config.ProtocolVersion] conf.ProtocolVersion = protocolVersionMap[s.config.ProtocolVersion]
conf.RejoinAfterLeave = s.config.RejoinAfterLeave conf.RejoinAfterLeave = s.config.RejoinAfterLeave
if opts.WAN { if opts.WAN {
conf.Merge = &wanMergeDelegate{} conf.Merge = &wanMergeDelegate{
localDatacenter: s.config.Datacenter,
}
} else { } else {
conf.Merge = &lanMergeDelegate{ conf.Merge = &lanMergeDelegate{
dc: s.config.Datacenter, dc: s.config.Datacenter,

View File

@ -74,7 +74,7 @@ func testServerACLConfig(cb func(*Config)) func(*Config) {
return func(c *Config) { return func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = TestDefaultMasterToken c.ACLInitialManagementToken = TestDefaultMasterToken
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
if cb != nil { if cb != nil {

View File

@ -151,7 +151,7 @@ func (s *Session) Apply(args *structs.SessionRequest, reply *string) error {
if args.Op == structs.SessionCreate && args.Session.TTL != "" { if args.Op == structs.SessionCreate && args.Session.TTL != "" {
// If we created a session with a TTL, reset the expiration timer // If we created a session with a TTL, reset the expiration timer
s.srv.resetSessionTimer(args.Session.ID, &args.Session) s.srv.resetSessionTimer(&args.Session)
} else if args.Op == structs.SessionDestroy { } else if args.Op == structs.SessionDestroy {
// If we destroyed a session, it might potentially have a TTL, // If we destroyed a session, it might potentially have a TTL,
// and we need to clear the timer // and we need to clear the timer
@ -308,7 +308,7 @@ func (s *Session) Renew(args *structs.SessionSpecificRequest,
// Reset the session TTL timer. // Reset the session TTL timer.
reply.Sessions = structs.Sessions{session} reply.Sessions = structs.Sessions{session}
if err := s.srv.resetSessionTimer(args.SessionID, session); err != nil { if err := s.srv.resetSessionTimer(session); err != nil {
s.logger.Error("Session renew failed", "error", err) s.logger.Error("Session renew failed", "error", err)
return err return err
} }

View File

@ -157,7 +157,7 @@ func TestSession_Apply_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -382,7 +382,7 @@ func TestSession_Get_List_NodeSessions_ACLFilter(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -731,7 +731,7 @@ func TestSession_Renew_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)

View File

@ -47,13 +47,12 @@ func (s *Server) initializeSessionTimers() error {
// Scan all sessions and reset their timer // Scan all sessions and reset their timer
state := s.fsm.State() state := s.fsm.State()
// TODO(partitions): track all session timers in all partitions _, sessions, err := state.SessionListAll(nil)
_, sessions, err := state.SessionList(nil, structs.WildcardEnterpriseMetaInDefaultPartition())
if err != nil { if err != nil {
return err return err
} }
for _, session := range sessions { for _, session := range sessions {
if err := s.resetSessionTimer(session.ID, session); err != nil { if err := s.resetSessionTimer(session); err != nil {
return err return err
} }
} }
@ -63,20 +62,7 @@ func (s *Server) initializeSessionTimers() error {
// resetSessionTimer is used to renew the TTL of a session. // resetSessionTimer is used to renew the TTL of a session.
// This can be used for new sessions and existing ones. A session // This can be used for new sessions and existing ones. A session
// will be faulted in if not given. // will be faulted in if not given.
func (s *Server) resetSessionTimer(id string, session *structs.Session) error { func (s *Server) resetSessionTimer(session *structs.Session) error {
// Fault the session in if not given
if session == nil {
state := s.fsm.State()
_, s, err := state.SessionGet(nil, id, nil)
if err != nil {
return err
}
if s == nil {
return fmt.Errorf("Session '%s' not found", id)
}
session = s
}
// Bail if the session has no TTL, fast-path some common inputs // Bail if the session has no TTL, fast-path some common inputs
switch session.TTL { switch session.TTL {
case "", "0", "0s", "0m", "0h": case "", "0", "0s", "0m", "0h":

View File

@ -11,7 +11,7 @@ import (
"github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/consul/sdk/testutil/retry"
"github.com/hashicorp/consul/testrpc" "github.com/hashicorp/consul/testrpc"
"github.com/hashicorp/go-uuid" "github.com/hashicorp/go-uuid"
"github.com/hashicorp/net-rpc-msgpackrpc" msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
) )
func generateUUID() (ret string) { func generateUUID() (ret string) {
@ -59,50 +59,6 @@ func TestInitializeSessionTimers(t *testing.T) {
} }
} }
func TestResetSessionTimer_Fault(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}
t.Parallel()
dir1, s1 := testServer(t)
defer os.RemoveAll(dir1)
defer s1.Shutdown()
testrpc.WaitForLeader(t, s1.RPC, "dc1")
// Should not exist
err := s1.resetSessionTimer(generateUUID(), nil)
if err == nil || !strings.Contains(err.Error(), "not found") {
t.Fatalf("err: %v", err)
}
// Create a session
state := s1.fsm.State()
if err := state.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil {
t.Fatalf("err: %s", err)
}
session := &structs.Session{
ID: generateUUID(),
Node: "foo",
TTL: "10s",
}
if err := state.SessionCreate(100, session); err != nil {
t.Fatalf("err: %v", err)
}
// Reset the session timer
err = s1.resetSessionTimer(session.ID, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
// Check that we have a timer
if s1.sessionTimers.Get(session.ID) == nil {
t.Fatalf("missing session timer")
}
}
func TestResetSessionTimer_NoTTL(t *testing.T) { func TestResetSessionTimer_NoTTL(t *testing.T) {
if testing.Short() { if testing.Short() {
t.Skip("too slow for testing.Short") t.Skip("too slow for testing.Short")
@ -130,7 +86,7 @@ func TestResetSessionTimer_NoTTL(t *testing.T) {
} }
// Reset the session timer // Reset the session timer
err := s1.resetSessionTimer(session.ID, session) err := s1.resetSessionTimer(session)
if err != nil { if err != nil {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }
@ -155,7 +111,7 @@ func TestResetSessionTimer_InvalidTTL(t *testing.T) {
} }
// Reset the session timer // Reset the session timer
err := s1.resetSessionTimer(session.ID, session) err := s1.resetSessionTimer(session)
if err == nil || !strings.Contains(err.Error(), "Invalid Session TTL") { if err == nil || !strings.Contains(err.Error(), "Invalid Session TTL") {
t.Fatalf("err: %v", err) t.Fatalf("err: %v", err)
} }

View File

@ -271,7 +271,7 @@ func TestSnapshot_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)

View File

@ -741,6 +741,9 @@ func ensureServiceTxn(tx WriteTxn, idx uint64, node string, preserveIndexes bool
if err = checkGatewayWildcardsAndUpdate(tx, idx, svc); err != nil { if err = checkGatewayWildcardsAndUpdate(tx, idx, svc); err != nil {
return fmt.Errorf("failed updating gateway mapping: %s", err) return fmt.Errorf("failed updating gateway mapping: %s", err)
} }
if err := upsertKindServiceName(tx, idx, svc.Kind, svc.CompoundServiceName()); err != nil {
return fmt.Errorf("failed to persist service name: %v", err)
}
// Update upstream/downstream mappings if it's a connect service // Update upstream/downstream mappings if it's a connect service
if svc.Kind == structs.ServiceKindConnectProxy || svc.Connect.Native { if svc.Kind == structs.ServiceKindConnectProxy || svc.Connect.Native {
@ -965,16 +968,14 @@ func (s *Store) Services(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (ui
return idx, results, nil return idx, results, nil
} }
func (s *Store) ServiceList(ws memdb.WatchSet, func (s *Store) ServiceList(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
include func(svc *structs.ServiceNode) bool, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
tx := s.db.Txn(false) tx := s.db.Txn(false)
defer tx.Abort() defer tx.Abort()
return serviceListTxn(tx, ws, include, entMeta) return serviceListTxn(tx, ws, entMeta)
} }
func serviceListTxn(tx ReadTxn, ws memdb.WatchSet, func serviceListTxn(tx ReadTxn, ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
include func(svc *structs.ServiceNode) bool, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
idx := catalogServicesMaxIndex(tx, entMeta) idx := catalogServicesMaxIndex(tx, entMeta)
services, err := tx.Get(tableServices, indexID+"_prefix", entMeta) services, err := tx.Get(tableServices, indexID+"_prefix", entMeta)
@ -986,11 +987,7 @@ func serviceListTxn(tx ReadTxn, ws memdb.WatchSet,
unique := make(map[structs.ServiceName]struct{}) unique := make(map[structs.ServiceName]struct{})
for service := services.Next(); service != nil; service = services.Next() { for service := services.Next(); service != nil; service = services.Next() {
svc := service.(*structs.ServiceNode) svc := service.(*structs.ServiceNode)
// TODO (freddy) This is a hack to exclude certain kinds. unique[svc.CompoundServiceName()] = struct{}{}
// Need a new index to query by kind and namespace, have to coordinate with consul foundations first
if include == nil || include(svc) {
unique[svc.CompoundServiceName()] = struct{}{}
}
} }
results := make(structs.ServiceList, 0, len(unique)) results := make(structs.ServiceList, 0, len(unique))
@ -1691,6 +1688,9 @@ func (s *Store) deleteServiceTxn(tx WriteTxn, idx uint64, nodeName, serviceID st
if err := freeServiceVirtualIP(tx, svc.ServiceName, entMeta); err != nil { if err := freeServiceVirtualIP(tx, svc.ServiceName, entMeta); err != nil {
return fmt.Errorf("failed to clean up virtual IP for %q: %v", name.String(), err) return fmt.Errorf("failed to clean up virtual IP for %q: %v", name.String(), err)
} }
if err := cleanupKindServiceName(tx, idx, svc.CompoundServiceName(), svc.ServiceKind); err != nil {
return fmt.Errorf("failed to persist service name: %v", err)
}
} }
} else { } else {
return fmt.Errorf("Could not find any service %s: %s", svc.ServiceName, err) return fmt.Errorf("Could not find any service %s: %s", svc.ServiceName, err)
@ -2526,6 +2526,30 @@ func (s *Store) VirtualIPForService(sn structs.ServiceName) (string, error) {
return result.String(), nil return result.String(), nil
} }
func (s *Store) ServiceNamesOfKind(ws memdb.WatchSet, kind structs.ServiceKind) (uint64, []*KindServiceName, error) {
tx := s.db.Txn(false)
defer tx.Abort()
return serviceNamesOfKindTxn(tx, ws, kind)
}
func serviceNamesOfKindTxn(tx ReadTxn, ws memdb.WatchSet, kind structs.ServiceKind) (uint64, []*KindServiceName, error) {
var names []*KindServiceName
iter, err := tx.Get(tableKindServiceNames, indexKindOnly, kind)
if err != nil {
return 0, nil, err
}
ws.Add(iter.WatchCh())
idx := kindServiceNamesMaxIndex(tx, ws, kind)
for name := iter.Next(); name != nil; name = iter.Next() {
ksn := name.(*KindServiceName)
names = append(names, ksn)
}
return idx, names, nil
}
// parseCheckServiceNodes is used to parse through a given set of services, // parseCheckServiceNodes is used to parse through a given set of services,
// and query for an associated node and a set of checks. This is the inner // and query for an associated node and a set of checks. This is the inner
// method used to return a rich set of results from a more simple query. // method used to return a rich set of results from a more simple query.
@ -3862,3 +3886,44 @@ func truncateGatewayServiceTopologyMappings(tx WriteTxn, idx uint64, gateway str
return nil return nil
} }
func upsertKindServiceName(tx WriteTxn, idx uint64, kind structs.ServiceKind, name structs.ServiceName) error {
q := KindServiceNameQuery{Name: name.Name, Kind: kind, EnterpriseMeta: name.EnterpriseMeta}
existing, err := tx.First(tableKindServiceNames, indexID, q)
if err != nil {
return err
}
// Service name is already known. Nothing to do.
if existing != nil {
return nil
}
ksn := KindServiceName{
Kind: kind,
Service: name,
RaftIndex: structs.RaftIndex{
CreateIndex: idx,
ModifyIndex: idx,
},
}
if err := tx.Insert(tableKindServiceNames, &ksn); err != nil {
return fmt.Errorf("failed inserting %s/%s into %s: %s", kind, name.String(), tableKindServiceNames, err)
}
if err := indexUpdateMaxTxn(tx, idx, kindServiceNameIndexName(kind)); err != nil {
return fmt.Errorf("failed updating %s index: %v", tableKindServiceNames, err)
}
return nil
}
func cleanupKindServiceName(tx WriteTxn, idx uint64, name structs.ServiceName, kind structs.ServiceKind) error {
q := KindServiceNameQuery{Name: name.Name, Kind: kind, EnterpriseMeta: name.EnterpriseMeta}
if _, err := tx.DeleteAll(tableKindServiceNames, indexID, q); err != nil {
return fmt.Errorf("failed to delete %s from %s: %s", name, tableKindServiceNames, err)
}
if err := indexUpdateMaxTxn(tx, idx, kindServiceNameIndexName(kind)); err != nil {
return fmt.Errorf("failed updating %s index: %v", tableKindServiceNames, err)
}
return nil
}

View File

@ -5,6 +5,7 @@ package state
import ( import (
"fmt" "fmt"
"strings"
memdb "github.com/hashicorp/go-memdb" memdb "github.com/hashicorp/go-memdb"
@ -18,13 +19,7 @@ func serviceIndexName(name string, _ *structs.EnterpriseMeta) string {
} }
func serviceKindIndexName(kind structs.ServiceKind, _ *structs.EnterpriseMeta) string { func serviceKindIndexName(kind structs.ServiceKind, _ *structs.EnterpriseMeta) string {
switch kind { return "service_kind." + kind.Normalized()
case structs.ServiceKindTypical:
// needs a special case here
return "service_kind.typical"
default:
return "service_kind." + string(kind)
}
} }
func catalogUpdateNodesIndexes(tx WriteTxn, idx uint64, entMeta *structs.EnterpriseMeta) error { func catalogUpdateNodesIndexes(tx WriteTxn, idx uint64, entMeta *structs.EnterpriseMeta) error {
@ -192,3 +187,22 @@ func validateRegisterRequestTxn(_ ReadTxn, _ *structs.RegisterRequest, _ bool) (
func (s *Store) ValidateRegisterRequest(_ *structs.RegisterRequest) (*structs.EnterpriseMeta, error) { func (s *Store) ValidateRegisterRequest(_ *structs.RegisterRequest) (*structs.EnterpriseMeta, error) {
return nil, nil return nil, nil
} }
func indexFromKindServiceName(arg interface{}) ([]byte, error) {
var b indexBuilder
switch n := arg.(type) {
case KindServiceNameQuery:
b.String(strings.ToLower(string(n.Kind)))
b.String(strings.ToLower(n.Name))
return b.Bytes(), nil
case *KindServiceName:
b.String(strings.ToLower(string(n.Kind)))
b.String(strings.ToLower(n.Service.Name))
return b.Bytes(), nil
default:
return nil, fmt.Errorf("type must be KindServiceNameQuery or *KindServiceName: %T", arg)
}
}

View File

@ -412,3 +412,40 @@ func testIndexerTableServiceVirtualIPs() map[string]indexerTestCase {
}, },
} }
} }
func testIndexerTableKindServiceNames() map[string]indexerTestCase {
obj := &KindServiceName{
Service: structs.ServiceName{
Name: "web-sidecar-proxy",
},
Kind: structs.ServiceKindConnectProxy,
}
return map[string]indexerTestCase{
indexID: {
read: indexValue{
source: &KindServiceName{
Service: structs.ServiceName{
Name: "web-sidecar-proxy",
},
Kind: structs.ServiceKindConnectProxy,
},
expected: []byte("connect-proxy\x00web-sidecar-proxy\x00"),
},
write: indexValue{
source: obj,
expected: []byte("connect-proxy\x00web-sidecar-proxy\x00"),
},
},
indexKind: {
read: indexValue{
source: structs.ServiceKindConnectProxy,
expected: []byte("connect-proxy\x00"),
},
write: indexValue{
source: obj,
expected: []byte("connect-proxy\x00"),
},
},
}
}

View File

@ -19,6 +19,7 @@ const (
tableMeshTopology = "mesh-topology" tableMeshTopology = "mesh-topology"
tableServiceVirtualIPs = "service-virtual-ips" tableServiceVirtualIPs = "service-virtual-ips"
tableFreeVirtualIPs = "free-virtual-ips" tableFreeVirtualIPs = "free-virtual-ips"
tableKindServiceNames = "kind-service-names"
indexID = "id" indexID = "id"
indexService = "service" indexService = "service"
@ -661,3 +662,80 @@ func freeVirtualIPTableSchema() *memdb.TableSchema {
}, },
} }
} }
type KindServiceName struct {
Kind structs.ServiceKind
Service structs.ServiceName
structs.RaftIndex
}
func kindServiceNameTableSchema() *memdb.TableSchema {
return &memdb.TableSchema{
Name: tableKindServiceNames,
Indexes: map[string]*memdb.IndexSchema{
indexID: {
Name: indexID,
AllowMissing: false,
Unique: true,
Indexer: indexerSingle{
readIndex: indexFromKindServiceName,
writeIndex: indexFromKindServiceName,
},
},
indexKindOnly: {
Name: indexKindOnly,
AllowMissing: false,
Unique: false,
Indexer: indexerSingle{
readIndex: indexFromKindServiceNameKindOnly,
writeIndex: indexFromKindServiceNameKindOnly,
},
},
},
}
}
// KindServiceNameQuery is used to lookup service names by kind or enterprise meta.
type KindServiceNameQuery struct {
Kind structs.ServiceKind
Name string
structs.EnterpriseMeta
}
// NamespaceOrDefault exists because structs.EnterpriseMeta uses a pointer
// receiver for this method. Remove once that is fixed.
func (q KindServiceNameQuery) NamespaceOrDefault() string {
return q.EnterpriseMeta.NamespaceOrDefault()
}
// PartitionOrDefault exists because structs.EnterpriseMeta uses a pointer
// receiver for this method. Remove once that is fixed.
func (q KindServiceNameQuery) PartitionOrDefault() string {
return q.EnterpriseMeta.PartitionOrDefault()
}
func indexFromKindServiceNameKindOnly(raw interface{}) ([]byte, error) {
switch x := raw.(type) {
case *KindServiceName:
var b indexBuilder
b.String(strings.ToLower(string(x.Kind)))
return b.Bytes(), nil
case structs.ServiceKind:
var b indexBuilder
b.String(strings.ToLower(string(x)))
return b.Bytes(), nil
default:
return nil, fmt.Errorf("type must be *KindServiceName or structs.ServiceKind: %T", raw)
}
}
func kindServiceNamesMaxIndex(tx ReadTxn, ws memdb.WatchSet, kind structs.ServiceKind) uint64 {
return maxIndexWatchTxn(tx, ws, kindServiceNameIndexName(kind))
}
func kindServiceNameIndexName(kind structs.ServiceKind) string {
return "kind_service_names." + kind.Normalized()
}

View File

@ -7656,6 +7656,143 @@ func TestProtocolForIngressGateway(t *testing.T) {
} }
} }
func TestStateStore_EnsureService_ServiceNames(t *testing.T) {
s := testStateStore(t)
// Create the service registration.
entMeta := structs.DefaultEnterpriseMetaInDefaultPartition()
services := []structs.NodeService{
{
Kind: structs.ServiceKindIngressGateway,
ID: "ingress-gateway",
Service: "ingress-gateway",
Address: "2.2.2.2",
Port: 2222,
EnterpriseMeta: *entMeta,
},
{
Kind: structs.ServiceKindMeshGateway,
ID: "mesh-gateway",
Service: "mesh-gateway",
Address: "4.4.4.4",
Port: 4444,
EnterpriseMeta: *entMeta,
},
{
Kind: structs.ServiceKindConnectProxy,
ID: "connect-proxy",
Service: "connect-proxy",
Address: "1.1.1.1",
Port: 1111,
Proxy: structs.ConnectProxyConfig{DestinationServiceName: "foo"},
EnterpriseMeta: *entMeta,
},
{
Kind: structs.ServiceKindTerminatingGateway,
ID: "terminating-gateway",
Service: "terminating-gateway",
Address: "3.3.3.3",
Port: 3333,
EnterpriseMeta: *entMeta,
},
{
Kind: structs.ServiceKindTypical,
ID: "web",
Service: "web",
Address: "5.5.5.5",
Port: 5555,
EnterpriseMeta: *entMeta,
},
}
var idx uint64
testRegisterNode(t, s, idx, "node1")
for _, svc := range services {
idx++
require.NoError(t, s.EnsureService(idx, "node1", &svc))
// Ensure the service name was stored for all of them under the appropriate kind
gotIdx, gotNames, err := s.ServiceNamesOfKind(nil, svc.Kind)
require.NoError(t, err)
require.Equal(t, idx, gotIdx)
require.Len(t, gotNames, 1)
require.Equal(t, svc.CompoundServiceName(), gotNames[0].Service)
require.Equal(t, svc.Kind, gotNames[0].Kind)
}
// Register another ingress gateway and there should be two names under the kind index
newIngress := structs.NodeService{
Kind: structs.ServiceKindIngressGateway,
ID: "new-ingress-gateway",
Service: "new-ingress-gateway",
Address: "6.6.6.6",
Port: 6666,
EnterpriseMeta: *entMeta,
}
idx++
require.NoError(t, s.EnsureService(idx, "node1", &newIngress))
gotIdx, got, err := s.ServiceNamesOfKind(nil, structs.ServiceKindIngressGateway)
require.NoError(t, err)
require.Equal(t, idx, gotIdx)
expect := []*KindServiceName{
{
Kind: structs.ServiceKindIngressGateway,
Service: structs.NewServiceName("ingress-gateway", nil),
RaftIndex: structs.RaftIndex{
CreateIndex: 1,
ModifyIndex: 1,
},
},
{
Kind: structs.ServiceKindIngressGateway,
Service: structs.NewServiceName("new-ingress-gateway", nil),
RaftIndex: structs.RaftIndex{
CreateIndex: idx,
ModifyIndex: idx,
},
},
}
require.Equal(t, expect, got)
// Deregister an ingress gateway and the index should not slide back
idx++
require.NoError(t, s.DeleteService(idx, "node1", "new-ingress-gateway", entMeta))
gotIdx, got, err = s.ServiceNamesOfKind(nil, structs.ServiceKindIngressGateway)
require.NoError(t, err)
require.Equal(t, idx, gotIdx)
require.Equal(t, expect[:1], got)
// Registering another instance of a known service should not bump the kind index
newMGW := structs.NodeService{
Kind: structs.ServiceKindMeshGateway,
ID: "mesh-gateway-1",
Service: "mesh-gateway",
Address: "7.7.7.7",
Port: 7777,
EnterpriseMeta: *entMeta,
}
idx++
require.NoError(t, s.EnsureService(idx, "node1", &newMGW))
gotIdx, _, err = s.ServiceNamesOfKind(nil, structs.ServiceKindMeshGateway)
require.NoError(t, err)
require.Equal(t, uint64(2), gotIdx)
// Deregister the single typical service and the service name should also be dropped
idx++
require.NoError(t, s.DeleteService(idx, "node1", "web", entMeta))
gotIdx, got, err = s.ServiceNamesOfKind(nil, structs.ServiceKindTypical)
require.NoError(t, err)
require.Equal(t, idx, gotIdx)
require.Empty(t, got)
}
func runStep(t *testing.T, name string, fn func(t *testing.T)) { func runStep(t *testing.T, name string, fn func(t *testing.T)) {
t.Helper() t.Helper()
if !t.Run(name, fn) { if !t.Run(name, fn) {

View File

@ -395,7 +395,7 @@ func validateProposedConfigEntryInGraph(
} }
case structs.ServiceIntentions: case structs.ServiceIntentions:
case structs.MeshConfig: case structs.MeshConfig:
case structs.PartitionExports: case structs.ExportedServices:
default: default:
return fmt.Errorf("unhandled kind %q during validation of %q", kindName.Kind, kindName.Name) return fmt.Errorf("unhandled kind %q during validation of %q", kindName.Kind, kindName.Name)
} }
@ -880,24 +880,21 @@ func readDiscoveryChainConfigEntriesTxn(
sid := structs.NewServiceID(serviceName, entMeta) sid := structs.NewServiceID(serviceName, entMeta)
// Grab the proxy defaults if they exist. // At every step we'll need service and proxy defaults.
idx, proxy, err := getProxyConfigEntryTxn(tx, ws, structs.ProxyConfigGlobal, overrides, entMeta)
if err != nil {
return 0, nil, err
} else if proxy != nil {
res.GlobalProxy = proxy
}
// At every step we'll need service defaults.
todoDefaults[sid] = struct{}{} todoDefaults[sid] = struct{}{}
var maxIdx uint64
// first fetch the router, of which we only collect 1 per chain eval // first fetch the router, of which we only collect 1 per chain eval
_, router, err := getRouterConfigEntryTxn(tx, ws, serviceName, overrides, entMeta) idx, router, err := getRouterConfigEntryTxn(tx, ws, serviceName, overrides, entMeta)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} else if router != nil { } else if router != nil {
res.Routers[sid] = router res.Routers[sid] = router
} }
if idx > maxIdx {
maxIdx = idx
}
if router != nil { if router != nil {
for _, svc := range router.ListRelatedServices() { for _, svc := range router.ListRelatedServices() {
@ -922,10 +919,13 @@ func readDiscoveryChainConfigEntriesTxn(
// Yes, even for splitters. // Yes, even for splitters.
todoDefaults[splitID] = struct{}{} todoDefaults[splitID] = struct{}{}
_, splitter, err := getSplitterConfigEntryTxn(tx, ws, splitID.ID, overrides, &splitID.EnterpriseMeta) idx, splitter, err := getSplitterConfigEntryTxn(tx, ws, splitID.ID, overrides, &splitID.EnterpriseMeta)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
if idx > maxIdx {
maxIdx = idx
}
if splitter == nil { if splitter == nil {
res.Splitters[splitID] = nil res.Splitters[splitID] = nil
@ -959,10 +959,13 @@ func readDiscoveryChainConfigEntriesTxn(
// And resolvers, too. // And resolvers, too.
todoDefaults[resolverID] = struct{}{} todoDefaults[resolverID] = struct{}{}
_, resolver, err := getResolverConfigEntryTxn(tx, ws, resolverID.ID, overrides, &resolverID.EnterpriseMeta) idx, resolver, err := getResolverConfigEntryTxn(tx, ws, resolverID.ID, overrides, &resolverID.EnterpriseMeta)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
if idx > maxIdx {
maxIdx = idx
}
if resolver == nil { if resolver == nil {
res.Resolvers[resolverID] = nil res.Resolvers[resolverID] = nil
@ -987,16 +990,31 @@ func readDiscoveryChainConfigEntriesTxn(
continue // already fetched continue // already fetched
} }
_, entry, err := getServiceConfigEntryTxn(tx, ws, svcID.ID, overrides, &svcID.EnterpriseMeta) if _, ok := res.ProxyDefaults[svcID.PartitionOrDefault()]; !ok {
idx, proxy, err := getProxyConfigEntryTxn(tx, ws, structs.ProxyConfigGlobal, overrides, &svcID.EnterpriseMeta)
if err != nil {
return 0, nil, err
}
if idx > maxIdx {
maxIdx = idx
}
if proxy != nil {
res.ProxyDefaults[proxy.PartitionOrDefault()] = proxy
}
}
idx, entry, err := getServiceConfigEntryTxn(tx, ws, svcID.ID, overrides, &svcID.EnterpriseMeta)
if err != nil { if err != nil {
return 0, nil, err return 0, nil, err
} }
if idx > maxIdx {
maxIdx = idx
}
if entry == nil { if entry == nil {
res.Services[svcID] = nil res.Services[svcID] = nil
continue continue
} }
res.Services[svcID] = entry res.Services[svcID] = entry
} }
@ -1022,7 +1040,7 @@ func readDiscoveryChainConfigEntriesTxn(
} }
} }
return idx, res, nil return maxIdx, res, nil
} }
// anyKey returns any key from the provided map if any exist. Useful for using // anyKey returns any key from the provided map if any exist. Useful for using

View File

@ -1347,6 +1347,13 @@ func entrySetToKindNames(entrySet *structs.DiscoveryChainConfigEntries) []Config
&entry.EnterpriseMeta, &entry.EnterpriseMeta,
)) ))
} }
for _, entry := range entrySet.ProxyDefaults {
out = append(out, NewConfigEntryKindName(
entry.Kind,
entry.Name,
&entry.EnterpriseMeta,
))
}
return out return out
} }

View File

@ -995,36 +995,29 @@ func (s *Store) intentionTopologyTxn(tx ReadTxn, ws memdb.WatchSet,
maxIdx = index maxIdx = index
} }
// Check for a wildcard intention (* -> *) since it overrides the default decision from ACLs // TODO(tproxy): One remaining improvement is that this includes non-Connect services (typical services without a proxy)
if len(intentions) > 0 { // Ideally those should be excluded as well, since they can't be upstreams/downstreams without a proxy.
// Intentions with wildcard source and destination have the lowest precedence, so they are last in the list // Maybe narrow serviceNamesOfKindTxn to services represented by proxies? (ingress, sidecar-proxy, terminating)
ixn := intentions[len(intentions)-1] index, services, err := serviceNamesOfKindTxn(tx, ws, structs.ServiceKindTypical)
if ixn.HasWildcardSource() && ixn.HasWildcardDestination() {
defaultDecision = acl.Allow
if ixn.Action == structs.IntentionActionDeny {
defaultDecision = acl.Deny
}
}
}
index, allServices, err := serviceListTxn(tx, ws, func(svc *structs.ServiceNode) bool {
// Only include ingress gateways as downstreams, since they cannot receive service mesh traffic
// TODO(freddy): One remaining issue is that this includes non-Connect services (typical services without a proxy)
// Ideally those should be excluded as well, since they can't be upstreams/downstreams without a proxy.
// Maybe start tracking services represented by proxies? (both sidecar and ingress)
if svc.ServiceKind == structs.ServiceKindTypical || (svc.ServiceKind == structs.ServiceKindIngressGateway && downstreams) {
return true
}
return false
}, target.WithWildcardNamespace())
if err != nil { if err != nil {
return index, nil, fmt.Errorf("failed to fetch catalog service list: %v", err) return index, nil, fmt.Errorf("failed to list ingress service names: %v", err)
} }
if index > maxIdx { if index > maxIdx {
maxIdx = index maxIdx = index
} }
if downstreams {
// Ingress gateways can only ever be downstreams, since mesh services don't dial them.
index, ingress, err := serviceNamesOfKindTxn(tx, ws, structs.ServiceKindIngressGateway)
if err != nil {
return index, nil, fmt.Errorf("failed to list ingress service names: %v", err)
}
if index > maxIdx {
maxIdx = index
}
services = append(services, ingress...)
}
// When checking authorization to upstreams, the match type for the decision is `destination` because we are deciding // When checking authorization to upstreams, the match type for the decision is `destination` because we are deciding
// if upstream candidates are covered by intentions that have the target service as a source. // if upstream candidates are covered by intentions that have the target service as a source.
// The reverse is true for downstreams. // The reverse is true for downstreams.
@ -1032,11 +1025,13 @@ func (s *Store) intentionTopologyTxn(tx ReadTxn, ws memdb.WatchSet,
if downstreams { if downstreams {
decisionMatchType = structs.IntentionMatchSource decisionMatchType = structs.IntentionMatchSource
} }
result := make([]ServiceWithDecision, 0, len(allServices)) result := make([]ServiceWithDecision, 0, len(services))
for _, candidate := range allServices { for _, svc := range services {
candidate := svc.Service
if candidate.Name == structs.ConsulServiceName { if candidate.Name == structs.ConsulServiceName {
continue continue
} }
opts := IntentionDecisionOpts{ opts := IntentionDecisionOpts{
Target: candidate.Name, Target: candidate.Name,
Namespace: candidate.NamespaceOrDefault(), Namespace: candidate.NamespaceOrDefault(),

View File

@ -40,6 +40,7 @@ func newDBSchema() *memdb.DBSchema {
tombstonesTableSchema, tombstonesTableSchema,
usageTableSchema, usageTableSchema,
freeVirtualIPTableSchema, freeVirtualIPTableSchema,
kindServiceNameTableSchema,
) )
withEnterpriseSchema(db) withEnterpriseSchema(db)
return db return db

View File

@ -50,6 +50,7 @@ func TestNewDBSchema_Indexers(t *testing.T) {
tableMeshTopology: testIndexerTableMeshTopology, tableMeshTopology: testIndexerTableMeshTopology,
tableGatewayServices: testIndexerTableGatewayServices, tableGatewayServices: testIndexerTableGatewayServices,
tableServiceVirtualIPs: testIndexerTableServiceVirtualIPs, tableServiceVirtualIPs: testIndexerTableServiceVirtualIPs,
tableKindServiceNames: testIndexerTableKindServiceNames,
// KV // KV
tableKVs: testIndexerTableKVs, tableKVs: testIndexerTableKVs,
tableTombstones: testIndexerTableTombstones, tableTombstones: testIndexerTableTombstones,

View File

@ -187,3 +187,7 @@ func (s *Store) SessionList(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta)
func maxIndexTxnSessions(tx *memdb.Txn, _ *structs.EnterpriseMeta) uint64 { func maxIndexTxnSessions(tx *memdb.Txn, _ *structs.EnterpriseMeta) uint64 {
return maxIndexTxn(tx, tableSessions) return maxIndexTxn(tx, tableSessions)
} }
func (s *Store) SessionListAll(ws memdb.WatchSet) (uint64, structs.Sessions, error) {
return s.SessionList(ws, nil)
}

View File

@ -319,7 +319,7 @@ func TestTxn_Apply_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)
@ -838,7 +838,7 @@ func TestTxn_Read_ACLDeny(t *testing.T) {
dir1, s1 := testServerWithConfig(t, func(c *Config) { dir1, s1 := testServerWithConfig(t, func(c *Config) {
c.PrimaryDatacenter = "dc1" c.PrimaryDatacenter = "dc1"
c.ACLsEnabled = true c.ACLsEnabled = true
c.ACLMasterToken = "root" c.ACLInitialManagementToken = "root"
c.ACLResolverSettings.ACLDefaultPolicy = "deny" c.ACLResolverSettings.ACLDefaultPolicy = "deny"
}) })
defer os.RemoveAll(dir1) defer os.RemoveAll(dir1)

View File

@ -178,12 +178,12 @@ func TestUsageReporter_emitNodeUsage_OSS(t *testing.T) {
{Name: "kind", Value: "terminating-gateway"}, {Name: "kind", Value: "terminating-gateway"},
}, },
}, },
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=partition-exports": { "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": {
Name: "consul.usage.test.consul.state.config_entries", Name: "consul.usage.test.consul.state.config_entries",
Value: 0, Value: 0,
Labels: []metrics.Label{ Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"}, {Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "partition-exports"}, {Name: "kind", Value: "exported-services"},
}, },
}, },
}, },
@ -363,12 +363,12 @@ func TestUsageReporter_emitNodeUsage_OSS(t *testing.T) {
{Name: "kind", Value: "terminating-gateway"}, {Name: "kind", Value: "terminating-gateway"},
}, },
}, },
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=partition-exports": { "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": {
Name: "consul.usage.test.consul.state.config_entries", Name: "consul.usage.test.consul.state.config_entries",
Value: 0, Value: 0,
Labels: []metrics.Label{ Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"}, {Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "partition-exports"}, {Name: "kind", Value: "exported-services"},
}, },
}, },
}, },
@ -576,12 +576,12 @@ func TestUsageReporter_emitServiceUsage_OSS(t *testing.T) {
{Name: "kind", Value: "terminating-gateway"}, {Name: "kind", Value: "terminating-gateway"},
}, },
}, },
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=partition-exports": { "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": {
Name: "consul.usage.test.consul.state.config_entries", Name: "consul.usage.test.consul.state.config_entries",
Value: 0, Value: 0,
Labels: []metrics.Label{ Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"}, {Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "partition-exports"}, {Name: "kind", Value: "exported-services"},
}, },
}, },
}, },
@ -803,12 +803,12 @@ func TestUsageReporter_emitServiceUsage_OSS(t *testing.T) {
{Name: "kind", Value: "terminating-gateway"}, {Name: "kind", Value: "terminating-gateway"},
}, },
}, },
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=partition-exports": { "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": {
Name: "consul.usage.test.consul.state.config_entries", Name: "consul.usage.test.consul.state.config_entries",
Value: 0, Value: 0,
Labels: []metrics.Label{ Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"}, {Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "partition-exports"}, {Name: "kind", Value: "exported-services"},
}, },
}, },
}, },
@ -1007,12 +1007,12 @@ func TestUsageReporter_emitKVUsage_OSS(t *testing.T) {
{Name: "kind", Value: "terminating-gateway"}, {Name: "kind", Value: "terminating-gateway"},
}, },
}, },
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=partition-exports": { "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": {
Name: "consul.usage.test.consul.state.config_entries", Name: "consul.usage.test.consul.state.config_entries",
Value: 0, Value: 0,
Labels: []metrics.Label{ Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"}, {Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "partition-exports"}, {Name: "kind", Value: "exported-services"},
}, },
}, },
}, },
@ -1201,12 +1201,12 @@ func TestUsageReporter_emitKVUsage_OSS(t *testing.T) {
{Name: "kind", Value: "terminating-gateway"}, {Name: "kind", Value: "terminating-gateway"},
}, },
}, },
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=partition-exports": { "consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": {
Name: "consul.usage.test.consul.state.config_entries", Name: "consul.usage.test.consul.state.config_entries",
Value: 0, Value: 0,
Labels: []metrics.Label{ Labels: []metrics.Label{
{Name: "datacenter", Value: "dc1"}, {Name: "datacenter", Value: "dc1"},
{Name: "kind", Value: "partition-exports"}, {Name: "kind", Value: "exported-services"},
}, },
}, },
}, },

View File

@ -6224,11 +6224,18 @@ func TestDNS_ServiceLookup_FilterACL(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run("ACLToken == "+tt.token, func(t *testing.T) { t.Run("ACLToken == "+tt.token, func(t *testing.T) {
a := NewTestAgent(t, ` a := NewTestAgent(t, `
acl_token = "`+tt.token+`" primary_datacenter = "dc1"
acl_master_token = "root"
acl_datacenter = "dc1" acl {
acl_down_policy = "deny" enabled = true
acl_default_policy = "deny" default_policy = "deny"
down_policy = "deny"
tokens {
initial_management = "root"
default = "`+tt.token+`"
}
}
`) `)
defer a.Shutdown() defer a.Shutdown()
testrpc.WaitForLeader(t, a.RPC, "dc1") testrpc.WaitForLeader(t, a.RPC, "dc1")

View File

@ -20,6 +20,7 @@ import (
"github.com/hashicorp/consul/ipaddr" "github.com/hashicorp/consul/ipaddr"
"github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/freeport"
"github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/consul/tlsutil"
"github.com/hashicorp/consul/types"
) )
// useTLSForDcAlwaysTrue tell GRPC to always return the TLS is enabled // useTLSForDcAlwaysTrue tell GRPC to always return the TLS is enabled
@ -33,7 +34,7 @@ func TestNewDialer_WithTLSWrapper(t *testing.T) {
t.Cleanup(logError(t, lis.Close)) t.Cleanup(logError(t, lis.Close))
builder := resolver.NewServerResolverBuilder(newConfig(t)) builder := resolver.NewServerResolverBuilder(newConfig(t))
builder.AddServer(&metadata.Server{ builder.AddServer(types.AreaWAN, &metadata.Server{
Name: "server-1", Name: "server-1",
ID: "ID1", ID: "ID1",
Datacenter: "dc1", Datacenter: "dc1",
@ -84,14 +85,14 @@ func TestNewDialer_WithALPNWrapper(t *testing.T) {
}() }()
builder := resolver.NewServerResolverBuilder(newConfig(t)) builder := resolver.NewServerResolverBuilder(newConfig(t))
builder.AddServer(&metadata.Server{ builder.AddServer(types.AreaWAN, &metadata.Server{
Name: "server-1", Name: "server-1",
ID: "ID1", ID: "ID1",
Datacenter: "dc1", Datacenter: "dc1",
Addr: lis1.Addr(), Addr: lis1.Addr(),
UseTLS: true, UseTLS: true,
}) })
builder.AddServer(&metadata.Server{ builder.AddServer(types.AreaWAN, &metadata.Server{
Name: "server-2", Name: "server-2",
ID: "ID2", ID: "ID2",
Datacenter: "dc2", Datacenter: "dc2",
@ -150,10 +151,10 @@ func TestNewDialer_IntegrationWithTLSEnabledHandler(t *testing.T) {
}, hclog.New(nil)) }, hclog.New(nil))
require.NoError(t, err) require.NoError(t, err)
srv := newTestServer(t, "server-1", "dc1", tlsConf) srv := newSimpleTestServer(t, "server-1", "dc1", tlsConf)
md := srv.Metadata() md := srv.Metadata()
res.AddServer(md) res.AddServer(types.AreaWAN, md)
t.Cleanup(srv.shutdown) t.Cleanup(srv.shutdown)
pool := NewClientConnPool(ClientConnPoolConfig{ pool := NewClientConnPool(ClientConnPoolConfig{
@ -198,7 +199,7 @@ func TestNewDialer_IntegrationWithTLSEnabledHandler_viaMeshGateway(t *testing.T)
}, hclog.New(nil)) }, hclog.New(nil))
require.NoError(t, err) require.NoError(t, err)
srv := newTestServer(t, "bob", "dc1", tlsConf) srv := newSimpleTestServer(t, "bob", "dc1", tlsConf)
// Send all of the traffic to dc1's server // Send all of the traffic to dc1's server
var p tcpproxy.Proxy var p tcpproxy.Proxy
@ -211,7 +212,7 @@ func TestNewDialer_IntegrationWithTLSEnabledHandler_viaMeshGateway(t *testing.T)
}() }()
md := srv.Metadata() md := srv.Metadata()
res.AddServer(md) res.AddServer(types.AreaWAN, md)
t.Cleanup(srv.shutdown) t.Cleanup(srv.shutdown)
clientTLSConf, err := tlsutil.NewConfigurator(tlsutil.Config{ clientTLSConf, err := tlsutil.NewConfigurator(tlsutil.Config{
@ -265,8 +266,8 @@ func TestClientConnPool_IntegrationWithGRPCResolver_Failover(t *testing.T) {
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
name := fmt.Sprintf("server-%d", i) name := fmt.Sprintf("server-%d", i)
srv := newTestServer(t, name, "dc1", nil) srv := newSimpleTestServer(t, name, "dc1", nil)
res.AddServer(srv.Metadata()) res.AddServer(types.AreaWAN, srv.Metadata())
t.Cleanup(srv.shutdown) t.Cleanup(srv.shutdown)
} }
@ -280,7 +281,7 @@ func TestClientConnPool_IntegrationWithGRPCResolver_Failover(t *testing.T) {
first, err := client.Something(ctx, &testservice.Req{}) first, err := client.Something(ctx, &testservice.Req{})
require.NoError(t, err) require.NoError(t, err)
res.RemoveServer(&metadata.Server{ID: first.ServerName, Datacenter: "dc1"}) res.RemoveServer(types.AreaWAN, &metadata.Server{ID: first.ServerName, Datacenter: "dc1"})
resp, err := client.Something(ctx, &testservice.Req{}) resp, err := client.Something(ctx, &testservice.Req{})
require.NoError(t, err) require.NoError(t, err)
@ -301,8 +302,8 @@ func TestClientConnPool_ForwardToLeader_Failover(t *testing.T) {
var servers []testServer var servers []testServer
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
name := fmt.Sprintf("server-%d", i) name := fmt.Sprintf("server-%d", i)
srv := newTestServer(t, name, "dc1", nil) srv := newSimpleTestServer(t, name, "dc1", nil)
res.AddServer(srv.Metadata()) res.AddServer(types.AreaWAN, srv.Metadata())
servers = append(servers, srv) servers = append(servers, srv)
t.Cleanup(srv.shutdown) t.Cleanup(srv.shutdown)
} }
@ -351,8 +352,8 @@ func TestClientConnPool_IntegrationWithGRPCResolver_Rebalance(t *testing.T) {
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
name := fmt.Sprintf("server-%d", i) name := fmt.Sprintf("server-%d", i)
srv := newTestServer(t, name, "dc1", nil) srv := newSimpleTestServer(t, name, "dc1", nil)
res.AddServer(srv.Metadata()) res.AddServer(types.AreaWAN, srv.Metadata())
t.Cleanup(srv.shutdown) t.Cleanup(srv.shutdown)
} }
@ -405,8 +406,8 @@ func TestClientConnPool_IntegrationWithGRPCResolver_MultiDC(t *testing.T) {
for _, dc := range dcs { for _, dc := range dcs {
name := "server-0-" + dc name := "server-0-" + dc
srv := newTestServer(t, name, dc, nil) srv := newSimpleTestServer(t, name, dc, nil)
res.AddServer(srv.Metadata()) res.AddServer(types.AreaWAN, srv.Metadata())
t.Cleanup(srv.shutdown) t.Cleanup(srv.shutdown)
} }

View File

@ -9,29 +9,73 @@ import (
"time" "time"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
"google.golang.org/grpc/status"
middleware "github.com/grpc-ecosystem/go-grpc-middleware"
recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
"github.com/hashicorp/go-hclog"
) )
// NewHandler returns a gRPC server that accepts connections from Handle(conn). // NewHandler returns a gRPC server that accepts connections from Handle(conn).
// The register function will be called with the grpc.Server to register // The register function will be called with the grpc.Server to register
// gRPC services with the server. // gRPC services with the server.
func NewHandler(addr net.Addr, register func(server *grpc.Server)) *Handler { func NewHandler(logger Logger, addr net.Addr, register func(server *grpc.Server)) *Handler {
metrics := defaultMetrics() metrics := defaultMetrics()
// We don't need to pass tls.Config to the server since it's multiplexed // We don't need to pass tls.Config to the server since it's multiplexed
// behind the RPC listener, which already has TLS configured. // behind the RPC listener, which already has TLS configured.
srv := grpc.NewServer( recoveryOpts := PanicHandlerMiddlewareOpts(logger)
opts := []grpc.ServerOption{
grpc.StatsHandler(newStatsHandler(metrics)), grpc.StatsHandler(newStatsHandler(metrics)),
grpc.StreamInterceptor((&activeStreamCounter{metrics: metrics}).Intercept), middleware.WithUnaryServerChain(
// Add middlware interceptors to recover in case of panics.
recovery.UnaryServerInterceptor(recoveryOpts...),
),
middleware.WithStreamServerChain(
// Add middlware interceptors to recover in case of panics.
recovery.StreamServerInterceptor(recoveryOpts...),
(&activeStreamCounter{metrics: metrics}).Intercept,
),
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{ grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
MinTime: 15 * time.Second, MinTime: 15 * time.Second,
}), }),
) }
// We don't need to pass tls.Config to the server since it's multiplexed
// behind the RPC listener, which already has TLS configured.
srv := grpc.NewServer(opts...)
register(srv) register(srv)
lis := &chanListener{addr: addr, conns: make(chan net.Conn), done: make(chan struct{})} lis := &chanListener{addr: addr, conns: make(chan net.Conn), done: make(chan struct{})}
return &Handler{srv: srv, listener: lis} return &Handler{srv: srv, listener: lis}
} }
// PanicHandlerMiddlewareOpts returns the []recovery.Option containing
// recovery handler function.
func PanicHandlerMiddlewareOpts(logger Logger) []recovery.Option {
return []recovery.Option{
recovery.WithRecoveryHandler(NewPanicHandler(logger)),
}
}
// NewPanicHandler returns a recovery.RecoveryHandlerFunc closure function
// to handle panic in GRPC server's handlers.
func NewPanicHandler(logger Logger) recovery.RecoveryHandlerFunc {
return func(p interface{}) (err error) {
// Log the panic and the stack trace of the Goroutine that caused the panic.
stacktrace := hclog.Stacktrace()
logger.Error("panic serving grpc request",
"panic", p,
"stack", stacktrace,
)
return status.Errorf(codes.Internal, "grpc: panic serving request")
}
}
// Handler implements a handler for the rpc server listener, and the // Handler implements a handler for the rpc server listener, and the
// agent.Component interface for managing the lifecycle of the grpc.Server. // agent.Component interface for managing the lifecycle of the grpc.Server.
type Handler struct { type Handler struct {

View File

@ -0,0 +1,61 @@
package grpc
import (
"bytes"
"context"
"testing"
"time"
"github.com/hashicorp/consul/types"
"github.com/hashicorp/go-hclog"
"github.com/stretchr/testify/require"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"github.com/hashicorp/consul/agent/grpc/internal/testservice"
"github.com/hashicorp/consul/agent/grpc/resolver"
)
func TestHandler_PanicRecoveryInterceptor(t *testing.T) {
// Prepare a logger with output to a buffer
// so we can check what it writes.
var buf bytes.Buffer
logger := hclog.New(&hclog.LoggerOptions{
Output: &buf,
})
res := resolver.NewServerResolverBuilder(newConfig(t))
registerWithGRPC(t, res)
srv := newPanicTestServer(t, logger, "server-1", "dc1", nil)
res.AddServer(types.AreaWAN, srv.Metadata())
t.Cleanup(srv.shutdown)
pool := NewClientConnPool(ClientConnPoolConfig{
Servers: res,
UseTLSForDC: useTLSForDcAlwaysTrue,
DialingFromServer: true,
DialingFromDatacenter: "dc1",
})
conn, err := pool.ClientConn("dc1")
require.NoError(t, err)
client := testservice.NewSimpleClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
t.Cleanup(cancel)
resp, err := client.Something(ctx, &testservice.Req{})
expectedErr := status.Errorf(codes.Internal, "grpc: panic serving request")
require.Equal(t, expectedErr, err)
require.Nil(t, resp)
// Read the log
strLog := buf.String()
// Checking the entire stack trace is not possible, let's
// make sure that it contains a couple of expected strings.
require.Contains(t, strLog, `[ERROR] panic serving grpc request: panic="panic from Something`)
require.Contains(t, strLog, `github.com/hashicorp/consul/agent/grpc.(*simplePanic).Something`)
}

View File

@ -10,6 +10,7 @@ import (
"google.golang.org/grpc/resolver" "google.golang.org/grpc/resolver"
"github.com/hashicorp/consul/agent/metadata" "github.com/hashicorp/consul/agent/metadata"
"github.com/hashicorp/consul/types"
) )
// ServerResolverBuilder tracks the current server list and keeps any // ServerResolverBuilder tracks the current server list and keeps any
@ -18,9 +19,9 @@ type ServerResolverBuilder struct {
cfg Config cfg Config
// leaderResolver is used to track the address of the leader in the local DC. // leaderResolver is used to track the address of the leader in the local DC.
leaderResolver leaderResolver leaderResolver leaderResolver
// servers is an index of Servers by Server.ID. The map contains server IDs // servers is an index of Servers by area and Server.ID. The map contains server IDs
// for all datacenters. // for all datacenters.
servers map[string]*metadata.Server servers map[types.AreaID]map[string]*metadata.Server
// resolvers is an index of connections to the serverResolver which manages // resolvers is an index of connections to the serverResolver which manages
// addresses of servers for that connection. // addresses of servers for that connection.
resolvers map[resolver.ClientConn]*serverResolver resolvers map[resolver.ClientConn]*serverResolver
@ -37,7 +38,7 @@ type Config struct {
func NewServerResolverBuilder(cfg Config) *ServerResolverBuilder { func NewServerResolverBuilder(cfg Config) *ServerResolverBuilder {
return &ServerResolverBuilder{ return &ServerResolverBuilder{
cfg: cfg, cfg: cfg,
servers: make(map[string]*metadata.Server), servers: make(map[types.AreaID]map[string]*metadata.Server),
resolvers: make(map[resolver.ClientConn]*serverResolver), resolvers: make(map[resolver.ClientConn]*serverResolver),
} }
} }
@ -72,9 +73,11 @@ func (s *ServerResolverBuilder) ServerForGlobalAddr(globalAddr string) (*metadat
s.lock.RLock() s.lock.RLock()
defer s.lock.RUnlock() defer s.lock.RUnlock()
for _, server := range s.servers { for _, areaServers := range s.servers {
if DCPrefix(server.Datacenter, server.Addr.String()) == globalAddr { for _, server := range areaServers {
return server, nil if DCPrefix(server.Datacenter, server.Addr.String()) == globalAddr {
return server, nil
}
} }
} }
return nil, fmt.Errorf("failed to find Consul server for global address %q", globalAddr) return nil, fmt.Errorf("failed to find Consul server for global address %q", globalAddr)
@ -138,11 +141,17 @@ func (s *ServerResolverBuilder) Authority() string {
} }
// AddServer updates the resolvers' states to include the new server's address. // AddServer updates the resolvers' states to include the new server's address.
func (s *ServerResolverBuilder) AddServer(server *metadata.Server) { func (s *ServerResolverBuilder) AddServer(areaID types.AreaID, server *metadata.Server) {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
s.servers[uniqueID(server)] = server areaServers, ok := s.servers[areaID]
if !ok {
areaServers = make(map[string]*metadata.Server)
s.servers[areaID] = areaServers
}
areaServers[uniqueID(server)] = server
addrs := s.getDCAddrs(server.Datacenter) addrs := s.getDCAddrs(server.Datacenter)
for _, resolver := range s.resolvers { for _, resolver := range s.resolvers {
@ -168,11 +177,19 @@ func DCPrefix(datacenter, suffix string) string {
} }
// RemoveServer updates the resolvers' states with the given server removed. // RemoveServer updates the resolvers' states with the given server removed.
func (s *ServerResolverBuilder) RemoveServer(server *metadata.Server) { func (s *ServerResolverBuilder) RemoveServer(areaID types.AreaID, server *metadata.Server) {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
delete(s.servers, uniqueID(server)) areaServers, ok := s.servers[areaID]
if !ok {
return // already gone
}
delete(areaServers, uniqueID(server))
if len(areaServers) == 0 {
delete(s.servers, areaID)
}
addrs := s.getDCAddrs(server.Datacenter) addrs := s.getDCAddrs(server.Datacenter)
for _, resolver := range s.resolvers { for _, resolver := range s.resolvers {
@ -185,18 +202,29 @@ func (s *ServerResolverBuilder) RemoveServer(server *metadata.Server) {
// getDCAddrs returns a list of the server addresses for the given datacenter. // getDCAddrs returns a list of the server addresses for the given datacenter.
// This method requires that lock is held for reads. // This method requires that lock is held for reads.
func (s *ServerResolverBuilder) getDCAddrs(dc string) []resolver.Address { func (s *ServerResolverBuilder) getDCAddrs(dc string) []resolver.Address {
var addrs []resolver.Address var (
for _, server := range s.servers { addrs []resolver.Address
if server.Datacenter != dc { keptServerIDs = make(map[string]struct{})
continue )
} for _, areaServers := range s.servers {
for _, server := range areaServers {
if server.Datacenter != dc {
continue
}
addrs = append(addrs, resolver.Address{ // Servers may be part of multiple areas, so only include each one once.
// NOTE: the address persisted here is only dialable using our custom dialer if _, ok := keptServerIDs[server.ID]; ok {
Addr: DCPrefix(server.Datacenter, server.Addr.String()), continue
Type: resolver.Backend, }
ServerName: server.Name, keptServerIDs[server.ID] = struct{}{}
})
addrs = append(addrs, resolver.Address{
// NOTE: the address persisted here is only dialable using our custom dialer
Addr: DCPrefix(server.Datacenter, server.Addr.String()),
Type: resolver.Backend,
ServerName: server.Name,
})
}
} }
return addrs return addrs
} }

View File

@ -18,6 +18,7 @@ import (
"github.com/hashicorp/consul/agent/metadata" "github.com/hashicorp/consul/agent/metadata"
"github.com/hashicorp/consul/agent/pool" "github.com/hashicorp/consul/agent/pool"
"github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/consul/tlsutil"
"github.com/hashicorp/go-hclog"
) )
type testServer struct { type testServer struct {
@ -39,11 +40,22 @@ func (s testServer) Metadata() *metadata.Server {
} }
} }
func newTestServer(t *testing.T, name string, dc string, tlsConf *tlsutil.Configurator) testServer { func newSimpleTestServer(t *testing.T, name, dc string, tlsConf *tlsutil.Configurator) testServer {
addr := &net.IPAddr{IP: net.ParseIP("127.0.0.1")} return newTestServer(t, hclog.Default(), name, dc, tlsConf, func(server *grpc.Server) {
handler := NewHandler(addr, func(server *grpc.Server) {
testservice.RegisterSimpleServer(server, &simple{name: name, dc: dc}) testservice.RegisterSimpleServer(server, &simple{name: name, dc: dc})
}) })
}
// newPanicTestServer sets up a simple server with handlers that panic.
func newPanicTestServer(t *testing.T, logger hclog.Logger, name, dc string, tlsConf *tlsutil.Configurator) testServer {
return newTestServer(t, logger, name, dc, tlsConf, func(server *grpc.Server) {
testservice.RegisterSimpleServer(server, &simplePanic{name: name, dc: dc})
})
}
func newTestServer(t *testing.T, logger hclog.Logger, name, dc string, tlsConf *tlsutil.Configurator, register func(server *grpc.Server)) testServer {
addr := &net.IPAddr{IP: net.ParseIP("127.0.0.1")}
handler := NewHandler(logger, addr, register)
lis, err := net.Listen("tcp", "127.0.0.1:0") lis, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err) require.NoError(t, err)
@ -103,6 +115,23 @@ func (s *simple) Something(_ context.Context, _ *testservice.Req) (*testservice.
return &testservice.Resp{ServerName: s.name, Datacenter: s.dc}, nil return &testservice.Resp{ServerName: s.name, Datacenter: s.dc}, nil
} }
type simplePanic struct {
name, dc string
}
func (s *simplePanic) Flow(_ *testservice.Req, flow testservice.Simple_FlowServer) error {
for flow.Context().Err() == nil {
time.Sleep(time.Millisecond)
panic("panic from Flow")
}
return nil
}
func (s *simplePanic) Something(_ context.Context, _ *testservice.Req) (*testservice.Resp, error) {
time.Sleep(time.Millisecond)
panic("panic from Something")
}
// fakeRPCListener mimics agent/consul.Server.listen to handle the RPCType byte. // fakeRPCListener mimics agent/consul.Server.listen to handle the RPCType byte.
// In the future we should be able to refactor Server and extract this RPC // In the future we should be able to refactor Server and extract this RPC
// handling logic so that we don't need to use a fake. // handling logic so that we don't need to use a fake.

View File

@ -15,6 +15,7 @@ import (
"google.golang.org/grpc" "google.golang.org/grpc"
"github.com/hashicorp/consul/agent/grpc/internal/testservice" "github.com/hashicorp/consul/agent/grpc/internal/testservice"
"github.com/hashicorp/go-hclog"
) )
func noopRegister(*grpc.Server) {} func noopRegister(*grpc.Server) {}
@ -23,7 +24,7 @@ func TestHandler_EmitsStats(t *testing.T) {
sink, reset := patchGlobalMetrics(t) sink, reset := patchGlobalMetrics(t)
addr := &net.IPAddr{IP: net.ParseIP("127.0.0.1")} addr := &net.IPAddr{IP: net.ParseIP("127.0.0.1")}
handler := NewHandler(addr, noopRegister) handler := NewHandler(hclog.Default(), addr, noopRegister)
reset() reset()
testservice.RegisterSimpleServer(handler.srv, &simple{}) testservice.RegisterSimpleServer(handler.srv, &simple{})

Some files were not shown because too many files have changed in this diff Show More