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:
commit
4c073e0d9f
|
@ -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.
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
types: add TLSVersion and TLSCipherSuite
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:feature
|
||||
ui: Add documentation link to Partition empty state
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:bug
|
||||
ui: Fix visual issue with slight table header overflow
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:feature
|
||||
ui: Adds support for partitions to the Routing visualization.
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
server: block enterprise-specific partition-exports config entry from being used in OSS Consul.
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:feature
|
||||
ui: Adds support for partitions to Service and Node Identity template visuals.
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:feature
|
||||
ui: Adds basic support for showing Services exported from another partition.
|
||||
```
|
|
@ -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
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
partitions: **(Enterprise only)** rename APIs, commands, and public types to use "partition" rather than "admin partition".
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
connect: **(Enterprise only)** add support for cross-partition transparent proxying.
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
api: **(Enterprise Only)** rename partition-exports config entry to exported-services.
|
||||
```
|
|
@ -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)
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:bug
|
||||
areas: **(Enterprise only)** make the gRPC server tracker network area aware
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:improvement
|
||||
connect: **(Enterprise only)** add support for targeting partitions in discovery chain routes, splits, and redirects.
|
||||
```
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:feature
|
||||
partitions: **(Enterprise only)** Ensure partitions and serf-based WAN federation are mutually exclusive.
|
||||
```
|
|
@ -3,30 +3,55 @@ updates:
|
|||
- package-ecosystem: gomod
|
||||
open-pull-requests-limit: 5
|
||||
directory: "/"
|
||||
labels:
|
||||
- "go"
|
||||
- "dependencies"
|
||||
- "pr/no-changelog"
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: gomod
|
||||
open-pull-requests-limit: 5
|
||||
directory: "/api"
|
||||
labels:
|
||||
- "go"
|
||||
- "dependencies"
|
||||
- "pr/no-changelog"
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: gomod
|
||||
open-pull-requests-limit: 5
|
||||
directory: "/sdk"
|
||||
labels:
|
||||
- "go"
|
||||
- "dependencies"
|
||||
- "pr/no-changelog"
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: npm
|
||||
open-pull-requests-limit: 5
|
||||
directory: "/ui"
|
||||
labels:
|
||||
- "javascript"
|
||||
- "dependencies"
|
||||
- "pr/no-changelog"
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: npm
|
||||
open-pull-requests-limit: 5
|
||||
directory: "/website"
|
||||
labels:
|
||||
- "javascript"
|
||||
- "dependencies"
|
||||
- "type/docs-cherrypick"
|
||||
- "pr/no-changelog"
|
||||
schedule:
|
||||
interval: daily
|
||||
- package-ecosystem: github-actions
|
||||
open-pull-requests-limit: 5
|
||||
directory: /
|
||||
labels:
|
||||
- "github_actions"
|
||||
- "dependencies"
|
||||
- "pr/no-changelog"
|
||||
schedule:
|
||||
interval: daily
|
116
CHANGELOG.md
116
CHANGELOG.md
|
@ -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)
|
||||
|
||||
|
|
|
@ -16,10 +16,10 @@ type Config struct {
|
|||
|
||||
type ExportFetcher interface {
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
|
@ -91,9 +91,14 @@ func TestACL_Bootstrap(t *testing.T) {
|
|||
}
|
||||
|
||||
t.Parallel()
|
||||
a := NewTestAgent(t, TestACLConfig()+`
|
||||
acl_master_token = ""
|
||||
`)
|
||||
a := NewTestAgent(t, `
|
||||
primary_datacenter = "dc1"
|
||||
|
||||
acl {
|
||||
enabled = true
|
||||
default_policy = "deny"
|
||||
}
|
||||
`)
|
||||
defer a.Shutdown()
|
||||
|
||||
tests := []struct {
|
||||
|
@ -881,7 +886,7 @@ func TestACL_HTTP(t *testing.T) {
|
|||
require.True(t, ok)
|
||||
require.Len(t, tokens, 1)
|
||||
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.Equal(t, structs.ACLPolicyGlobalManagementID, token.Policies[0].ID)
|
||||
})
|
||||
|
@ -1689,7 +1694,7 @@ func TestACLEndpoint_LoginLogout_jwt(t *testing.T) {
|
|||
for name, tc := range cases {
|
||||
tc := tc
|
||||
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.Config = map[string]interface{}{
|
||||
"JWTSupportedAlgs": []string{"ES256"},
|
||||
|
@ -1758,7 +1763,7 @@ func TestACLEndpoint_LoginLogout_jwt(t *testing.T) {
|
|||
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.BindType = structs.BindingRuleBindTypeService
|
||||
rule.BindName = "test--${value.name}--${value.primary_org}"
|
||||
|
@ -1798,7 +1803,7 @@ func TestACLEndpoint_LoginLogout_jwt(t *testing.T) {
|
|||
|
||||
// verify the token was deleted
|
||||
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()
|
||||
|
||||
// make the request
|
||||
|
@ -1819,7 +1824,7 @@ func TestACL_Authorize(t *testing.T) {
|
|||
a1 := NewTestAgent(t, TestACLConfigWithParams(nil))
|
||||
defer a1.Shutdown()
|
||||
|
||||
testrpc.WaitForTestAgent(t, a1.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken))
|
||||
testrpc.WaitForTestAgent(t, a1.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))
|
||||
|
||||
policyReq := structs.ACLPolicySetRequest{
|
||||
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" } `,
|
||||
},
|
||||
Datacenter: "dc1",
|
||||
WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken},
|
||||
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
||||
}
|
||||
var policy structs.ACLPolicy
|
||||
require.NoError(t, a1.RPC("ACL.PolicySet", &policyReq, &policy))
|
||||
|
@ -1841,15 +1846,15 @@ func TestACL_Authorize(t *testing.T) {
|
|||
},
|
||||
},
|
||||
Datacenter: "dc1",
|
||||
WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken},
|
||||
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
||||
}
|
||||
|
||||
var token structs.ACLToken
|
||||
require.NoError(t, a1.RPC("ACL.TokenSet", &tokenReq, &token))
|
||||
|
||||
// secondary also needs to setup a replication token to pull tokens and policies
|
||||
secondaryParams := DefaulTestACLConfigParams()
|
||||
secondaryParams.ReplicationToken = secondaryParams.MasterToken
|
||||
secondaryParams := DefaultTestACLConfigParams()
|
||||
secondaryParams.ReplicationToken = secondaryParams.InitialManagementToken
|
||||
secondaryParams.EnableTokenReplication = true
|
||||
|
||||
a2 := NewTestAgent(t, `datacenter = "dc2" `+TestACLConfigWithParams(secondaryParams))
|
||||
|
@ -1859,7 +1864,7 @@ func TestACL_Authorize(t *testing.T) {
|
|||
_, err := a2.JoinWAN([]string{addr})
|
||||
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
|
||||
// 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)
|
||||
|
@ -1874,7 +1879,7 @@ func TestACL_Authorize(t *testing.T) {
|
|||
Local: true,
|
||||
},
|
||||
Datacenter: "dc2",
|
||||
WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken},
|
||||
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
||||
}
|
||||
|
||||
var localToken structs.ACLToken
|
||||
|
@ -2004,7 +2009,7 @@ func TestACL_Authorize(t *testing.T) {
|
|||
for _, dc := range []string{"dc1", "dc2"} {
|
||||
t.Run(dc, func(t *testing.T) {
|
||||
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()
|
||||
raw, err := a1.srv.ACLAuthorize(recorder, req)
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -1153,8 +1153,8 @@ func newConsulConfig(runtimeCfg *config.RuntimeConfig, logger hclog.Logger) (*co
|
|||
if runtimeCfg.RaftTrailingLogs != 0 {
|
||||
cfg.RaftConfig.TrailingLogs = uint64(runtimeCfg.RaftTrailingLogs)
|
||||
}
|
||||
if runtimeCfg.ACLMasterToken != "" {
|
||||
cfg.ACLMasterToken = runtimeCfg.ACLMasterToken
|
||||
if runtimeCfg.ACLInitialManagementToken != "" {
|
||||
cfg.ACLInitialManagementToken = runtimeCfg.ACLInitialManagementToken
|
||||
}
|
||||
cfg.ACLTokenReplication = runtimeCfg.ACLTokenReplication
|
||||
cfg.ACLsEnabled = runtimeCfg.ACLsEnabled
|
||||
|
@ -1263,6 +1263,7 @@ func newConsulConfig(runtimeCfg *config.RuntimeConfig, logger hclog.Logger) (*co
|
|||
}
|
||||
|
||||
cfg.ConfigEntryBootstrap = runtimeCfg.ConfigEntryBootstrap
|
||||
cfg.RaftBoltDBConfig = runtimeCfg.RaftBoltDBConfig
|
||||
|
||||
// Duplicate our own serf config once to make sure that the duplication
|
||||
// function does not drift.
|
||||
|
|
|
@ -1005,7 +1005,7 @@ func (s *HTTPHandlers) AgentHealthServiceByID(resp http.ResponseWriter, req *htt
|
|||
}
|
||||
notFoundReason := fmt.Sprintf("ServiceId %s not found", sid.String())
|
||||
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{
|
||||
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":
|
||||
s.agent.tokens.UpdateAgentMasterToken(args.Token, token_store.TokenSourceAPI)
|
||||
s.agent.tokens.UpdateAgentRecoveryToken(args.Token, token_store.TokenSourceAPI)
|
||||
|
||||
case "acl_replication_token", "replication":
|
||||
s.agent.tokens.UpdateReplicationToken(args.Token, token_store.TokenSourceAPI)
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -214,10 +214,14 @@ func TestAgent_TokenStore(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
a := NewTestAgent(t, `
|
||||
acl_token = "user"
|
||||
acl_agent_token = "agent"
|
||||
acl_agent_master_token = "master"`,
|
||||
)
|
||||
acl {
|
||||
tokens {
|
||||
default = "user"
|
||||
agent = "agent"
|
||||
agent_recovery = "recovery"
|
||||
}
|
||||
}
|
||||
`)
|
||||
defer a.Shutdown()
|
||||
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -5037,7 +5041,7 @@ func TestAutoConfig_Integration(t *testing.T) {
|
|||
srv := StartTestAgent(t, TestAgent{Name: "TestAgent-Server", HCL: hclConfig})
|
||||
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
|
||||
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
|
||||
// and then connect. Additionally we would have to have certificates or else the
|
||||
// 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
|
||||
require.NotEmpty(t, client.tokens.AgentToken())
|
||||
|
@ -5098,7 +5102,7 @@ func TestAutoConfig_Integration(t *testing.T) {
|
|||
ca := connect.TestCA(t, nil)
|
||||
req := &structs.CARequest{
|
||||
Datacenter: "dc1",
|
||||
WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken},
|
||||
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
||||
Config: &structs.CAConfiguration{
|
||||
Provider: "consul",
|
||||
Config: map[string]interface{}{
|
||||
|
@ -5170,7 +5174,7 @@ func TestAgent_AutoEncrypt(t *testing.T) {
|
|||
srv := StartTestAgent(t, TestAgent{Name: "test-server", HCL: hclConfig})
|
||||
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) + `
|
||||
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
|
||||
// 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
|
||||
aeCert := client.tlsConfigurator.Cert()
|
||||
|
|
|
@ -65,14 +65,12 @@ func translateConfig(c *pbconfig.Config) config.Config {
|
|||
}
|
||||
|
||||
result.ACL.Tokens = config.Tokens{
|
||||
InitialManagement: stringPtrOrNil(t.InitialManagement),
|
||||
AgentRecovery: stringPtrOrNil(t.AgentRecovery),
|
||||
Replication: stringPtrOrNil(t.Replication),
|
||||
Default: stringPtrOrNil(t.Default),
|
||||
Agent: stringPtrOrNil(t.Agent),
|
||||
ManagedServiceProvider: tokens,
|
||||
DeprecatedTokens: config.DeprecatedTokens{
|
||||
Master: stringPtrOrNil(t.Master),
|
||||
AgentMaster: stringPtrOrNil(t.AgentMaster),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,11 +69,11 @@ func TestTranslateConfig(t *testing.T) {
|
|||
EnableTokenPersistence: true,
|
||||
MSPDisableBootstrap: false,
|
||||
Tokens: &pbconfig.ACLTokens{
|
||||
Master: "99e7e490-6baf-43fc-9010-78b6aa9a6813",
|
||||
Replication: "51308d40-465c-4ac6-a636-7c0747edec89",
|
||||
AgentMaster: "e012e1ea-78a2-41cc-bc8b-231a44196f39",
|
||||
Default: "8781a3f5-de46-4b45-83e1-c92f4cfd0332",
|
||||
Agent: "ddb8f1b0-8a99-4032-b601-87926bce244e",
|
||||
InitialManagement: "99e7e490-6baf-43fc-9010-78b6aa9a6813",
|
||||
Replication: "51308d40-465c-4ac6-a636-7c0747edec89",
|
||||
AgentRecovery: "e012e1ea-78a2-41cc-bc8b-231a44196f39",
|
||||
Default: "8781a3f5-de46-4b45-83e1-c92f4cfd0332",
|
||||
Agent: "ddb8f1b0-8a99-4032-b601-87926bce244e",
|
||||
ManagedServiceProvider: []*pbconfig.ACLServiceProviderToken{
|
||||
{
|
||||
AccessorID: "23f37987-7b9e-4e5b-acae-dbc9bc137bae",
|
||||
|
@ -129,19 +129,17 @@ func TestTranslateConfig(t *testing.T) {
|
|||
EnableKeyListPolicy: boolPointer(true),
|
||||
EnableTokenPersistence: boolPointer(true),
|
||||
Tokens: config.Tokens{
|
||||
Replication: stringPointer("51308d40-465c-4ac6-a636-7c0747edec89"),
|
||||
Default: stringPointer("8781a3f5-de46-4b45-83e1-c92f4cfd0332"),
|
||||
Agent: stringPointer("ddb8f1b0-8a99-4032-b601-87926bce244e"),
|
||||
InitialManagement: stringPointer("99e7e490-6baf-43fc-9010-78b6aa9a6813"),
|
||||
AgentRecovery: stringPointer("e012e1ea-78a2-41cc-bc8b-231a44196f39"),
|
||||
Replication: stringPointer("51308d40-465c-4ac6-a636-7c0747edec89"),
|
||||
Default: stringPointer("8781a3f5-de46-4b45-83e1-c92f4cfd0332"),
|
||||
Agent: stringPointer("ddb8f1b0-8a99-4032-b601-87926bce244e"),
|
||||
ManagedServiceProvider: []config.ServiceProviderToken{
|
||||
{
|
||||
AccessorID: stringPointer("23f37987-7b9e-4e5b-acae-dbc9bc137bae"),
|
||||
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{
|
||||
|
|
|
@ -860,18 +860,18 @@ func (b *builder) build() (rt RuntimeConfig, err error) {
|
|||
ACLDefaultPolicy: stringVal(c.ACL.DefaultPolicy),
|
||||
},
|
||||
|
||||
ACLEnableKeyListPolicy: boolVal(c.ACL.EnableKeyListPolicy),
|
||||
ACLMasterToken: stringVal(c.ACL.Tokens.InitialManagement),
|
||||
ACLEnableKeyListPolicy: boolVal(c.ACL.EnableKeyListPolicy),
|
||||
ACLInitialManagementToken: stringVal(c.ACL.Tokens.InitialManagement),
|
||||
|
||||
ACLTokenReplication: boolVal(c.ACL.TokenReplication),
|
||||
|
||||
ACLTokens: token.Config{
|
||||
DataDir: dataDir,
|
||||
EnablePersistence: boolValWithDefault(c.ACL.EnableTokenPersistence, false),
|
||||
ACLDefaultToken: stringVal(c.ACL.Tokens.Default),
|
||||
ACLAgentToken: stringVal(c.ACL.Tokens.Agent),
|
||||
ACLAgentMasterToken: stringVal(c.ACL.Tokens.AgentRecovery),
|
||||
ACLReplicationToken: stringVal(c.ACL.Tokens.Replication),
|
||||
DataDir: dataDir,
|
||||
EnablePersistence: boolValWithDefault(c.ACL.EnableTokenPersistence, false),
|
||||
ACLDefaultToken: stringVal(c.ACL.Tokens.Default),
|
||||
ACLAgentToken: stringVal(c.ACL.Tokens.Agent),
|
||||
ACLAgentRecoveryToken: stringVal(c.ACL.Tokens.AgentRecovery),
|
||||
ACLReplicationToken: stringVal(c.ACL.Tokens.Replication),
|
||||
},
|
||||
|
||||
// Autopilot
|
||||
|
@ -1094,6 +1094,10 @@ func (b *builder) build() (rt RuntimeConfig, err error) {
|
|||
|
||||
rt.UseStreamingBackend = boolValWithDefault(c.UseStreamingBackend, true)
|
||||
|
||||
if c.RaftBoltDBConfig != nil {
|
||||
rt.RaftBoltDBConfig = *c.RaftBoltDBConfig
|
||||
}
|
||||
|
||||
if rt.Cache.EntryFetchMaxBurst <= 0 {
|
||||
return RuntimeConfig{}, fmt.Errorf("cache.entry_fetch_max_burst must be strictly positive, was: %v", rt.Cache.EntryFetchMaxBurst)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/hashicorp/consul/agent/consul"
|
||||
|
||||
"github.com/hashicorp/hcl"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
|
||||
|
@ -256,6 +258,8 @@ type Config struct {
|
|||
|
||||
RPC RPC `mapstructure:"rpc"`
|
||||
|
||||
RaftBoltDBConfig *consul.RaftBoltDBConfig `mapstructure:"raft_boltdb"`
|
||||
|
||||
// UseStreamingBackend instead of blocking queries for service health and
|
||||
// any other endpoints which support streaming.
|
||||
UseStreamingBackend *bool `mapstructure:"use_streaming_backend"`
|
||||
|
|
|
@ -110,8 +110,8 @@ func TestLoad_DeprecatedConfig_ACLMasterTokens(t *testing.T) {
|
|||
require.ElementsMatch(expectWarns, result.Warnings)
|
||||
|
||||
rt := result.RuntimeConfig
|
||||
require.Equal("token1", rt.ACLMasterToken)
|
||||
require.Equal("token2", rt.ACLTokens.ACLAgentMasterToken)
|
||||
require.Equal("token1", rt.ACLInitialManagementToken)
|
||||
require.Equal("token2", rt.ACLTokens.ACLAgentRecoveryToken)
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
rt := result.RuntimeConfig
|
||||
require.Equal("token1", rt.ACLMasterToken)
|
||||
require.Equal("token2", rt.ACLTokens.ACLAgentMasterToken)
|
||||
require.Equal("token1", rt.ACLInitialManagementToken)
|
||||
require.Equal("token2", rt.ACLTokens.ACLAgentRecoveryToken)
|
||||
})
|
||||
|
||||
t.Run("both", func(t *testing.T) {
|
||||
|
@ -169,7 +169,7 @@ func TestLoad_DeprecatedConfig_ACLMasterTokens(t *testing.T) {
|
|||
require.NoError(err)
|
||||
|
||||
rt := result.RuntimeConfig
|
||||
require.Equal("token3", rt.ACLMasterToken)
|
||||
require.Equal("token4", rt.ACLTokens.ACLAgentMasterToken)
|
||||
require.Equal("token3", rt.ACLInitialManagementToken)
|
||||
require.Equal("token4", rt.ACLTokens.ACLAgentRecoveryToken)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -73,12 +73,12 @@ type RuntimeConfig struct {
|
|||
// hcl: acl.enable_key_list_policy = (true|false)
|
||||
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
|
||||
// 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
|
||||
ACLMasterToken string
|
||||
ACLInitialManagementToken string
|
||||
|
||||
// ACLtokenReplication is used to indicate that both tokens and policies
|
||||
// should be replicated instead of just policies
|
||||
|
@ -943,6 +943,8 @@ type RuntimeConfig struct {
|
|||
// hcl: raft_trailing_logs = int
|
||||
RaftTrailingLogs int
|
||||
|
||||
RaftBoltDBConfig consul.RaftBoltDBConfig
|
||||
|
||||
// ReconnectTimeoutLAN specifies the amount of time to wait to reconnect with
|
||||
// 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.
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/google/go-cmp/cmp/cmpopts"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/hashicorp/consul/acl"
|
||||
"github.com/hashicorp/consul/agent/cache"
|
||||
"github.com/hashicorp/consul/agent/checks"
|
||||
"github.com/hashicorp/consul/agent/consul"
|
||||
|
@ -4085,6 +4086,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
|
|||
Service: "carrot",
|
||||
ServiceSubset: "kale",
|
||||
Namespace: "leek",
|
||||
Partition: acl.DefaultPartitionName,
|
||||
PrefixRewrite: "/alternate",
|
||||
RequestTimeout: 99 * time.Second,
|
||||
NumRetries: 12345,
|
||||
|
@ -5339,12 +5341,12 @@ func TestLoad_FullConfig(t *testing.T) {
|
|||
// user configurable values
|
||||
|
||||
ACLTokens: token.Config{
|
||||
EnablePersistence: true,
|
||||
DataDir: dataDir,
|
||||
ACLDefaultToken: "418fdff1",
|
||||
ACLAgentToken: "bed2377c",
|
||||
ACLAgentMasterToken: "1dba6aba",
|
||||
ACLReplicationToken: "5795983a",
|
||||
EnablePersistence: true,
|
||||
DataDir: dataDir,
|
||||
ACLDefaultToken: "418fdff1",
|
||||
ACLAgentToken: "bed2377c",
|
||||
ACLAgentRecoveryToken: "1dba6aba",
|
||||
ACLReplicationToken: "5795983a",
|
||||
},
|
||||
|
||||
ACLsEnabled: true,
|
||||
|
@ -5361,7 +5363,7 @@ func TestLoad_FullConfig(t *testing.T) {
|
|||
ACLRoleTTL: 9876 * time.Second,
|
||||
},
|
||||
ACLEnableKeyListPolicy: true,
|
||||
ACLMasterToken: "3820e09a",
|
||||
ACLInitialManagementToken: "3820e09a",
|
||||
ACLTokenReplication: true,
|
||||
AdvertiseAddrLAN: ipAddr("17.99.29.16"),
|
||||
AdvertiseAddrWAN: ipAddr("78.63.37.19"),
|
||||
|
@ -6015,6 +6017,7 @@ func TestLoad_FullConfig(t *testing.T) {
|
|||
"args": []interface{}{"dltjDJ2a", "flEa7C2d"},
|
||||
},
|
||||
},
|
||||
RaftBoltDBConfig: consul.RaftBoltDBConfig{NoFreelistSync: true},
|
||||
}
|
||||
entFullRuntimeConfig(expected)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"ACLEnableKeyListPolicy": false,
|
||||
"ACLMasterToken": "hidden",
|
||||
"ACLInitialManagementToken": "hidden",
|
||||
"ACLResolverSettings": {
|
||||
"ACLDefaultPolicy": "",
|
||||
"ACLDownPolicy": "",
|
||||
|
@ -14,7 +14,7 @@
|
|||
},
|
||||
"ACLTokenReplication": false,
|
||||
"ACLTokens": {
|
||||
"ACLAgentMasterToken": "hidden",
|
||||
"ACLAgentRecoveryToken": "hidden",
|
||||
"ACLAgentToken": "hidden",
|
||||
"ACLDefaultToken": "hidden",
|
||||
"ACLReplicationToken": "hidden",
|
||||
|
@ -252,6 +252,9 @@
|
|||
"RPCMaxConnsPerClient": 0,
|
||||
"RPCProtocol": 0,
|
||||
"RPCRateLimit": 0,
|
||||
"RaftBoltDBConfig": {
|
||||
"NoFreelistSync": false
|
||||
},
|
||||
"RaftProtocol": 3,
|
||||
"RaftSnapshotInterval": "0s",
|
||||
"RaftSnapshotThreshold": 0,
|
||||
|
|
|
@ -328,6 +328,9 @@ raft_protocol = 3
|
|||
raft_snapshot_threshold = 16384
|
||||
raft_snapshot_interval = "30s"
|
||||
raft_trailing_logs = 83749
|
||||
raft_boltdb {
|
||||
NoFreelistSync = true
|
||||
}
|
||||
read_replica = true
|
||||
reconnect_timeout = "23739s"
|
||||
reconnect_timeout_wan = "26694s"
|
||||
|
|
|
@ -326,6 +326,9 @@
|
|||
"raft_snapshot_threshold": 16384,
|
||||
"raft_snapshot_interval": "30s",
|
||||
"raft_trailing_logs": 83749,
|
||||
"raft_boltdb": {
|
||||
"NoFreelistSync": true
|
||||
},
|
||||
"read_replica": true,
|
||||
"reconnect_timeout": "23739s",
|
||||
"reconnect_timeout_wan": "26694s",
|
||||
|
|
|
@ -1053,7 +1053,7 @@ func (r *ACLResolver) resolveLocallyManagedToken(token string) (structs.ACLIdent
|
|||
return nil, nil, false
|
||||
}
|
||||
|
||||
if r.tokens.IsAgentMasterToken(token) {
|
||||
if r.tokens.IsAgentRecoveryToken(token) {
|
||||
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
|
||||
// remove elements the provided ACL token cannot access.
|
||||
func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
|
||||
// remove elements the provided ACL token cannot access. Returns true if
|
||||
// any elements were removed.
|
||||
func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) bool {
|
||||
nd := *dump
|
||||
|
||||
var authzContext acl.AuthorizerContext
|
||||
var removed bool
|
||||
for i := 0; i < len(nd); i++ {
|
||||
info := nd[i]
|
||||
|
||||
|
@ -1477,6 +1479,7 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
|
|||
info.FillAuthzContext(&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()))
|
||||
removed = true
|
||||
nd = append(nd[:i], nd[i+1:]...)
|
||||
i--
|
||||
continue
|
||||
|
@ -1490,6 +1493,7 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
|
|||
continue
|
||||
}
|
||||
f.logger.Debug("dropping service from result due to ACLs", "service", svc)
|
||||
removed = true
|
||||
info.Services = append(info.Services[:j], info.Services[j+1:]...)
|
||||
j--
|
||||
}
|
||||
|
@ -1502,17 +1506,21 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
|
|||
continue
|
||||
}
|
||||
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:]...)
|
||||
j--
|
||||
}
|
||||
}
|
||||
*dump = nd
|
||||
return removed
|
||||
}
|
||||
|
||||
// filterServiceDump is used to filter nodes based on ACL rules.
|
||||
func (f *aclFilter) filterServiceDump(services *structs.ServiceDump) {
|
||||
// filterServiceDump is used to filter nodes based on ACL rules. Returns true
|
||||
// if any elements were removed.
|
||||
func (f *aclFilter) filterServiceDump(services *structs.ServiceDump) bool {
|
||||
svcs := *services
|
||||
var authzContext acl.AuthorizerContext
|
||||
var removed bool
|
||||
|
||||
for i := 0; i < len(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)
|
||||
removed = true
|
||||
svcs = append(svcs[:i], svcs[i+1:]...)
|
||||
i--
|
||||
}
|
||||
*services = svcs
|
||||
return removed
|
||||
}
|
||||
|
||||
// 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.
|
||||
// We prune entries the user doesn't have access to, and we redact any tokens
|
||||
// if the user doesn't have a management token.
|
||||
func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
|
||||
// if the user doesn't have a management token. Returns true if any (named)
|
||||
// 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
|
||||
structs.DefaultEnterpriseMetaInDefaultPartition().FillAuthzContext(&authzContext)
|
||||
// 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
|
||||
// so asking for ACLWrite should be unnecessary.
|
||||
if f.authorizer.ACLWrite(&authzContext) == acl.Allow {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
// Otherwise, we need to see what the token has access to.
|
||||
var namedQueriesRemoved bool
|
||||
ret := make(structs.PreparedQueries, 0, len(*queries))
|
||||
for _, query := range *queries {
|
||||
// If no prefix ACL applies to this query then filter it, since
|
||||
// we know at this point the user doesn't have a management
|
||||
// token, otherwise see what the policy says.
|
||||
prefix, ok := query.GetACLPrefix()
|
||||
if !ok || f.authorizer.PreparedQueryRead(prefix, &authzContext) != acl.Allow {
|
||||
prefix, hasName := query.GetACLPrefix()
|
||||
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)
|
||||
continue
|
||||
}
|
||||
|
@ -1623,6 +1640,7 @@ func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
|
|||
ret = append(ret, final)
|
||||
}
|
||||
*queries = ret
|
||||
return namedQueriesRemoved
|
||||
}
|
||||
|
||||
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.
|
||||
// Returns true if any elements were removed.
|
||||
func (f *aclFilter) filterGatewayServices(mappings *structs.GatewayServices) bool {
|
||||
var removed bool
|
||||
ret := make(structs.GatewayServices, 0, len(*mappings))
|
||||
var removed bool
|
||||
for _, s := range *mappings {
|
||||
// This filter only checks ServiceRead on the linked service.
|
||||
// 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:
|
||||
v.QueryMeta.ResultsFilteredByACLs = filt.filterCheckServiceNodes(&v.Nodes)
|
||||
|
||||
case *structs.PreparedQueryExecuteResponse:
|
||||
v.QueryMeta.ResultsFilteredByACLs = filt.filterCheckServiceNodes(&v.Nodes)
|
||||
|
||||
case *structs.IndexedServiceTopology:
|
||||
filtered := filt.filterServiceTopology(v.ServiceTopology)
|
||||
if filtered {
|
||||
|
@ -1867,10 +1888,10 @@ func filterACLWithAuthorizer(logger hclog.Logger, authorizer acl.Authorizer, sub
|
|||
v.QueryMeta.ResultsFilteredByACLs = filt.filterIntentions(&v.Intentions)
|
||||
|
||||
case *structs.IndexedNodeDump:
|
||||
filt.filterNodeDump(&v.Dump)
|
||||
v.QueryMeta.ResultsFilteredByACLs = filt.filterNodeDump(&v.Dump)
|
||||
|
||||
case *structs.IndexedServiceDump:
|
||||
filt.filterServiceDump(&v.Dump)
|
||||
v.QueryMeta.ResultsFilteredByACLs = filt.filterServiceDump(&v.Dump)
|
||||
|
||||
case *structs.IndexedNodes:
|
||||
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)
|
||||
|
||||
case *structs.IndexedPreparedQueries:
|
||||
filt.filterPreparedQueries(&v.Queries)
|
||||
v.QueryMeta.ResultsFilteredByACLs = filt.filterPreparedQueries(&v.Queries)
|
||||
|
||||
case **structs.PreparedQuery:
|
||||
filt.redactPreparedQueryTokens(v)
|
||||
|
@ -1959,6 +1980,6 @@ func filterACL(r *ACLResolver, token string, subj interface{}) error {
|
|||
|
||||
type partitionInfoNoop struct{}
|
||||
|
||||
func (p *partitionInfoNoop) ExportsForPartition(partition string) acl.PartitionExports {
|
||||
return acl.PartitionExports{}
|
||||
func (p *partitionInfoNoop) ExportsForPartition(partition string) acl.ExportedServices {
|
||||
return acl.ExportedServices{}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ func TestACLEndpoint_BootstrapTokens(t *testing.T) {
|
|||
t.Parallel()
|
||||
dir, srv, codec := testACLServerWithConfig(t, func(c *Config) {
|
||||
// remove this as we are bootstrapping
|
||||
c.ACLMasterToken = ""
|
||||
c.ACLInitialManagementToken = ""
|
||||
}, false)
|
||||
waitForLeaderEstablishment(t, srv)
|
||||
|
||||
|
|
|
@ -301,7 +301,7 @@ func TestACLReplication_Tokens(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
|
@ -513,7 +513,7 @@ func TestACLReplication_Policies(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
|
@ -633,7 +633,7 @@ func TestACLReplication_TokensRedacted(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
|
@ -783,7 +783,7 @@ func TestACLReplication_AllTypes(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
|
|
|
@ -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) {
|
||||
t.Parallel()
|
||||
// Create some nodes.
|
||||
|
@ -3024,107 +3126,105 @@ func TestACL_filterSessions(t *testing.T) {
|
|||
|
||||
func TestACL_filterNodeDump(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Create a node dump.
|
||||
fill := func() structs.NodeDump {
|
||||
return structs.NodeDump{
|
||||
&structs.NodeInfo{
|
||||
Node: "node1",
|
||||
Services: []*structs.NodeService{
|
||||
{
|
||||
ID: "foo",
|
||||
Service: "foo",
|
||||
|
||||
logger := hclog.NewNullLogger()
|
||||
|
||||
makeList := func() *structs.IndexedNodeDump {
|
||||
return &structs.IndexedNodeDump{
|
||||
Dump: structs.NodeDump{
|
||||
{
|
||||
Node: "node1",
|
||||
Services: []*structs.NodeService{
|
||||
{
|
||||
ID: "foo",
|
||||
Service: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
Checks: []*structs.HealthCheck{
|
||||
{
|
||||
Node: "node1",
|
||||
CheckID: "check1",
|
||||
ServiceName: "foo",
|
||||
Checks: []*structs.HealthCheck{
|
||||
{
|
||||
Node: "node1",
|
||||
CheckID: "check1",
|
||||
ServiceName: "foo",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Try permissive filtering.
|
||||
{
|
||||
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)
|
||||
}
|
||||
}
|
||||
t.Run("allowed", func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Try restrictive filtering.
|
||||
{
|
||||
dump := fill()
|
||||
filt := newACLFilter(acl.DenyAll(), nil)
|
||||
filt.filterNodeDump(&dump)
|
||||
if len(dump) != 0 {
|
||||
t.Fatalf("bad: %#v", dump)
|
||||
}
|
||||
}
|
||||
policy, err := acl.NewPolicyFromSource(`
|
||||
service "foo" {
|
||||
policy = "read"
|
||||
}
|
||||
node "node1" {
|
||||
policy = "read"
|
||||
}
|
||||
`, acl.SyntaxLegacy, nil, nil)
|
||||
require.NoError(err)
|
||||
|
||||
// Allowed to see the service but not the node.
|
||||
policy, err := acl.NewPolicyFromSource(`
|
||||
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)
|
||||
}
|
||||
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||
require.NoError(err)
|
||||
|
||||
// But the node will block it.
|
||||
{
|
||||
dump := fill()
|
||||
filt := newACLFilter(perms, nil)
|
||||
filt.filterNodeDump(&dump)
|
||||
if len(dump) != 0 {
|
||||
t.Fatalf("bad: %#v", dump)
|
||||
}
|
||||
}
|
||||
list := makeList()
|
||||
filterACLWithAuthorizer(logger, authz, list)
|
||||
|
||||
// Chain on access to the node.
|
||||
policy, err = acl.NewPolicyFromSource(`
|
||||
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)
|
||||
}
|
||||
require.Len(list.Dump, 1)
|
||||
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||
})
|
||||
|
||||
// Now it should go through.
|
||||
{
|
||||
dump := fill()
|
||||
filt := newACLFilter(perms, 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)
|
||||
}
|
||||
}
|
||||
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.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) {
|
||||
|
@ -3155,6 +3255,277 @@ func TestACL_filterNodes(t *testing.T) {
|
|||
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) {
|
||||
t.Parallel()
|
||||
|
||||
|
@ -3353,70 +3724,97 @@ func TestFilterACL_redactTokenSecrets(t *testing.T) {
|
|||
|
||||
func TestACL_filterPreparedQueries(t *testing.T) {
|
||||
t.Parallel()
|
||||
queries := structs.PreparedQueries{
|
||||
&structs.PreparedQuery{
|
||||
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",
|
||||
},
|
||||
|
||||
logger := hclog.NewNullLogger()
|
||||
|
||||
makeList := func() *structs.IndexedPreparedQueries {
|
||||
return &structs.IndexedPreparedQueries{
|
||||
Queries: structs.PreparedQueries{
|
||||
{ID: "f004177f-2c28-83b7-4229-eacc25fe55d1"},
|
||||
{
|
||||
ID: "f004177f-2c28-83b7-4229-eacc25fe55d2",
|
||||
Name: "query-with-no-token",
|
||||
},
|
||||
{
|
||||
ID: "f004177f-2c28-83b7-4229-eacc25fe55d3",
|
||||
Name: "query-with-a-token",
|
||||
Token: "root",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
expected := structs.PreparedQueries{
|
||||
&structs.PreparedQuery{
|
||||
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",
|
||||
},
|
||||
}
|
||||
t.Run("management token", func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Try permissive filtering with a management token. This will allow the
|
||||
// embedded token to be seen.
|
||||
filt := newACLFilter(acl.ManageAll(), nil)
|
||||
filt.filterPreparedQueries(&queries)
|
||||
if !reflect.DeepEqual(queries, expected) {
|
||||
t.Fatalf("bad: %#v", queries)
|
||||
}
|
||||
list := makeList()
|
||||
filterACLWithAuthorizer(logger, acl.ManageAll(), list)
|
||||
|
||||
// Hang on to the entry with a token, which needs to survive the next
|
||||
// operation.
|
||||
original := queries[2]
|
||||
// Check we get the un-named query.
|
||||
require.Len(list.Queries, 3)
|
||||
|
||||
// Now try permissive filtering with a client token, which should cause
|
||||
// the embedded token to get redacted, and the query with no name to get
|
||||
// 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)
|
||||
}
|
||||
// Check we get the un-redacted token.
|
||||
require.Equal("root", list.Queries[2].Token)
|
||||
|
||||
// Make sure that the original object didn't lose its token.
|
||||
if original.Token != "root" {
|
||||
t.Fatalf("bad token: %s", original.Token)
|
||||
}
|
||||
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||
})
|
||||
|
||||
// Now try restrictive filtering.
|
||||
filt = newACLFilter(acl.DenyAll(), nil)
|
||||
filt.filterPreparedQueries(&queries)
|
||||
if len(queries) != 0 {
|
||||
t.Fatalf("bad: %#v", queries)
|
||||
}
|
||||
t.Run("permissive filtering", func(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
list := makeList()
|
||||
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) {
|
||||
|
@ -3622,7 +4020,7 @@ func TestACLResolver_AgentMaster(t *testing.T) {
|
|||
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")
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -44,7 +44,7 @@ func testACLTokenReap_Primary(t *testing.T, local, global bool) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
||||
c.ACLTokenMaxExpirationTTL = 8 * time.Second
|
||||
})
|
||||
|
|
|
@ -71,76 +71,8 @@ type Catalog struct {
|
|||
logger hclog.Logger
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// Register is used register that a node is providing a given service.
|
||||
// Register a service and/or check(s) in a node, creating the node if it doesn't exist.
|
||||
// It is valid to pass no service or checks to simply create the node itself.
|
||||
func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error {
|
||||
if done, err := c.srv.ForwardRPC("Catalog.Register", args, reply); done {
|
||||
return err
|
||||
|
@ -212,6 +144,75 @@ func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error
|
|||
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
|
||||
// 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
|
||||
|
@ -330,7 +331,13 @@ func vetRegisterWithACL(
|
|||
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 {
|
||||
if done, err := c.srv.ForwardRPC("Catalog.Deregister", args, reply); done {
|
||||
return err
|
||||
|
@ -458,7 +465,7 @@ func (c *Catalog) ListDatacenters(args *structs.DatacentersRequest, reply *[]str
|
|||
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 {
|
||||
if done, err := c.srv.ForwardRPC("Catalog.ListNodes", args, reply); done {
|
||||
return err
|
||||
|
@ -509,7 +516,8 @@ func isUnmodified(opts structs.QueryOptions, index uint64) bool {
|
|||
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 {
|
||||
if done, err := c.srv.ForwardRPC("Catalog.ListServices", args, reply); done {
|
||||
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 {
|
||||
if done, err := c.srv.ForwardRPC("Catalog.ServiceList", args, reply); done {
|
||||
return err
|
||||
|
@ -570,7 +580,7 @@ func (c *Catalog) ServiceList(args *structs.DCSpecificRequest, reply *structs.In
|
|||
&args.QueryOptions,
|
||||
&reply.QueryMeta,
|
||||
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 {
|
||||
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 {
|
||||
if done, err := c.srv.ForwardRPC("Catalog.ServiceNodes", args, reply); done {
|
||||
return err
|
||||
|
@ -721,7 +731,8 @@ func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *stru
|
|||
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 {
|
||||
if done, err := c.srv.ForwardRPC("Catalog.NodeServices", args, reply); done {
|
||||
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 {
|
||||
if done, err := c.srv.ForwardRPC("Catalog.NodeServiceList", args, reply); done {
|
||||
return err
|
||||
|
|
|
@ -184,7 +184,7 @@ func TestCatalog_Register_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -452,7 +452,7 @@ func TestCatalog_Register_ConnectProxy_ACLDestinationServiceName(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -570,7 +570,7 @@ func TestCatalog_Deregister_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1297,7 +1297,7 @@ func TestCatalog_ListNodes_ACLFilter(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -2409,7 +2409,7 @@ func TestCatalog_ListServiceNodes_ConnectProxy_ACL(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
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) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
|
||||
|
@ -2855,7 +2855,7 @@ func TestCatalog_NodeServices_ACL(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -3258,7 +3258,7 @@ func TestCatalog_GatewayServices_ACLFiltering(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -3970,7 +3970,7 @@ func TestCatalog_VirtualIPForService_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
c.Build = "1.11.0"
|
||||
})
|
||||
|
|
|
@ -180,10 +180,10 @@ type Config struct {
|
|||
// ACLEnabled is used to enable ACLs
|
||||
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
|
||||
// that the Master token is available. This provides the initial token.
|
||||
ACLMasterToken string
|
||||
// that the initial management token is available. This provides the initial token.
|
||||
ACLInitialManagementToken string
|
||||
|
||||
// ACLTokenReplication is used to enabled token replication.
|
||||
//
|
||||
|
@ -391,6 +391,8 @@ type Config struct {
|
|||
|
||||
RPCConfig RPCConfig
|
||||
|
||||
RaftBoltDBConfig RaftBoltDBConfig
|
||||
|
||||
// Embedded Consul Enterprise specific configuration
|
||||
*EnterpriseConfig
|
||||
}
|
||||
|
@ -603,3 +605,7 @@ type ReloadableConfig struct {
|
|||
RaftSnapshotInterval time.Duration
|
||||
RaftTrailingLogs int
|
||||
}
|
||||
|
||||
type RaftBoltDBConfig struct {
|
||||
NoFreelistSync bool
|
||||
}
|
||||
|
|
|
@ -594,10 +594,10 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
|
|||
}
|
||||
|
||||
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
|
||||
// and services cannot be exported to another datacenter.
|
||||
if kind != structs.PartitionExports {
|
||||
if kind != structs.ExportedServices {
|
||||
return nil
|
||||
}
|
||||
if localDC == "" {
|
||||
|
@ -611,10 +611,10 @@ func gateWriteToSecondary(targetDC, localDC, primaryDC, kind string) error {
|
|||
|
||||
switch {
|
||||
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:
|
||||
return fmt.Errorf("partition-exports writes must not target secondary datacenters.")
|
||||
return fmt.Errorf("exported-services writes must not target secondary datacenters.")
|
||||
|
||||
}
|
||||
return nil
|
||||
|
|
|
@ -155,7 +155,7 @@ func TestConfigEntry_Apply_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -271,7 +271,7 @@ func TestConfigEntry_Get_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -471,7 +471,7 @@ func TestConfigEntry_List_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -545,7 +545,7 @@ func TestConfigEntry_ListAll_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -750,7 +750,7 @@ func TestConfigEntry_Delete_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1959,7 +1959,7 @@ func TestConfigEntry_ResolveServiceConfig_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -2093,7 +2093,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
|||
targetDC: "",
|
||||
localDC: "dc1",
|
||||
primaryDC: "",
|
||||
kind: structs.PartitionExports,
|
||||
kind: structs.ExportedServices,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -2102,7 +2102,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
|||
targetDC: "",
|
||||
localDC: "dc1",
|
||||
primaryDC: "dc1",
|
||||
kind: structs.PartitionExports,
|
||||
kind: structs.ExportedServices,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -2111,7 +2111,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
|||
targetDC: "dc1",
|
||||
localDC: "dc1",
|
||||
primaryDC: "dc1",
|
||||
kind: structs.PartitionExports,
|
||||
kind: structs.ExportedServices,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -2120,7 +2120,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
|||
targetDC: "dc2",
|
||||
localDC: "dc1",
|
||||
primaryDC: "",
|
||||
kind: structs.PartitionExports,
|
||||
kind: structs.ExportedServices,
|
||||
},
|
||||
wantErr: "writes must not target secondary datacenters",
|
||||
},
|
||||
|
@ -2130,7 +2130,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
|||
targetDC: "dc2",
|
||||
localDC: "dc1",
|
||||
primaryDC: "dc1",
|
||||
kind: structs.PartitionExports,
|
||||
kind: structs.ExportedServices,
|
||||
},
|
||||
wantErr: "writes must not target secondary datacenters",
|
||||
},
|
||||
|
@ -2140,7 +2140,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
|||
targetDC: "dc2",
|
||||
localDC: "dc2",
|
||||
primaryDC: "dc1",
|
||||
kind: structs.PartitionExports,
|
||||
kind: structs.ExportedServices,
|
||||
},
|
||||
wantErr: "writes must not target secondary datacenters",
|
||||
},
|
||||
|
@ -2150,7 +2150,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
|||
targetDC: "",
|
||||
localDC: "dc2",
|
||||
primaryDC: "dc1",
|
||||
kind: structs.PartitionExports,
|
||||
kind: structs.ExportedServices,
|
||||
},
|
||||
wantErr: "must target the primary datacenter explicitly",
|
||||
},
|
||||
|
@ -2158,7 +2158,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
|||
name: "empty local DC",
|
||||
args: args{
|
||||
localDC: "",
|
||||
kind: structs.PartitionExports,
|
||||
kind: structs.ExportedServices,
|
||||
},
|
||||
wantErr: "unknown local datacenter",
|
||||
},
|
||||
|
@ -2179,7 +2179,7 @@ func Test_gateWriteToSecondary_AllowedKinds(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, kind := range structs.AllConfigEntryKinds {
|
||||
if kind == structs.PartitionExports {
|
||||
if kind == structs.ExportedServices {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
@ -92,8 +92,8 @@ func (s *Server) reconcileLocalConfig(ctx context.Context, configs []structs.Con
|
|||
defer ticker.Stop()
|
||||
|
||||
for i, entry := range configs {
|
||||
// Partition exports only apply to the primary datacenter.
|
||||
if entry.GetKind() == structs.PartitionExports {
|
||||
// Exported services only apply to the primary datacenter.
|
||||
if entry.GetKind() == structs.ExportedServices {
|
||||
continue
|
||||
}
|
||||
req := structs.ConfigEntryRequest{
|
||||
|
|
|
@ -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) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
|
|
|
@ -163,7 +163,7 @@ func TestConnectCAConfig_GetSet_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = TestDefaultMasterToken
|
||||
c.ACLInitialManagementToken = TestDefaultMasterToken
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1108,7 +1108,7 @@ func TestConnectCASignValidation(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
|
|
@ -191,7 +191,7 @@ func TestCoordinate_Update_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -349,7 +349,7 @@ func TestCoordinate_ListNodes_ACLFilter(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -524,7 +524,7 @@ func TestCoordinate_Node_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
|
|
@ -26,7 +26,7 @@ func TestDiscoveryChainEndpoint_Get(t *testing.T) {
|
|||
c.PrimaryDatacenter = "dc1"
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
|
|
@ -185,7 +185,7 @@ type customizationMarkers struct {
|
|||
// the String() method on the type itself. It is this way to be more
|
||||
// consistent with other string ids within the discovery chain.
|
||||
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 {
|
||||
|
@ -213,10 +213,10 @@ func (c *compiler) recordServiceProtocol(sid structs.ServiceID) error {
|
|||
if serviceDefault := c.entries.GetService(sid); serviceDefault != nil {
|
||||
return c.recordProtocol(sid, serviceDefault.Protocol)
|
||||
}
|
||||
if c.entries.GlobalProxy != nil {
|
||||
if proxyDefault := c.entries.GetProxyDefaults(sid.PartitionOrDefault()); proxyDefault != nil {
|
||||
var cfg proxyConfig
|
||||
// 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 != "" {
|
||||
return c.recordProtocol(sid, cfg.Protocol)
|
||||
}
|
||||
|
@ -567,11 +567,12 @@ func (c *compiler) assembleChain() error {
|
|||
dest = &structs.ServiceRouteDestination{
|
||||
Service: c.serviceName,
|
||||
Namespace: router.NamespaceOrDefault(),
|
||||
Partition: router.PartitionOrDefault(),
|
||||
}
|
||||
}
|
||||
svc := defaultIfEmpty(dest.Service, c.serviceName)
|
||||
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.
|
||||
var (
|
||||
|
@ -602,7 +603,7 @@ func (c *compiler) assembleChain() error {
|
|||
}
|
||||
|
||||
defaultRoute := &structs.DiscoveryRoute{
|
||||
Definition: newDefaultServiceRoute(router.Name, router.NamespaceOrDefault()),
|
||||
Definition: newDefaultServiceRoute(router.Name, router.NamespaceOrDefault(), router.PartitionOrDefault()),
|
||||
NextNode: defaultDestinationNode.MapKey(),
|
||||
}
|
||||
routeNode.Routes = append(routeNode.Routes, defaultRoute)
|
||||
|
@ -613,7 +614,7 @@ func (c *compiler) assembleChain() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func newDefaultServiceRoute(serviceName string, namespace string) *structs.ServiceRoute {
|
||||
func newDefaultServiceRoute(serviceName, namespace, partition string) *structs.ServiceRoute {
|
||||
return &structs.ServiceRoute{
|
||||
Match: &structs.ServiceRouteMatch{
|
||||
HTTP: &structs.ServiceRouteHTTPMatch{
|
||||
|
@ -623,6 +624,7 @@ func newDefaultServiceRoute(serviceName string, namespace string) *structs.Servi
|
|||
Destination: &structs.ServiceRouteDestination{
|
||||
Service: serviceName,
|
||||
Namespace: namespace,
|
||||
Partition: partition,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -836,7 +838,7 @@ RESOLVE_AGAIN:
|
|||
target,
|
||||
redirect.Service,
|
||||
redirect.ServiceSubset,
|
||||
target.Partition,
|
||||
redirect.Partition,
|
||||
redirect.Namespace,
|
||||
redirect.Datacenter,
|
||||
)
|
||||
|
@ -940,9 +942,9 @@ RESOLVE_AGAIN:
|
|||
if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil {
|
||||
target.MeshGateway = serviceDefault.MeshGateway
|
||||
}
|
||||
|
||||
if c.entries.GlobalProxy != nil && target.MeshGateway.Mode == structs.MeshGatewayModeDefault {
|
||||
target.MeshGateway.Mode = c.entries.GlobalProxy.MeshGateway.Mode
|
||||
proxyDefault := c.entries.GetProxyDefaults(targetID.PartitionOrDefault())
|
||||
if proxyDefault != nil && target.MeshGateway.Mode == structs.MeshGatewayModeDefault {
|
||||
target.MeshGateway.Mode = proxyDefault.MeshGateway.Mode
|
||||
}
|
||||
|
||||
if c.overrideMeshGateway.Mode != structs.MeshGatewayModeDefault {
|
||||
|
|
|
@ -158,14 +158,14 @@ func testcase_JustRouterWithDefaults() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "resolver:main.default.default.dc1",
|
||||
},
|
||||
},
|
||||
|
@ -210,11 +210,11 @@ func testcase_JustRouterWithNoDestination() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: &structs.ServiceRoute{
|
||||
|
@ -227,7 +227,7 @@ func testcase_JustRouterWithNoDestination() compileTestCase {
|
|||
NextNode: "resolver:main.default.default.dc1",
|
||||
},
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "resolver:main.default.default.dc1",
|
||||
},
|
||||
},
|
||||
|
@ -270,14 +270,14 @@ func testcase_RouterWithDefaults_NoSplit_WithResolver() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "resolver:main.default.default.dc1",
|
||||
},
|
||||
},
|
||||
|
@ -321,21 +321,21 @@ func testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver() compileTestCase
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
NextNode: "splitter:main.default",
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "splitter:main.default.default",
|
||||
},
|
||||
},
|
||||
},
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -386,21 +386,21 @@ func testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults() compileTestC
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
NextNode: "splitter:main.default",
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "splitter:main.default.default",
|
||||
},
|
||||
},
|
||||
},
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -458,21 +458,21 @@ func testcase_RouterWithDefaults_WithNoopSplit_WithResolver() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
NextNode: "splitter:main.default",
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "splitter:main.default.default",
|
||||
},
|
||||
},
|
||||
},
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -542,18 +542,18 @@ func testcase_RouteBypassesSplit() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: &router.Routes[0],
|
||||
NextNode: "resolver:bypass.other.default.default.dc1",
|
||||
},
|
||||
{
|
||||
Definition: newDefaultServiceRoute("main", "default"),
|
||||
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||
NextNode: "resolver:main.default.default.dc1",
|
||||
},
|
||||
},
|
||||
|
@ -605,11 +605,11 @@ func testcase_NoopSplit_DefaultResolver() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -661,11 +661,11 @@ func testcase_NoopSplit_WithResolver() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -724,11 +724,11 @@ func testcase_SubsetSplit() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -801,11 +801,11 @@ func testcase_ServiceSplit() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -898,11 +898,11 @@ func testcase_SplitBypassesSplit() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -1053,13 +1053,14 @@ func testcase_DatacenterRedirect() compileTestCase {
|
|||
|
||||
func testcase_DatacenterRedirect_WithMeshGateways() compileTestCase {
|
||||
entries := newEntries()
|
||||
entries.GlobalProxy = &structs.ProxyConfigEntry{
|
||||
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
|
||||
Kind: structs.ProxyDefaults,
|
||||
Name: structs.ProxyConfigGlobal,
|
||||
MeshGateway: structs.MeshGatewayConfig{
|
||||
Mode: structs.MeshGatewayModeRemote,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
entries.AddResolvers(
|
||||
&structs.ServiceResolverConfigEntry{
|
||||
Kind: "service-resolver",
|
||||
|
@ -1300,13 +1301,15 @@ func testcase_DatacenterFailover() compileTestCase {
|
|||
|
||||
func testcase_DatacenterFailover_WithMeshGateways() compileTestCase {
|
||||
entries := newEntries()
|
||||
entries.GlobalProxy = &structs.ProxyConfigEntry{
|
||||
|
||||
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
|
||||
Kind: structs.ProxyDefaults,
|
||||
Name: structs.ProxyConfigGlobal,
|
||||
MeshGateway: structs.MeshGatewayConfig{
|
||||
Mode: structs.MeshGatewayModeRemote,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
entries.AddResolvers(
|
||||
&structs.ServiceResolverConfigEntry{
|
||||
Kind: "service-resolver",
|
||||
|
@ -1384,11 +1387,11 @@ func testcase_NoopSplit_WithDefaultSubset() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -1446,7 +1449,8 @@ func testcase_DefaultResolver() compileTestCase {
|
|||
|
||||
func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
|
||||
entries := newEntries()
|
||||
entries.GlobalProxy = &structs.ProxyConfigEntry{
|
||||
|
||||
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
|
||||
Kind: structs.ProxyDefaults,
|
||||
Name: structs.ProxyConfigGlobal,
|
||||
Config: map[string]interface{}{
|
||||
|
@ -1455,7 +1459,7 @@ func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
|
|||
MeshGateway: structs.MeshGatewayConfig{
|
||||
Mode: structs.MeshGatewayModeRemote,
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "grpc",
|
||||
|
@ -1699,11 +1703,11 @@ func testcase_MultiDatacenterCanary() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -1880,11 +1884,11 @@ func testcase_AllBellsAndWhistles() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "router:main.default",
|
||||
StartNode: "router:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"router:main.default": {
|
||||
"router:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Routes: []*structs.DiscoveryRoute{
|
||||
{
|
||||
Definition: &router.Routes[0],
|
||||
|
@ -1892,17 +1896,17 @@ func testcase_AllBellsAndWhistles() compileTestCase {
|
|||
},
|
||||
{
|
||||
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",
|
||||
},
|
||||
},
|
||||
},
|
||||
"splitter:svc-split.default": {
|
||||
"splitter:svc-split.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "svc-split.default",
|
||||
Name: "svc-split.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -2455,11 +2459,11 @@ func testcase_LBSplitterAndResolver() compileTestCase {
|
|||
|
||||
expect := &structs.CompiledDiscoveryChain{
|
||||
Protocol: "http",
|
||||
StartNode: "splitter:main.default",
|
||||
StartNode: "splitter:main.default.default",
|
||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||
"splitter:main.default": {
|
||||
"splitter:main.default.default": {
|
||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||
Name: "main.default",
|
||||
Name: "main.default.default",
|
||||
Splits: []*structs.DiscoverySplit{
|
||||
{
|
||||
Definition: &structs.ServiceSplit{
|
||||
|
@ -2642,13 +2646,13 @@ func newSimpleRoute(name string, muts ...func(*structs.ServiceRoute)) structs.Se
|
|||
}
|
||||
|
||||
func setGlobalProxyProtocol(entries *structs.DiscoveryChainConfigEntries, protocol string) {
|
||||
entries.GlobalProxy = &structs.ProxyConfigEntry{
|
||||
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
|
||||
Kind: structs.ProxyDefaults,
|
||||
Name: structs.ProxyConfigGlobal,
|
||||
Config: map[string]interface{}{
|
||||
"protocol": protocol,
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func setServiceProtocol(entries *structs.DiscoveryChainConfigEntries, name, protocol string) {
|
||||
|
|
|
@ -88,7 +88,7 @@ func (s *Server) validateEnterpriseIntentionNamespace(ns string, _ bool) error {
|
|||
func (s *Server) setupSerfLAN(config *Config) error {
|
||||
var err error
|
||||
// Initialize the LAN Serf for the default network segment.
|
||||
s.serfLAN, err = s.setupSerf(setupSerfOptions{
|
||||
s.serfLAN, _, err = s.setupSerf(setupSerfOptions{
|
||||
Config: config.SerfLANConfig,
|
||||
EventCh: s.eventChLAN,
|
||||
SnapshotPath: serfLANSnapshot,
|
||||
|
|
|
@ -116,7 +116,7 @@ func TestFederationState_Apply_Upsert_ACLDeny(t *testing.T) {
|
|||
c.DisableFederationStateAntiEntropy = true
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -237,7 +237,7 @@ func TestFederationState_Get_ACLDeny(t *testing.T) {
|
|||
c.DisableFederationStateAntiEntropy = true
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -409,7 +409,7 @@ func TestFederationState_List_ACLDeny(t *testing.T) {
|
|||
c.PrimaryDatacenter = "dc1"
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -425,7 +425,7 @@ func TestFederationState_List_ACLDeny(t *testing.T) {
|
|||
c.PrimaryDatacenter = "dc1"
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir2)
|
||||
|
@ -695,7 +695,7 @@ func TestFederationState_Apply_Delete_ACLDeny(t *testing.T) {
|
|||
c.DisableFederationStateAntiEntropy = true
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
|
|
@ -464,6 +464,14 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
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
|
||||
snap, err := fsm.Snapshot()
|
||||
require.NoError(t, err)
|
||||
|
@ -690,10 +698,10 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
|
|||
require.Len(t, roots, 2)
|
||||
|
||||
// Verify provider state is restored.
|
||||
_, state, err := fsm2.state.CAProviderState("asdf")
|
||||
_, provider, err := fsm2.state.CAProviderState("asdf")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "foo", state.PrivateKey)
|
||||
require.Equal(t, "bar", state.RootCert)
|
||||
require.Equal(t, "foo", provider.PrivateKey)
|
||||
require.Equal(t, "bar", provider.RootCert)
|
||||
|
||||
// Verify CA configuration is restored.
|
||||
_, caConf, err := fsm2.state.CAConfig(nil)
|
||||
|
@ -751,6 +759,14 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
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
|
||||
snap, err = fsm2.Snapshot()
|
||||
require.NoError(t, err)
|
||||
|
|
|
@ -983,7 +983,7 @@ func TestHealth_ServiceNodes_ConnectProxy_ACL(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1289,7 +1289,7 @@ func TestHealth_ServiceNodes_Ingress_ACL(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
|
|
@ -867,7 +867,7 @@ func TestIntentionApply_aclDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1257,7 +1257,7 @@ func TestIntentionApply_aclDelete(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1323,7 +1323,7 @@ func TestIntentionApply_aclUpdate(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1377,7 +1377,7 @@ func TestIntentionApply_aclManagement(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1422,7 +1422,7 @@ func TestIntentionApply_aclUpdateChange(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1472,7 +1472,7 @@ func TestIntentionGet_acl(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1879,7 +1879,7 @@ func TestIntentionCheck_defaultACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1915,7 +1915,7 @@ func TestIntentionCheck_defaultACLAllow(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1951,7 +1951,7 @@ func TestIntentionCheck_aclDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
|
|
@ -72,18 +72,21 @@ func (m *Internal) NodeDump(args *structs.DCSpecificRequest,
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
reply.Index, reply.Dump = index, dump
|
||||
if err := m.srv.filterACL(args.Token, reply); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
raw, err := filter.Execute(reply.Dump)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
@ -114,10 +117,6 @@ func (m *Internal) ServiceDump(args *structs.ServiceDumpRequest, reply *structs.
|
|||
}
|
||||
reply.Nodes = nodes
|
||||
|
||||
if err := m.srv.filterACL(args.Token, &reply.Nodes); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get, store, and filter gateway services
|
||||
idx, gatewayServices, err := state.DumpGatewayServices(ws)
|
||||
if err != nil {
|
||||
|
|
|
@ -461,7 +461,7 @@ func TestInternal_NodeInfo_FilterACL(t *testing.T) {
|
|||
QueryOptions: structs.QueryOptions{Token: token},
|
||||
}
|
||||
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)
|
||||
}
|
||||
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
|
||||
// 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
|
||||
|
@ -515,7 +519,7 @@ func TestInternal_NodeDump_FilterACL(t *testing.T) {
|
|||
QueryOptions: structs.QueryOptions{Token: token},
|
||||
}
|
||||
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)
|
||||
}
|
||||
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
|
||||
// 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
|
||||
|
@ -562,7 +570,7 @@ func TestInternal_EventFire_Token(t *testing.T) {
|
|||
dir, srv := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDownPolicy = "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) {
|
||||
if 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) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
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].Service.Service, "db")
|
||||
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) {
|
||||
|
@ -1308,7 +1528,7 @@ func TestInternal_GatewayServiceDump_Ingress_ACL(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1972,7 +2192,7 @@ func TestInternal_ServiceTopology_ACL(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = TestDefaultMasterToken
|
||||
c.ACLInitialManagementToken = TestDefaultMasterToken
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -2111,7 +2331,7 @@ func TestInternal_IntentionUpstreams_ACL(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = TestDefaultMasterToken
|
||||
c.ACLInitialManagementToken = TestDefaultMasterToken
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
|
|
@ -84,7 +84,7 @@ func TestKVS_Apply_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -189,7 +189,7 @@ func TestKVS_Get_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -413,7 +413,7 @@ func TestKVSEndpoint_List_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -492,7 +492,7 @@ func TestKVSEndpoint_List_ACLEnableKeyListPolicy(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
c.ACLEnableKeyListPolicy = true
|
||||
})
|
||||
|
@ -684,7 +684,7 @@ func TestKVSEndpoint_ListKeys_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
|
|
@ -431,28 +431,28 @@ func (s *Server) initializeACLs(ctx context.Context) error {
|
|||
s.logger.Info("Created ACL 'global-management' policy")
|
||||
}
|
||||
|
||||
// Check for configured master token.
|
||||
if master := s.config.ACLMasterToken; len(master) > 0 {
|
||||
// Check for configured initial management token.
|
||||
if initialManagement := s.config.ACLInitialManagementToken; len(initialManagement) > 0 {
|
||||
state := s.fsm.State()
|
||||
if _, err := uuid.ParseUUID(master); err != nil {
|
||||
s.logger.Warn("Configuring a non-UUID master token is deprecated")
|
||||
if _, err := uuid.ParseUUID(initialManagement); err != nil {
|
||||
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 {
|
||||
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.
|
||||
if token == nil {
|
||||
accessor, err := lib.GenerateUUID(s.checkTokenUUID)
|
||||
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{
|
||||
AccessorID: accessor,
|
||||
SecretID: master,
|
||||
Description: "Master Token",
|
||||
SecretID: initialManagement,
|
||||
Description: "Initial Management Token",
|
||||
Policies: []structs.ACLTokenPolicyLink{
|
||||
{
|
||||
ID: structs.ACLPolicyGlobalManagementID,
|
||||
|
@ -472,12 +472,12 @@ func (s *Server) initializeACLs(ctx context.Context) error {
|
|||
ResetIndex: 0,
|
||||
}
|
||||
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
|
||||
} else {
|
||||
if err.Error() != structs.ACLBootstrapNotAllowedErr.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,
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -204,7 +204,7 @@ func TestCAManager_Initialize_Secondary(t *testing.T) {
|
|||
c.PrimaryDatacenter = "primary"
|
||||
c.Build = "1.6.0"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = masterToken
|
||||
c.ACLInitialManagementToken = masterToken
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
c.CAConfig.Config["PrivateKeyType"] = tc.keyType
|
||||
c.CAConfig.Config["PrivateKeyBits"] = tc.keyBits
|
||||
|
|
|
@ -359,7 +359,7 @@ func TestLeader_FederationStateAntiEntropyPruning_ACLDeny(t *testing.T) {
|
|||
c.PrimaryDatacenter = "dc1"
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -373,7 +373,7 @@ func TestLeader_FederationStateAntiEntropyPruning_ACLDeny(t *testing.T) {
|
|||
c.PrimaryDatacenter = "dc1"
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir2)
|
||||
|
|
|
@ -29,7 +29,7 @@ func TestLeader_ReplicateIntentions(t *testing.T) {
|
|||
c.Datacenter = "dc1"
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
c.Build = "1.6.0"
|
||||
c.OverrideInitialSerfTags = func(tags map[string]string) {
|
||||
|
|
|
@ -31,7 +31,7 @@ func TestLeader_RegisterMember(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -106,7 +106,7 @@ func TestLeader_FailedMember(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -171,7 +171,7 @@ func TestLeader_LeftMember(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -221,7 +221,7 @@ func TestLeader_ReapMember(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -286,7 +286,7 @@ func TestLeader_CheckServersMeta(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||
c.Bootstrap = true
|
||||
})
|
||||
|
@ -296,7 +296,7 @@ func TestLeader_CheckServersMeta(t *testing.T) {
|
|||
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||
c.Bootstrap = false
|
||||
})
|
||||
|
@ -306,7 +306,7 @@ func TestLeader_CheckServersMeta(t *testing.T) {
|
|||
dir3, s3 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||
c.Bootstrap = false
|
||||
})
|
||||
|
@ -394,7 +394,7 @@ func TestLeader_ReapServer(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||
c.Bootstrap = true
|
||||
})
|
||||
|
@ -404,7 +404,7 @@ func TestLeader_ReapServer(t *testing.T) {
|
|||
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||
c.Bootstrap = false
|
||||
})
|
||||
|
@ -414,7 +414,7 @@ func TestLeader_ReapServer(t *testing.T) {
|
|||
dir3, s3 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||
c.Bootstrap = false
|
||||
})
|
||||
|
@ -473,7 +473,7 @@ func TestLeader_Reconcile_ReapMember(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -526,7 +526,7 @@ func TestLeader_Reconcile(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -875,7 +875,7 @@ func TestLeader_ReapTombstones(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
c.TombstoneTTL = 50 * time.Millisecond
|
||||
c.TombstoneTTLGranularity = 10 * time.Millisecond
|
||||
|
@ -1180,7 +1180,7 @@ func TestLeader_ACL_Initialization(t *testing.T) {
|
|||
c.Datacenter = "dc1"
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = tt.master
|
||||
c.ACLInitialManagementToken = tt.master
|
||||
}
|
||||
dir1, s1 := testServerWithConfig(t, conf)
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1225,7 +1225,7 @@ func TestLeader_ACLUpgrade_IsStickyEvenIfSerfTagsRegress(t *testing.T) {
|
|||
c.Datacenter = "dc1"
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
|
|
|
@ -2,6 +2,7 @@ package consul
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"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
|
||||
// otherwise.
|
||||
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 {
|
||||
// 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 {
|
||||
ok, _ := metadata.IsConsulServer(*m)
|
||||
ok, srv := metadata.IsConsulServer(*m)
|
||||
if !ok {
|
||||
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
|
||||
}
|
||||
|
|
|
@ -138,10 +138,16 @@ func TestMerge_WAN(t *testing.T) {
|
|||
type testcase struct {
|
||||
members []*serf.Member
|
||||
expect string
|
||||
setupFn func(t *testing.T, delegate *wanMergeDelegate)
|
||||
}
|
||||
|
||||
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)
|
||||
if tc.expect == "" {
|
||||
require.NoError(t, err)
|
||||
|
@ -177,7 +183,33 @@ func TestMerge_WAN(t *testing.T) {
|
|||
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`,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ func TestOperator_Autopilot_GetConfiguration_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
c.AutopilotConfig.CleanupDeadServers = false
|
||||
})
|
||||
|
@ -138,7 +138,7 @@ func TestOperator_Autopilot_SetConfiguration_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
c.AutopilotConfig.CleanupDeadServers = false
|
||||
})
|
||||
|
|
|
@ -72,7 +72,7 @@ func TestOperator_RaftGetConfiguration_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -199,7 +199,7 @@ func TestOperator_RaftRemovePeerByAddress_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -305,7 +305,7 @@ func TestOperator_RaftRemovePeerByID_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
c.RaftConfig.ProtocolVersion = 3
|
||||
})
|
||||
|
|
|
@ -373,7 +373,7 @@ func (p *PreparedQuery) Execute(args *structs.PreparedQueryExecuteRequest,
|
|||
if 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
|
||||
}
|
||||
|
||||
|
@ -500,7 +500,7 @@ func (p *PreparedQuery) ExecuteRemote(args *structs.PreparedQueryExecuteRemoteRe
|
|||
if 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
|
||||
}
|
||||
|
||||
|
|
|
@ -200,7 +200,7 @@ func TestPreparedQuery_Apply_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -629,7 +629,7 @@ func TestPreparedQuery_ACLDeny_Catchall_Template(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -831,7 +831,7 @@ func TestPreparedQuery_Get(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1072,7 +1072,7 @@ func TestPreparedQuery_List(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
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
|
||||
// token.
|
||||
query.Query.Token = "le-token"
|
||||
|
@ -1268,7 +1293,7 @@ func TestPreparedQuery_Explain(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -1392,7 +1417,7 @@ func TestPreparedQuery_Execute(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -2124,6 +2149,7 @@ func TestPreparedQuery_Execute(t *testing.T) {
|
|||
require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Execute", &req, &reply))
|
||||
|
||||
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) {
|
||||
|
@ -2246,6 +2272,20 @@ func TestPreparedQuery_Execute(t *testing.T) {
|
|||
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.
|
||||
query.Query.Token = execToken
|
||||
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) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -2669,7 +2709,7 @@ func TestPreparedQuery_Wrapper(t *testing.T) {
|
|||
c.Datacenter = "dc2"
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir2)
|
||||
|
|
|
@ -882,7 +882,7 @@ func TestRPC_LocalTokenStrippedOnForward(t *testing.T) {
|
|||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
defer s1.Shutdown()
|
||||
|
@ -1010,7 +1010,7 @@ func TestRPC_LocalTokenStrippedOnForward_GRPC(t *testing.T) {
|
|||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.RPCConfig.EnableStreaming = true
|
||||
})
|
||||
s1.tokens.UpdateAgentToken("root", tokenStore.TokenSourceConfig)
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/hashicorp/go-version"
|
||||
"go.etcd.io/bbolt"
|
||||
|
||||
"github.com/armon/go-metrics"
|
||||
connlimit "github.com/hashicorp/go-connlimit"
|
||||
|
@ -25,7 +26,7 @@ import (
|
|||
"github.com/hashicorp/go-memdb"
|
||||
"github.com/hashicorp/raft"
|
||||
autopilot "github.com/hashicorp/raft-autopilot"
|
||||
raftboltdb "github.com/hashicorp/raft-boltdb"
|
||||
raftboltdb "github.com/hashicorp/raft-boltdb/v2"
|
||||
"github.com/hashicorp/serf/serf"
|
||||
"golang.org/x/time/rate"
|
||||
"google.golang.org/grpc"
|
||||
|
@ -188,6 +189,12 @@ type Server struct {
|
|||
// serf cluster that spans datacenters
|
||||
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
|
||||
// strong consistency.
|
||||
fsm *fsm.FSM
|
||||
|
@ -265,6 +272,7 @@ type Server struct {
|
|||
// serfWAN is the Serf cluster maintained between DC's
|
||||
// which SHOULD only consist of Consul servers
|
||||
serfWAN *serf.Serf
|
||||
serfWANConfig *serf.Config
|
||||
memberlistTransportWAN wanfed.IngestionAwareTransport
|
||||
gatewayLocator *GatewayLocator
|
||||
|
||||
|
@ -492,7 +500,7 @@ func NewServer(config *Config, flat Deps) (*Server, error) {
|
|||
|
||||
// Initialize the WAN Serf if enabled
|
||||
if config.SerfWANConfig != nil {
|
||||
s.serfWAN, err = s.setupSerf(setupSerfOptions{
|
||||
s.serfWAN, s.serfWANConfig, err = s.setupSerf(setupSerfOptions{
|
||||
Config: config.SerfWANConfig,
|
||||
EventCh: s.eventChWAN,
|
||||
SnapshotPath: serfWANSnapshot,
|
||||
|
@ -547,7 +555,7 @@ func NewServer(config *Config, flat Deps) (*Server, error) {
|
|||
s.Shutdown()
|
||||
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.
|
||||
addrFn := func(s *metadata.Server) (string, error) {
|
||||
|
@ -646,7 +654,7 @@ func newGRPCHandlerFromConfig(deps Deps, config *Config, s *Server) connHandler
|
|||
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) {
|
||||
|
@ -729,13 +737,21 @@ func (s *Server) setupRaft() error {
|
|||
}
|
||||
|
||||
// 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 {
|
||||
return err
|
||||
}
|
||||
s.raftStore = 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.
|
||||
cacheStore, err := raft.NewLogCache(raftLogCacheSize, store)
|
||||
if err != nil {
|
||||
|
@ -1115,6 +1131,11 @@ func (s *Server) JoinWAN(addrs []string) (int, error) {
|
|||
if s.serfWAN == nil {
|
||||
return 0, ErrWANFederationDisabled
|
||||
}
|
||||
|
||||
if err := s.enterpriseValidateJoinWAN(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return s.serfWAN.Join(addrs, true)
|
||||
}
|
||||
|
||||
|
|
|
@ -19,6 +19,10 @@ import (
|
|||
|
||||
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
|
||||
// should be another node inside the DC listening on the Serf LAN address
|
||||
func (s *Server) JoinLAN(addrs []string, entMeta *structs.EnterpriseMeta) (int, error) {
|
||||
|
|
|
@ -48,12 +48,18 @@ type setupSerfOptions struct {
|
|||
}
|
||||
|
||||
// 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)
|
||||
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) {
|
||||
|
@ -152,7 +158,9 @@ func (s *Server) setupSerfConfig(opts setupSerfOptions) (*serf.Config, error) {
|
|||
conf.ProtocolVersion = protocolVersionMap[s.config.ProtocolVersion]
|
||||
conf.RejoinAfterLeave = s.config.RejoinAfterLeave
|
||||
if opts.WAN {
|
||||
conf.Merge = &wanMergeDelegate{}
|
||||
conf.Merge = &wanMergeDelegate{
|
||||
localDatacenter: s.config.Datacenter,
|
||||
}
|
||||
} else {
|
||||
conf.Merge = &lanMergeDelegate{
|
||||
dc: s.config.Datacenter,
|
||||
|
|
|
@ -74,7 +74,7 @@ func testServerACLConfig(cb func(*Config)) func(*Config) {
|
|||
return func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = TestDefaultMasterToken
|
||||
c.ACLInitialManagementToken = TestDefaultMasterToken
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
|
||||
if cb != nil {
|
||||
|
|
|
@ -151,7 +151,7 @@ func (s *Session) Apply(args *structs.SessionRequest, reply *string) error {
|
|||
|
||||
if args.Op == structs.SessionCreate && args.Session.TTL != "" {
|
||||
// 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 {
|
||||
// If we destroyed a session, it might potentially have a TTL,
|
||||
// and we need to clear the timer
|
||||
|
@ -308,7 +308,7 @@ func (s *Session) Renew(args *structs.SessionSpecificRequest,
|
|||
|
||||
// Reset the session TTL timer.
|
||||
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)
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -157,7 +157,7 @@ func TestSession_Apply_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -382,7 +382,7 @@ func TestSession_Get_List_NodeSessions_ACLFilter(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -731,7 +731,7 @@ func TestSession_Renew_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
|
|
@ -47,13 +47,12 @@ func (s *Server) initializeSessionTimers() error {
|
|||
// Scan all sessions and reset their timer
|
||||
state := s.fsm.State()
|
||||
|
||||
// TODO(partitions): track all session timers in all partitions
|
||||
_, sessions, err := state.SessionList(nil, structs.WildcardEnterpriseMetaInDefaultPartition())
|
||||
_, sessions, err := state.SessionListAll(nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, session := range sessions {
|
||||
if err := s.resetSessionTimer(session.ID, session); err != nil {
|
||||
if err := s.resetSessionTimer(session); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -63,20 +62,7 @@ func (s *Server) initializeSessionTimers() error {
|
|||
// resetSessionTimer is used to renew the TTL of a session.
|
||||
// This can be used for new sessions and existing ones. A session
|
||||
// will be faulted in if not given.
|
||||
func (s *Server) resetSessionTimer(id string, 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
|
||||
}
|
||||
|
||||
func (s *Server) resetSessionTimer(session *structs.Session) error {
|
||||
// Bail if the session has no TTL, fast-path some common inputs
|
||||
switch session.TTL {
|
||||
case "", "0", "0s", "0m", "0h":
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||
"github.com/hashicorp/consul/testrpc"
|
||||
"github.com/hashicorp/go-uuid"
|
||||
"github.com/hashicorp/net-rpc-msgpackrpc"
|
||||
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
|
||||
)
|
||||
|
||||
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) {
|
||||
if testing.Short() {
|
||||
t.Skip("too slow for testing.Short")
|
||||
|
@ -130,7 +86,7 @@ func TestResetSessionTimer_NoTTL(t *testing.T) {
|
|||
}
|
||||
|
||||
// Reset the session timer
|
||||
err := s1.resetSessionTimer(session.ID, session)
|
||||
err := s1.resetSessionTimer(session)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
@ -155,7 +111,7 @@ func TestResetSessionTimer_InvalidTTL(t *testing.T) {
|
|||
}
|
||||
|
||||
// Reset the session timer
|
||||
err := s1.resetSessionTimer(session.ID, session)
|
||||
err := s1.resetSessionTimer(session)
|
||||
if err == nil || !strings.Contains(err.Error(), "Invalid Session TTL") {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
|
|
@ -271,7 +271,7 @@ func TestSnapshot_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
|
|
@ -741,6 +741,9 @@ func ensureServiceTxn(tx WriteTxn, idx uint64, node string, preserveIndexes bool
|
|||
if err = checkGatewayWildcardsAndUpdate(tx, idx, svc); err != nil {
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
func (s *Store) ServiceList(ws memdb.WatchSet,
|
||||
include func(svc *structs.ServiceNode) bool, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
|
||||
func (s *Store) ServiceList(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
|
||||
tx := s.db.Txn(false)
|
||||
defer tx.Abort()
|
||||
|
||||
return serviceListTxn(tx, ws, include, entMeta)
|
||||
return serviceListTxn(tx, ws, entMeta)
|
||||
}
|
||||
|
||||
func serviceListTxn(tx ReadTxn, ws memdb.WatchSet,
|
||||
include func(svc *structs.ServiceNode) bool, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
|
||||
func serviceListTxn(tx ReadTxn, ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
|
||||
idx := catalogServicesMaxIndex(tx, 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{})
|
||||
for service := services.Next(); service != nil; service = services.Next() {
|
||||
svc := service.(*structs.ServiceNode)
|
||||
// TODO (freddy) This is a hack to exclude certain kinds.
|
||||
// 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{}{}
|
||||
}
|
||||
unique[svc.CompoundServiceName()] = struct{}{}
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
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,
|
||||
// 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.
|
||||
|
@ -3862,3 +3886,44 @@ func truncateGatewayServiceTopologyMappings(tx WriteTxn, idx uint64, gateway str
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ package state
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
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 {
|
||||
switch kind {
|
||||
case structs.ServiceKindTypical:
|
||||
// needs a special case here
|
||||
return "service_kind.typical"
|
||||
default:
|
||||
return "service_kind." + string(kind)
|
||||
}
|
||||
return "service_kind." + kind.Normalized()
|
||||
}
|
||||
|
||||
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) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ const (
|
|||
tableMeshTopology = "mesh-topology"
|
||||
tableServiceVirtualIPs = "service-virtual-ips"
|
||||
tableFreeVirtualIPs = "free-virtual-ips"
|
||||
tableKindServiceNames = "kind-service-names"
|
||||
|
||||
indexID = "id"
|
||||
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()
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
t.Helper()
|
||||
if !t.Run(name, fn) {
|
||||
|
|
|
@ -395,7 +395,7 @@ func validateProposedConfigEntryInGraph(
|
|||
}
|
||||
case structs.ServiceIntentions:
|
||||
case structs.MeshConfig:
|
||||
case structs.PartitionExports:
|
||||
case structs.ExportedServices:
|
||||
default:
|
||||
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)
|
||||
|
||||
// Grab the proxy defaults if they exist.
|
||||
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.
|
||||
// At every step we'll need service and proxy defaults.
|
||||
todoDefaults[sid] = struct{}{}
|
||||
|
||||
var maxIdx uint64
|
||||
|
||||
// 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 {
|
||||
return 0, nil, err
|
||||
} else if router != nil {
|
||||
res.Routers[sid] = router
|
||||
}
|
||||
if idx > maxIdx {
|
||||
maxIdx = idx
|
||||
}
|
||||
|
||||
if router != nil {
|
||||
for _, svc := range router.ListRelatedServices() {
|
||||
|
@ -922,10 +919,13 @@ func readDiscoveryChainConfigEntriesTxn(
|
|||
// Yes, even for splitters.
|
||||
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 {
|
||||
return 0, nil, err
|
||||
}
|
||||
if idx > maxIdx {
|
||||
maxIdx = idx
|
||||
}
|
||||
|
||||
if splitter == nil {
|
||||
res.Splitters[splitID] = nil
|
||||
|
@ -959,10 +959,13 @@ func readDiscoveryChainConfigEntriesTxn(
|
|||
// And resolvers, too.
|
||||
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 {
|
||||
return 0, nil, err
|
||||
}
|
||||
if idx > maxIdx {
|
||||
maxIdx = idx
|
||||
}
|
||||
|
||||
if resolver == nil {
|
||||
res.Resolvers[resolverID] = nil
|
||||
|
@ -987,16 +990,31 @@ func readDiscoveryChainConfigEntriesTxn(
|
|||
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 {
|
||||
return 0, nil, err
|
||||
}
|
||||
if idx > maxIdx {
|
||||
maxIdx = idx
|
||||
}
|
||||
|
||||
if entry == nil {
|
||||
res.Services[svcID] = nil
|
||||
continue
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
@ -1347,6 +1347,13 @@ func entrySetToKindNames(entrySet *structs.DiscoveryChainConfigEntries) []Config
|
|||
&entry.EnterpriseMeta,
|
||||
))
|
||||
}
|
||||
for _, entry := range entrySet.ProxyDefaults {
|
||||
out = append(out, NewConfigEntryKindName(
|
||||
entry.Kind,
|
||||
entry.Name,
|
||||
&entry.EnterpriseMeta,
|
||||
))
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
|
|
|
@ -995,36 +995,29 @@ func (s *Store) intentionTopologyTxn(tx ReadTxn, ws memdb.WatchSet,
|
|||
maxIdx = index
|
||||
}
|
||||
|
||||
// Check for a wildcard intention (* -> *) since it overrides the default decision from ACLs
|
||||
if len(intentions) > 0 {
|
||||
// Intentions with wildcard source and destination have the lowest precedence, so they are last in the list
|
||||
ixn := intentions[len(intentions)-1]
|
||||
|
||||
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())
|
||||
// TODO(tproxy): One remaining improvement 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 narrow serviceNamesOfKindTxn to services represented by proxies? (ingress, sidecar-proxy, terminating)
|
||||
index, services, err := serviceNamesOfKindTxn(tx, ws, structs.ServiceKindTypical)
|
||||
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 {
|
||||
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
|
||||
// if upstream candidates are covered by intentions that have the target service as a source.
|
||||
// The reverse is true for downstreams.
|
||||
|
@ -1032,11 +1025,13 @@ func (s *Store) intentionTopologyTxn(tx ReadTxn, ws memdb.WatchSet,
|
|||
if downstreams {
|
||||
decisionMatchType = structs.IntentionMatchSource
|
||||
}
|
||||
result := make([]ServiceWithDecision, 0, len(allServices))
|
||||
for _, candidate := range allServices {
|
||||
result := make([]ServiceWithDecision, 0, len(services))
|
||||
for _, svc := range services {
|
||||
candidate := svc.Service
|
||||
if candidate.Name == structs.ConsulServiceName {
|
||||
continue
|
||||
}
|
||||
|
||||
opts := IntentionDecisionOpts{
|
||||
Target: candidate.Name,
|
||||
Namespace: candidate.NamespaceOrDefault(),
|
||||
|
|
|
@ -40,6 +40,7 @@ func newDBSchema() *memdb.DBSchema {
|
|||
tombstonesTableSchema,
|
||||
usageTableSchema,
|
||||
freeVirtualIPTableSchema,
|
||||
kindServiceNameTableSchema,
|
||||
)
|
||||
withEnterpriseSchema(db)
|
||||
return db
|
||||
|
|
|
@ -50,6 +50,7 @@ func TestNewDBSchema_Indexers(t *testing.T) {
|
|||
tableMeshTopology: testIndexerTableMeshTopology,
|
||||
tableGatewayServices: testIndexerTableGatewayServices,
|
||||
tableServiceVirtualIPs: testIndexerTableServiceVirtualIPs,
|
||||
tableKindServiceNames: testIndexerTableKindServiceNames,
|
||||
// KV
|
||||
tableKVs: testIndexerTableKVs,
|
||||
tableTombstones: testIndexerTableTombstones,
|
||||
|
|
|
@ -187,3 +187,7 @@ func (s *Store) SessionList(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta)
|
|||
func maxIndexTxnSessions(tx *memdb.Txn, _ *structs.EnterpriseMeta) uint64 {
|
||||
return maxIndexTxn(tx, tableSessions)
|
||||
}
|
||||
|
||||
func (s *Store) SessionListAll(ws memdb.WatchSet) (uint64, structs.Sessions, error) {
|
||||
return s.SessionList(ws, nil)
|
||||
}
|
||||
|
|
|
@ -319,7 +319,7 @@ func TestTxn_Apply_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
@ -838,7 +838,7 @@ func TestTxn_Read_ACLDeny(t *testing.T) {
|
|||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||
c.PrimaryDatacenter = "dc1"
|
||||
c.ACLsEnabled = true
|
||||
c.ACLMasterToken = "root"
|
||||
c.ACLInitialManagementToken = "root"
|
||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||
})
|
||||
defer os.RemoveAll(dir1)
|
||||
|
|
|
@ -178,12 +178,12 @@ func TestUsageReporter_emitNodeUsage_OSS(t *testing.T) {
|
|||
{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",
|
||||
Value: 0,
|
||||
Labels: []metrics.Label{
|
||||
{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"},
|
||||
},
|
||||
},
|
||||
"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",
|
||||
Value: 0,
|
||||
Labels: []metrics.Label{
|
||||
{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"},
|
||||
},
|
||||
},
|
||||
"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",
|
||||
Value: 0,
|
||||
Labels: []metrics.Label{
|
||||
{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"},
|
||||
},
|
||||
},
|
||||
"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",
|
||||
Value: 0,
|
||||
Labels: []metrics.Label{
|
||||
{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"},
|
||||
},
|
||||
},
|
||||
"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",
|
||||
Value: 0,
|
||||
Labels: []metrics.Label{
|
||||
{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"},
|
||||
},
|
||||
},
|
||||
"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",
|
||||
Value: 0,
|
||||
Labels: []metrics.Label{
|
||||
{Name: "datacenter", Value: "dc1"},
|
||||
{Name: "kind", Value: "partition-exports"},
|
||||
{Name: "kind", Value: "exported-services"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -6224,11 +6224,18 @@ func TestDNS_ServiceLookup_FilterACL(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run("ACLToken == "+tt.token, func(t *testing.T) {
|
||||
a := NewTestAgent(t, `
|
||||
acl_token = "`+tt.token+`"
|
||||
acl_master_token = "root"
|
||||
acl_datacenter = "dc1"
|
||||
acl_down_policy = "deny"
|
||||
acl_default_policy = "deny"
|
||||
primary_datacenter = "dc1"
|
||||
|
||||
acl {
|
||||
enabled = true
|
||||
default_policy = "deny"
|
||||
down_policy = "deny"
|
||||
|
||||
tokens {
|
||||
initial_management = "root"
|
||||
default = "`+tt.token+`"
|
||||
}
|
||||
}
|
||||
`)
|
||||
defer a.Shutdown()
|
||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||
|
|
|
@ -20,6 +20,7 @@ import (
|
|||
"github.com/hashicorp/consul/ipaddr"
|
||||
"github.com/hashicorp/consul/sdk/freeport"
|
||||
"github.com/hashicorp/consul/tlsutil"
|
||||
"github.com/hashicorp/consul/types"
|
||||
)
|
||||
|
||||
// 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))
|
||||
|
||||
builder := resolver.NewServerResolverBuilder(newConfig(t))
|
||||
builder.AddServer(&metadata.Server{
|
||||
builder.AddServer(types.AreaWAN, &metadata.Server{
|
||||
Name: "server-1",
|
||||
ID: "ID1",
|
||||
Datacenter: "dc1",
|
||||
|
@ -84,14 +85,14 @@ func TestNewDialer_WithALPNWrapper(t *testing.T) {
|
|||
}()
|
||||
|
||||
builder := resolver.NewServerResolverBuilder(newConfig(t))
|
||||
builder.AddServer(&metadata.Server{
|
||||
builder.AddServer(types.AreaWAN, &metadata.Server{
|
||||
Name: "server-1",
|
||||
ID: "ID1",
|
||||
Datacenter: "dc1",
|
||||
Addr: lis1.Addr(),
|
||||
UseTLS: true,
|
||||
})
|
||||
builder.AddServer(&metadata.Server{
|
||||
builder.AddServer(types.AreaWAN, &metadata.Server{
|
||||
Name: "server-2",
|
||||
ID: "ID2",
|
||||
Datacenter: "dc2",
|
||||
|
@ -150,10 +151,10 @@ func TestNewDialer_IntegrationWithTLSEnabledHandler(t *testing.T) {
|
|||
}, hclog.New(nil))
|
||||
require.NoError(t, err)
|
||||
|
||||
srv := newTestServer(t, "server-1", "dc1", tlsConf)
|
||||
srv := newSimpleTestServer(t, "server-1", "dc1", tlsConf)
|
||||
|
||||
md := srv.Metadata()
|
||||
res.AddServer(md)
|
||||
res.AddServer(types.AreaWAN, md)
|
||||
t.Cleanup(srv.shutdown)
|
||||
|
||||
pool := NewClientConnPool(ClientConnPoolConfig{
|
||||
|
@ -198,7 +199,7 @@ func TestNewDialer_IntegrationWithTLSEnabledHandler_viaMeshGateway(t *testing.T)
|
|||
}, hclog.New(nil))
|
||||
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
|
||||
var p tcpproxy.Proxy
|
||||
|
@ -211,7 +212,7 @@ func TestNewDialer_IntegrationWithTLSEnabledHandler_viaMeshGateway(t *testing.T)
|
|||
}()
|
||||
|
||||
md := srv.Metadata()
|
||||
res.AddServer(md)
|
||||
res.AddServer(types.AreaWAN, md)
|
||||
t.Cleanup(srv.shutdown)
|
||||
|
||||
clientTLSConf, err := tlsutil.NewConfigurator(tlsutil.Config{
|
||||
|
@ -265,8 +266,8 @@ func TestClientConnPool_IntegrationWithGRPCResolver_Failover(t *testing.T) {
|
|||
|
||||
for i := 0; i < count; i++ {
|
||||
name := fmt.Sprintf("server-%d", i)
|
||||
srv := newTestServer(t, name, "dc1", nil)
|
||||
res.AddServer(srv.Metadata())
|
||||
srv := newSimpleTestServer(t, name, "dc1", nil)
|
||||
res.AddServer(types.AreaWAN, srv.Metadata())
|
||||
t.Cleanup(srv.shutdown)
|
||||
}
|
||||
|
||||
|
@ -280,7 +281,7 @@ func TestClientConnPool_IntegrationWithGRPCResolver_Failover(t *testing.T) {
|
|||
first, err := client.Something(ctx, &testservice.Req{})
|
||||
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{})
|
||||
require.NoError(t, err)
|
||||
|
@ -301,8 +302,8 @@ func TestClientConnPool_ForwardToLeader_Failover(t *testing.T) {
|
|||
var servers []testServer
|
||||
for i := 0; i < count; i++ {
|
||||
name := fmt.Sprintf("server-%d", i)
|
||||
srv := newTestServer(t, name, "dc1", nil)
|
||||
res.AddServer(srv.Metadata())
|
||||
srv := newSimpleTestServer(t, name, "dc1", nil)
|
||||
res.AddServer(types.AreaWAN, srv.Metadata())
|
||||
servers = append(servers, srv)
|
||||
t.Cleanup(srv.shutdown)
|
||||
}
|
||||
|
@ -351,8 +352,8 @@ func TestClientConnPool_IntegrationWithGRPCResolver_Rebalance(t *testing.T) {
|
|||
|
||||
for i := 0; i < count; i++ {
|
||||
name := fmt.Sprintf("server-%d", i)
|
||||
srv := newTestServer(t, name, "dc1", nil)
|
||||
res.AddServer(srv.Metadata())
|
||||
srv := newSimpleTestServer(t, name, "dc1", nil)
|
||||
res.AddServer(types.AreaWAN, srv.Metadata())
|
||||
t.Cleanup(srv.shutdown)
|
||||
}
|
||||
|
||||
|
@ -405,8 +406,8 @@ func TestClientConnPool_IntegrationWithGRPCResolver_MultiDC(t *testing.T) {
|
|||
|
||||
for _, dc := range dcs {
|
||||
name := "server-0-" + dc
|
||||
srv := newTestServer(t, name, dc, nil)
|
||||
res.AddServer(srv.Metadata())
|
||||
srv := newSimpleTestServer(t, name, dc, nil)
|
||||
res.AddServer(types.AreaWAN, srv.Metadata())
|
||||
t.Cleanup(srv.shutdown)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,29 +9,73 @@ import (
|
|||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"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).
|
||||
// The register function will be called with the grpc.Server to register
|
||||
// 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()
|
||||
|
||||
// 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(
|
||||
recoveryOpts := PanicHandlerMiddlewareOpts(logger)
|
||||
|
||||
opts := []grpc.ServerOption{
|
||||
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{
|
||||
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)
|
||||
|
||||
lis := &chanListener{addr: addr, conns: make(chan net.Conn), done: make(chan struct{})}
|
||||
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
|
||||
// agent.Component interface for managing the lifecycle of the grpc.Server.
|
||||
type Handler struct {
|
||||
|
|
|
@ -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`)
|
||||
}
|
|
@ -10,6 +10,7 @@ import (
|
|||
"google.golang.org/grpc/resolver"
|
||||
|
||||
"github.com/hashicorp/consul/agent/metadata"
|
||||
"github.com/hashicorp/consul/types"
|
||||
)
|
||||
|
||||
// ServerResolverBuilder tracks the current server list and keeps any
|
||||
|
@ -18,9 +19,9 @@ type ServerResolverBuilder struct {
|
|||
cfg Config
|
||||
// leaderResolver is used to track the address of the leader in the local DC.
|
||||
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.
|
||||
servers map[string]*metadata.Server
|
||||
servers map[types.AreaID]map[string]*metadata.Server
|
||||
// resolvers is an index of connections to the serverResolver which manages
|
||||
// addresses of servers for that connection.
|
||||
resolvers map[resolver.ClientConn]*serverResolver
|
||||
|
@ -37,7 +38,7 @@ type Config struct {
|
|||
func NewServerResolverBuilder(cfg Config) *ServerResolverBuilder {
|
||||
return &ServerResolverBuilder{
|
||||
cfg: cfg,
|
||||
servers: make(map[string]*metadata.Server),
|
||||
servers: make(map[types.AreaID]map[string]*metadata.Server),
|
||||
resolvers: make(map[resolver.ClientConn]*serverResolver),
|
||||
}
|
||||
}
|
||||
|
@ -72,9 +73,11 @@ func (s *ServerResolverBuilder) ServerForGlobalAddr(globalAddr string) (*metadat
|
|||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
for _, server := range s.servers {
|
||||
if DCPrefix(server.Datacenter, server.Addr.String()) == globalAddr {
|
||||
return server, nil
|
||||
for _, areaServers := range s.servers {
|
||||
for _, server := range areaServers {
|
||||
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)
|
||||
|
@ -138,11 +141,17 @@ func (s *ServerResolverBuilder) Authority() string {
|
|||
}
|
||||
|
||||
// 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()
|
||||
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)
|
||||
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.
|
||||
func (s *ServerResolverBuilder) RemoveServer(server *metadata.Server) {
|
||||
func (s *ServerResolverBuilder) RemoveServer(areaID types.AreaID, server *metadata.Server) {
|
||||
s.lock.Lock()
|
||||
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)
|
||||
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.
|
||||
// This method requires that lock is held for reads.
|
||||
func (s *ServerResolverBuilder) getDCAddrs(dc string) []resolver.Address {
|
||||
var addrs []resolver.Address
|
||||
for _, server := range s.servers {
|
||||
if server.Datacenter != dc {
|
||||
continue
|
||||
}
|
||||
var (
|
||||
addrs []resolver.Address
|
||||
keptServerIDs = make(map[string]struct{})
|
||||
)
|
||||
for _, areaServers := range s.servers {
|
||||
for _, server := range areaServers {
|
||||
if server.Datacenter != dc {
|
||||
continue
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
// Servers may be part of multiple areas, so only include each one once.
|
||||
if _, ok := keptServerIDs[server.ID]; ok {
|
||||
continue
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import (
|
|||
"github.com/hashicorp/consul/agent/metadata"
|
||||
"github.com/hashicorp/consul/agent/pool"
|
||||
"github.com/hashicorp/consul/tlsutil"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
)
|
||||
|
||||
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 {
|
||||
addr := &net.IPAddr{IP: net.ParseIP("127.0.0.1")}
|
||||
handler := NewHandler(addr, func(server *grpc.Server) {
|
||||
func newSimpleTestServer(t *testing.T, name, dc string, tlsConf *tlsutil.Configurator) testServer {
|
||||
return newTestServer(t, hclog.Default(), name, dc, tlsConf, func(server *grpc.Server) {
|
||||
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")
|
||||
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
|
||||
}
|
||||
|
||||
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.
|
||||
// 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.
|
||||
|
|
|
@ -15,6 +15,7 @@ import (
|
|||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/hashicorp/consul/agent/grpc/internal/testservice"
|
||||
"github.com/hashicorp/go-hclog"
|
||||
)
|
||||
|
||||
func noopRegister(*grpc.Server) {}
|
||||
|
@ -23,7 +24,7 @@ func TestHandler_EmitsStats(t *testing.T) {
|
|||
sink, reset := patchGlobalMetrics(t)
|
||||
|
||||
addr := &net.IPAddr{IP: net.ParseIP("127.0.0.1")}
|
||||
handler := NewHandler(addr, noopRegister)
|
||||
handler := NewHandler(hclog.Default(), addr, noopRegister)
|
||||
reset()
|
||||
|
||||
testservice.RegisterSimpleServer(handler.srv, &simple{})
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue