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
|
- package-ecosystem: gomod
|
||||||
open-pull-requests-limit: 5
|
open-pull-requests-limit: 5
|
||||||
directory: "/"
|
directory: "/"
|
||||||
|
labels:
|
||||||
|
- "go"
|
||||||
|
- "dependencies"
|
||||||
|
- "pr/no-changelog"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
- package-ecosystem: gomod
|
- package-ecosystem: gomod
|
||||||
open-pull-requests-limit: 5
|
open-pull-requests-limit: 5
|
||||||
directory: "/api"
|
directory: "/api"
|
||||||
|
labels:
|
||||||
|
- "go"
|
||||||
|
- "dependencies"
|
||||||
|
- "pr/no-changelog"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
- package-ecosystem: gomod
|
- package-ecosystem: gomod
|
||||||
open-pull-requests-limit: 5
|
open-pull-requests-limit: 5
|
||||||
directory: "/sdk"
|
directory: "/sdk"
|
||||||
|
labels:
|
||||||
|
- "go"
|
||||||
|
- "dependencies"
|
||||||
|
- "pr/no-changelog"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
open-pull-requests-limit: 5
|
open-pull-requests-limit: 5
|
||||||
directory: "/ui"
|
directory: "/ui"
|
||||||
|
labels:
|
||||||
|
- "javascript"
|
||||||
|
- "dependencies"
|
||||||
|
- "pr/no-changelog"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
- package-ecosystem: npm
|
- package-ecosystem: npm
|
||||||
open-pull-requests-limit: 5
|
open-pull-requests-limit: 5
|
||||||
directory: "/website"
|
directory: "/website"
|
||||||
|
labels:
|
||||||
|
- "javascript"
|
||||||
|
- "dependencies"
|
||||||
|
- "type/docs-cherrypick"
|
||||||
|
- "pr/no-changelog"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
open-pull-requests-limit: 5
|
open-pull-requests-limit: 5
|
||||||
directory: /
|
directory: /
|
||||||
|
labels:
|
||||||
|
- "github_actions"
|
||||||
|
- "dependencies"
|
||||||
|
- "pr/no-changelog"
|
||||||
schedule:
|
schedule:
|
||||||
interval: daily
|
interval: daily
|
||||||
|
|
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)
|
## 1.11.0-beta2 (November 02, 2021)
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,10 @@ type Config struct {
|
||||||
|
|
||||||
type ExportFetcher interface {
|
type ExportFetcher interface {
|
||||||
// ExportsForPartition returns the config entry defining exports for a partition
|
// ExportsForPartition returns the config entry defining exports for a partition
|
||||||
ExportsForPartition(partition string) PartitionExports
|
ExportsForPartition(partition string) ExportedServices
|
||||||
}
|
}
|
||||||
|
|
||||||
type PartitionExports struct {
|
type ExportedServices struct {
|
||||||
Data map[string]map[string][]string
|
Data map[string]map[string][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -91,9 +91,14 @@ func TestACL_Bootstrap(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
a := NewTestAgent(t, TestACLConfig()+`
|
a := NewTestAgent(t, `
|
||||||
acl_master_token = ""
|
primary_datacenter = "dc1"
|
||||||
`)
|
|
||||||
|
acl {
|
||||||
|
enabled = true
|
||||||
|
default_policy = "deny"
|
||||||
|
}
|
||||||
|
`)
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
@ -881,7 +886,7 @@ func TestACL_HTTP(t *testing.T) {
|
||||||
require.True(t, ok)
|
require.True(t, ok)
|
||||||
require.Len(t, tokens, 1)
|
require.Len(t, tokens, 1)
|
||||||
token := tokens[0]
|
token := tokens[0]
|
||||||
require.Equal(t, "Master Token", token.Description)
|
require.Equal(t, "Initial Management Token", token.Description)
|
||||||
require.Len(t, token.Policies, 1)
|
require.Len(t, token.Policies, 1)
|
||||||
require.Equal(t, structs.ACLPolicyGlobalManagementID, token.Policies[0].ID)
|
require.Equal(t, structs.ACLPolicyGlobalManagementID, token.Policies[0].ID)
|
||||||
})
|
})
|
||||||
|
@ -1689,7 +1694,7 @@ func TestACLEndpoint_LoginLogout_jwt(t *testing.T) {
|
||||||
for name, tc := range cases {
|
for name, tc := range cases {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
method, err := upsertTestCustomizedAuthMethod(a.RPC, TestDefaultMasterToken, "dc1", func(method *structs.ACLAuthMethod) {
|
method, err := upsertTestCustomizedAuthMethod(a.RPC, TestDefaultInitialManagementToken, "dc1", func(method *structs.ACLAuthMethod) {
|
||||||
method.Type = "jwt"
|
method.Type = "jwt"
|
||||||
method.Config = map[string]interface{}{
|
method.Config = map[string]interface{}{
|
||||||
"JWTSupportedAlgs": []string{"ES256"},
|
"JWTSupportedAlgs": []string{"ES256"},
|
||||||
|
@ -1758,7 +1763,7 @@ func TestACLEndpoint_LoginLogout_jwt(t *testing.T) {
|
||||||
testutil.RequireErrorContains(t, err, "Permission denied")
|
testutil.RequireErrorContains(t, err, "Permission denied")
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err = upsertTestCustomizedBindingRule(a.RPC, TestDefaultMasterToken, "dc1", func(rule *structs.ACLBindingRule) {
|
_, err = upsertTestCustomizedBindingRule(a.RPC, TestDefaultInitialManagementToken, "dc1", func(rule *structs.ACLBindingRule) {
|
||||||
rule.AuthMethod = method.Name
|
rule.AuthMethod = method.Name
|
||||||
rule.BindType = structs.BindingRuleBindTypeService
|
rule.BindType = structs.BindingRuleBindTypeService
|
||||||
rule.BindName = "test--${value.name}--${value.primary_org}"
|
rule.BindName = "test--${value.name}--${value.primary_org}"
|
||||||
|
@ -1798,7 +1803,7 @@ func TestACLEndpoint_LoginLogout_jwt(t *testing.T) {
|
||||||
|
|
||||||
// verify the token was deleted
|
// verify the token was deleted
|
||||||
req, _ = http.NewRequest("GET", "/v1/acl/token/"+token.AccessorID, nil)
|
req, _ = http.NewRequest("GET", "/v1/acl/token/"+token.AccessorID, nil)
|
||||||
req.Header.Add("X-Consul-Token", TestDefaultMasterToken)
|
req.Header.Add("X-Consul-Token", TestDefaultInitialManagementToken)
|
||||||
resp = httptest.NewRecorder()
|
resp = httptest.NewRecorder()
|
||||||
|
|
||||||
// make the request
|
// make the request
|
||||||
|
@ -1819,7 +1824,7 @@ func TestACL_Authorize(t *testing.T) {
|
||||||
a1 := NewTestAgent(t, TestACLConfigWithParams(nil))
|
a1 := NewTestAgent(t, TestACLConfigWithParams(nil))
|
||||||
defer a1.Shutdown()
|
defer a1.Shutdown()
|
||||||
|
|
||||||
testrpc.WaitForTestAgent(t, a1.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken))
|
testrpc.WaitForTestAgent(t, a1.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))
|
||||||
|
|
||||||
policyReq := structs.ACLPolicySetRequest{
|
policyReq := structs.ACLPolicySetRequest{
|
||||||
Policy: structs.ACLPolicy{
|
Policy: structs.ACLPolicy{
|
||||||
|
@ -1827,7 +1832,7 @@ func TestACL_Authorize(t *testing.T) {
|
||||||
Rules: `acl = "read" operator = "write" service_prefix "" { policy = "read"} node_prefix "" { policy= "write" } key_prefix "/foo" { policy = "write" } `,
|
Rules: `acl = "read" operator = "write" service_prefix "" { policy = "read"} node_prefix "" { policy= "write" } key_prefix "/foo" { policy = "write" } `,
|
||||||
},
|
},
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken},
|
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
||||||
}
|
}
|
||||||
var policy structs.ACLPolicy
|
var policy structs.ACLPolicy
|
||||||
require.NoError(t, a1.RPC("ACL.PolicySet", &policyReq, &policy))
|
require.NoError(t, a1.RPC("ACL.PolicySet", &policyReq, &policy))
|
||||||
|
@ -1841,15 +1846,15 @@ func TestACL_Authorize(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken},
|
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
||||||
}
|
}
|
||||||
|
|
||||||
var token structs.ACLToken
|
var token structs.ACLToken
|
||||||
require.NoError(t, a1.RPC("ACL.TokenSet", &tokenReq, &token))
|
require.NoError(t, a1.RPC("ACL.TokenSet", &tokenReq, &token))
|
||||||
|
|
||||||
// secondary also needs to setup a replication token to pull tokens and policies
|
// secondary also needs to setup a replication token to pull tokens and policies
|
||||||
secondaryParams := DefaulTestACLConfigParams()
|
secondaryParams := DefaultTestACLConfigParams()
|
||||||
secondaryParams.ReplicationToken = secondaryParams.MasterToken
|
secondaryParams.ReplicationToken = secondaryParams.InitialManagementToken
|
||||||
secondaryParams.EnableTokenReplication = true
|
secondaryParams.EnableTokenReplication = true
|
||||||
|
|
||||||
a2 := NewTestAgent(t, `datacenter = "dc2" `+TestACLConfigWithParams(secondaryParams))
|
a2 := NewTestAgent(t, `datacenter = "dc2" `+TestACLConfigWithParams(secondaryParams))
|
||||||
|
@ -1859,7 +1864,7 @@ func TestACL_Authorize(t *testing.T) {
|
||||||
_, err := a2.JoinWAN([]string{addr})
|
_, err := a2.JoinWAN([]string{addr})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
testrpc.WaitForTestAgent(t, a2.RPC, "dc2", testrpc.WithToken(TestDefaultMasterToken))
|
testrpc.WaitForTestAgent(t, a2.RPC, "dc2", testrpc.WithToken(TestDefaultInitialManagementToken))
|
||||||
// this actually ensures a few things. First the dcs got connect okay, secondly that the policy we
|
// this actually ensures a few things. First the dcs got connect okay, secondly that the policy we
|
||||||
// are about ready to use in our local token creation exists in the secondary DC
|
// are about ready to use in our local token creation exists in the secondary DC
|
||||||
testrpc.WaitForACLReplication(t, a2.RPC, "dc2", structs.ACLReplicateTokens, policy.CreateIndex, 1, 0)
|
testrpc.WaitForACLReplication(t, a2.RPC, "dc2", structs.ACLReplicateTokens, policy.CreateIndex, 1, 0)
|
||||||
|
@ -1874,7 +1879,7 @@ func TestACL_Authorize(t *testing.T) {
|
||||||
Local: true,
|
Local: true,
|
||||||
},
|
},
|
||||||
Datacenter: "dc2",
|
Datacenter: "dc2",
|
||||||
WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken},
|
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
||||||
}
|
}
|
||||||
|
|
||||||
var localToken structs.ACLToken
|
var localToken structs.ACLToken
|
||||||
|
@ -2004,7 +2009,7 @@ func TestACL_Authorize(t *testing.T) {
|
||||||
for _, dc := range []string{"dc1", "dc2"} {
|
for _, dc := range []string{"dc1", "dc2"} {
|
||||||
t.Run(dc, func(t *testing.T) {
|
t.Run(dc, func(t *testing.T) {
|
||||||
req, _ := http.NewRequest("POST", "/v1/internal/acl/authorize?dc="+dc, jsonBody(request))
|
req, _ := http.NewRequest("POST", "/v1/internal/acl/authorize?dc="+dc, jsonBody(request))
|
||||||
req.Header.Add("X-Consul-Token", TestDefaultMasterToken)
|
req.Header.Add("X-Consul-Token", TestDefaultInitialManagementToken)
|
||||||
recorder := httptest.NewRecorder()
|
recorder := httptest.NewRecorder()
|
||||||
raw, err := a1.srv.ACLAuthorize(recorder, req)
|
raw, err := a1.srv.ACLAuthorize(recorder, req)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -1153,8 +1153,8 @@ func newConsulConfig(runtimeCfg *config.RuntimeConfig, logger hclog.Logger) (*co
|
||||||
if runtimeCfg.RaftTrailingLogs != 0 {
|
if runtimeCfg.RaftTrailingLogs != 0 {
|
||||||
cfg.RaftConfig.TrailingLogs = uint64(runtimeCfg.RaftTrailingLogs)
|
cfg.RaftConfig.TrailingLogs = uint64(runtimeCfg.RaftTrailingLogs)
|
||||||
}
|
}
|
||||||
if runtimeCfg.ACLMasterToken != "" {
|
if runtimeCfg.ACLInitialManagementToken != "" {
|
||||||
cfg.ACLMasterToken = runtimeCfg.ACLMasterToken
|
cfg.ACLInitialManagementToken = runtimeCfg.ACLInitialManagementToken
|
||||||
}
|
}
|
||||||
cfg.ACLTokenReplication = runtimeCfg.ACLTokenReplication
|
cfg.ACLTokenReplication = runtimeCfg.ACLTokenReplication
|
||||||
cfg.ACLsEnabled = runtimeCfg.ACLsEnabled
|
cfg.ACLsEnabled = runtimeCfg.ACLsEnabled
|
||||||
|
@ -1263,6 +1263,7 @@ func newConsulConfig(runtimeCfg *config.RuntimeConfig, logger hclog.Logger) (*co
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.ConfigEntryBootstrap = runtimeCfg.ConfigEntryBootstrap
|
cfg.ConfigEntryBootstrap = runtimeCfg.ConfigEntryBootstrap
|
||||||
|
cfg.RaftBoltDBConfig = runtimeCfg.RaftBoltDBConfig
|
||||||
|
|
||||||
// Duplicate our own serf config once to make sure that the duplication
|
// Duplicate our own serf config once to make sure that the duplication
|
||||||
// function does not drift.
|
// function does not drift.
|
||||||
|
|
|
@ -1005,7 +1005,7 @@ func (s *HTTPHandlers) AgentHealthServiceByID(resp http.ResponseWriter, req *htt
|
||||||
}
|
}
|
||||||
notFoundReason := fmt.Sprintf("ServiceId %s not found", sid.String())
|
notFoundReason := fmt.Sprintf("ServiceId %s not found", sid.String())
|
||||||
if returnTextPlain(req) {
|
if returnTextPlain(req) {
|
||||||
return notFoundReason, CodeWithPayloadError{StatusCode: http.StatusNotFound, Reason: notFoundReason, ContentType: "application/json"}
|
return notFoundReason, CodeWithPayloadError{StatusCode: http.StatusNotFound, Reason: notFoundReason, ContentType: "text/plain"}
|
||||||
}
|
}
|
||||||
return &api.AgentServiceChecksInfo{
|
return &api.AgentServiceChecksInfo{
|
||||||
AggregatedStatus: api.HealthCritical,
|
AggregatedStatus: api.HealthCritical,
|
||||||
|
@ -1510,7 +1510,7 @@ func (s *HTTPHandlers) AgentToken(resp http.ResponseWriter, req *http.Request) (
|
||||||
}
|
}
|
||||||
|
|
||||||
case "acl_agent_master_token", "agent_master", "agent_recovery":
|
case "acl_agent_master_token", "agent_master", "agent_recovery":
|
||||||
s.agent.tokens.UpdateAgentMasterToken(args.Token, token_store.TokenSourceAPI)
|
s.agent.tokens.UpdateAgentRecoveryToken(args.Token, token_store.TokenSourceAPI)
|
||||||
|
|
||||||
case "acl_replication_token", "replication":
|
case "acl_replication_token", "replication":
|
||||||
s.agent.tokens.UpdateReplicationToken(args.Token, token_store.TokenSourceAPI)
|
s.agent.tokens.UpdateReplicationToken(args.Token, token_store.TokenSourceAPI)
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -214,10 +214,14 @@ func TestAgent_TokenStore(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
a := NewTestAgent(t, `
|
a := NewTestAgent(t, `
|
||||||
acl_token = "user"
|
acl {
|
||||||
acl_agent_token = "agent"
|
tokens {
|
||||||
acl_agent_master_token = "master"`,
|
default = "user"
|
||||||
)
|
agent = "agent"
|
||||||
|
agent_recovery = "recovery"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
|
|
||||||
if got, want := a.tokens.UserToken(), "user"; got != want {
|
if got, want := a.tokens.UserToken(), "user"; got != want {
|
||||||
|
@ -226,7 +230,7 @@ func TestAgent_TokenStore(t *testing.T) {
|
||||||
if got, want := a.tokens.AgentToken(), "agent"; got != want {
|
if got, want := a.tokens.AgentToken(), "agent"; got != want {
|
||||||
t.Fatalf("got %q want %q", got, want)
|
t.Fatalf("got %q want %q", got, want)
|
||||||
}
|
}
|
||||||
if got, want := a.tokens.IsAgentMasterToken("master"), true; got != want {
|
if got, want := a.tokens.IsAgentRecoveryToken("recovery"), true; got != want {
|
||||||
t.Fatalf("got %v want %v", got, want)
|
t.Fatalf("got %v want %v", got, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5037,7 +5041,7 @@ func TestAutoConfig_Integration(t *testing.T) {
|
||||||
srv := StartTestAgent(t, TestAgent{Name: "TestAgent-Server", HCL: hclConfig})
|
srv := StartTestAgent(t, TestAgent{Name: "TestAgent-Server", HCL: hclConfig})
|
||||||
defer srv.Shutdown()
|
defer srv.Shutdown()
|
||||||
|
|
||||||
testrpc.WaitForTestAgent(t, srv.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken))
|
testrpc.WaitForTestAgent(t, srv.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))
|
||||||
|
|
||||||
// sign a JWT token
|
// sign a JWT token
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
@ -5084,7 +5088,7 @@ func TestAutoConfig_Integration(t *testing.T) {
|
||||||
// when this is successful we managed to get the gossip key and serf addresses to bind to
|
// when this is successful we managed to get the gossip key and serf addresses to bind to
|
||||||
// and then connect. Additionally we would have to have certificates or else the
|
// and then connect. Additionally we would have to have certificates or else the
|
||||||
// verify_incoming config on the server would not let it work.
|
// verify_incoming config on the server would not let it work.
|
||||||
testrpc.WaitForTestAgent(t, client.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken))
|
testrpc.WaitForTestAgent(t, client.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))
|
||||||
|
|
||||||
// spot check that we now have an ACL token
|
// spot check that we now have an ACL token
|
||||||
require.NotEmpty(t, client.tokens.AgentToken())
|
require.NotEmpty(t, client.tokens.AgentToken())
|
||||||
|
@ -5098,7 +5102,7 @@ func TestAutoConfig_Integration(t *testing.T) {
|
||||||
ca := connect.TestCA(t, nil)
|
ca := connect.TestCA(t, nil)
|
||||||
req := &structs.CARequest{
|
req := &structs.CARequest{
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
WriteRequest: structs.WriteRequest{Token: TestDefaultMasterToken},
|
WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken},
|
||||||
Config: &structs.CAConfiguration{
|
Config: &structs.CAConfiguration{
|
||||||
Provider: "consul",
|
Provider: "consul",
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
|
@ -5170,7 +5174,7 @@ func TestAgent_AutoEncrypt(t *testing.T) {
|
||||||
srv := StartTestAgent(t, TestAgent{Name: "test-server", HCL: hclConfig})
|
srv := StartTestAgent(t, TestAgent{Name: "test-server", HCL: hclConfig})
|
||||||
defer srv.Shutdown()
|
defer srv.Shutdown()
|
||||||
|
|
||||||
testrpc.WaitForTestAgent(t, srv.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken))
|
testrpc.WaitForTestAgent(t, srv.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))
|
||||||
|
|
||||||
client := StartTestAgent(t, TestAgent{Name: "test-client", HCL: TestACLConfigWithParams(nil) + `
|
client := StartTestAgent(t, TestAgent{Name: "test-client", HCL: TestACLConfigWithParams(nil) + `
|
||||||
bootstrap = false
|
bootstrap = false
|
||||||
|
@ -5193,7 +5197,7 @@ func TestAgent_AutoEncrypt(t *testing.T) {
|
||||||
|
|
||||||
// when this is successful we managed to get a TLS certificate and are using it for
|
// when this is successful we managed to get a TLS certificate and are using it for
|
||||||
// encrypted RPC connections.
|
// encrypted RPC connections.
|
||||||
testrpc.WaitForTestAgent(t, client.RPC, "dc1", testrpc.WithToken(TestDefaultMasterToken))
|
testrpc.WaitForTestAgent(t, client.RPC, "dc1", testrpc.WithToken(TestDefaultInitialManagementToken))
|
||||||
|
|
||||||
// now we need to validate that our certificate has the correct CN
|
// now we need to validate that our certificate has the correct CN
|
||||||
aeCert := client.tlsConfigurator.Cert()
|
aeCert := client.tlsConfigurator.Cert()
|
||||||
|
|
|
@ -65,14 +65,12 @@ func translateConfig(c *pbconfig.Config) config.Config {
|
||||||
}
|
}
|
||||||
|
|
||||||
result.ACL.Tokens = config.Tokens{
|
result.ACL.Tokens = config.Tokens{
|
||||||
|
InitialManagement: stringPtrOrNil(t.InitialManagement),
|
||||||
|
AgentRecovery: stringPtrOrNil(t.AgentRecovery),
|
||||||
Replication: stringPtrOrNil(t.Replication),
|
Replication: stringPtrOrNil(t.Replication),
|
||||||
Default: stringPtrOrNil(t.Default),
|
Default: stringPtrOrNil(t.Default),
|
||||||
Agent: stringPtrOrNil(t.Agent),
|
Agent: stringPtrOrNil(t.Agent),
|
||||||
ManagedServiceProvider: tokens,
|
ManagedServiceProvider: tokens,
|
||||||
DeprecatedTokens: config.DeprecatedTokens{
|
|
||||||
Master: stringPtrOrNil(t.Master),
|
|
||||||
AgentMaster: stringPtrOrNil(t.AgentMaster),
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,11 +69,11 @@ func TestTranslateConfig(t *testing.T) {
|
||||||
EnableTokenPersistence: true,
|
EnableTokenPersistence: true,
|
||||||
MSPDisableBootstrap: false,
|
MSPDisableBootstrap: false,
|
||||||
Tokens: &pbconfig.ACLTokens{
|
Tokens: &pbconfig.ACLTokens{
|
||||||
Master: "99e7e490-6baf-43fc-9010-78b6aa9a6813",
|
InitialManagement: "99e7e490-6baf-43fc-9010-78b6aa9a6813",
|
||||||
Replication: "51308d40-465c-4ac6-a636-7c0747edec89",
|
Replication: "51308d40-465c-4ac6-a636-7c0747edec89",
|
||||||
AgentMaster: "e012e1ea-78a2-41cc-bc8b-231a44196f39",
|
AgentRecovery: "e012e1ea-78a2-41cc-bc8b-231a44196f39",
|
||||||
Default: "8781a3f5-de46-4b45-83e1-c92f4cfd0332",
|
Default: "8781a3f5-de46-4b45-83e1-c92f4cfd0332",
|
||||||
Agent: "ddb8f1b0-8a99-4032-b601-87926bce244e",
|
Agent: "ddb8f1b0-8a99-4032-b601-87926bce244e",
|
||||||
ManagedServiceProvider: []*pbconfig.ACLServiceProviderToken{
|
ManagedServiceProvider: []*pbconfig.ACLServiceProviderToken{
|
||||||
{
|
{
|
||||||
AccessorID: "23f37987-7b9e-4e5b-acae-dbc9bc137bae",
|
AccessorID: "23f37987-7b9e-4e5b-acae-dbc9bc137bae",
|
||||||
|
@ -129,19 +129,17 @@ func TestTranslateConfig(t *testing.T) {
|
||||||
EnableKeyListPolicy: boolPointer(true),
|
EnableKeyListPolicy: boolPointer(true),
|
||||||
EnableTokenPersistence: boolPointer(true),
|
EnableTokenPersistence: boolPointer(true),
|
||||||
Tokens: config.Tokens{
|
Tokens: config.Tokens{
|
||||||
Replication: stringPointer("51308d40-465c-4ac6-a636-7c0747edec89"),
|
InitialManagement: stringPointer("99e7e490-6baf-43fc-9010-78b6aa9a6813"),
|
||||||
Default: stringPointer("8781a3f5-de46-4b45-83e1-c92f4cfd0332"),
|
AgentRecovery: stringPointer("e012e1ea-78a2-41cc-bc8b-231a44196f39"),
|
||||||
Agent: stringPointer("ddb8f1b0-8a99-4032-b601-87926bce244e"),
|
Replication: stringPointer("51308d40-465c-4ac6-a636-7c0747edec89"),
|
||||||
|
Default: stringPointer("8781a3f5-de46-4b45-83e1-c92f4cfd0332"),
|
||||||
|
Agent: stringPointer("ddb8f1b0-8a99-4032-b601-87926bce244e"),
|
||||||
ManagedServiceProvider: []config.ServiceProviderToken{
|
ManagedServiceProvider: []config.ServiceProviderToken{
|
||||||
{
|
{
|
||||||
AccessorID: stringPointer("23f37987-7b9e-4e5b-acae-dbc9bc137bae"),
|
AccessorID: stringPointer("23f37987-7b9e-4e5b-acae-dbc9bc137bae"),
|
||||||
SecretID: stringPointer("e28b820a-438e-4e2b-ad24-fe59e6a4914f"),
|
SecretID: stringPointer("e28b820a-438e-4e2b-ad24-fe59e6a4914f"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
DeprecatedTokens: config.DeprecatedTokens{
|
|
||||||
Master: stringPointer("99e7e490-6baf-43fc-9010-78b6aa9a6813"),
|
|
||||||
AgentMaster: stringPointer("e012e1ea-78a2-41cc-bc8b-231a44196f39"),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
AutoEncrypt: config.AutoEncrypt{
|
AutoEncrypt: config.AutoEncrypt{
|
||||||
|
|
|
@ -860,18 +860,18 @@ func (b *builder) build() (rt RuntimeConfig, err error) {
|
||||||
ACLDefaultPolicy: stringVal(c.ACL.DefaultPolicy),
|
ACLDefaultPolicy: stringVal(c.ACL.DefaultPolicy),
|
||||||
},
|
},
|
||||||
|
|
||||||
ACLEnableKeyListPolicy: boolVal(c.ACL.EnableKeyListPolicy),
|
ACLEnableKeyListPolicy: boolVal(c.ACL.EnableKeyListPolicy),
|
||||||
ACLMasterToken: stringVal(c.ACL.Tokens.InitialManagement),
|
ACLInitialManagementToken: stringVal(c.ACL.Tokens.InitialManagement),
|
||||||
|
|
||||||
ACLTokenReplication: boolVal(c.ACL.TokenReplication),
|
ACLTokenReplication: boolVal(c.ACL.TokenReplication),
|
||||||
|
|
||||||
ACLTokens: token.Config{
|
ACLTokens: token.Config{
|
||||||
DataDir: dataDir,
|
DataDir: dataDir,
|
||||||
EnablePersistence: boolValWithDefault(c.ACL.EnableTokenPersistence, false),
|
EnablePersistence: boolValWithDefault(c.ACL.EnableTokenPersistence, false),
|
||||||
ACLDefaultToken: stringVal(c.ACL.Tokens.Default),
|
ACLDefaultToken: stringVal(c.ACL.Tokens.Default),
|
||||||
ACLAgentToken: stringVal(c.ACL.Tokens.Agent),
|
ACLAgentToken: stringVal(c.ACL.Tokens.Agent),
|
||||||
ACLAgentMasterToken: stringVal(c.ACL.Tokens.AgentRecovery),
|
ACLAgentRecoveryToken: stringVal(c.ACL.Tokens.AgentRecovery),
|
||||||
ACLReplicationToken: stringVal(c.ACL.Tokens.Replication),
|
ACLReplicationToken: stringVal(c.ACL.Tokens.Replication),
|
||||||
},
|
},
|
||||||
|
|
||||||
// Autopilot
|
// Autopilot
|
||||||
|
@ -1094,6 +1094,10 @@ func (b *builder) build() (rt RuntimeConfig, err error) {
|
||||||
|
|
||||||
rt.UseStreamingBackend = boolValWithDefault(c.UseStreamingBackend, true)
|
rt.UseStreamingBackend = boolValWithDefault(c.UseStreamingBackend, true)
|
||||||
|
|
||||||
|
if c.RaftBoltDBConfig != nil {
|
||||||
|
rt.RaftBoltDBConfig = *c.RaftBoltDBConfig
|
||||||
|
}
|
||||||
|
|
||||||
if rt.Cache.EntryFetchMaxBurst <= 0 {
|
if rt.Cache.EntryFetchMaxBurst <= 0 {
|
||||||
return RuntimeConfig{}, fmt.Errorf("cache.entry_fetch_max_burst must be strictly positive, was: %v", rt.Cache.EntryFetchMaxBurst)
|
return RuntimeConfig{}, fmt.Errorf("cache.entry_fetch_max_burst must be strictly positive, was: %v", rt.Cache.EntryFetchMaxBurst)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,8 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/agent/consul"
|
||||||
|
|
||||||
"github.com/hashicorp/hcl"
|
"github.com/hashicorp/hcl"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
|
|
||||||
|
@ -256,6 +258,8 @@ type Config struct {
|
||||||
|
|
||||||
RPC RPC `mapstructure:"rpc"`
|
RPC RPC `mapstructure:"rpc"`
|
||||||
|
|
||||||
|
RaftBoltDBConfig *consul.RaftBoltDBConfig `mapstructure:"raft_boltdb"`
|
||||||
|
|
||||||
// UseStreamingBackend instead of blocking queries for service health and
|
// UseStreamingBackend instead of blocking queries for service health and
|
||||||
// any other endpoints which support streaming.
|
// any other endpoints which support streaming.
|
||||||
UseStreamingBackend *bool `mapstructure:"use_streaming_backend"`
|
UseStreamingBackend *bool `mapstructure:"use_streaming_backend"`
|
||||||
|
|
|
@ -110,8 +110,8 @@ func TestLoad_DeprecatedConfig_ACLMasterTokens(t *testing.T) {
|
||||||
require.ElementsMatch(expectWarns, result.Warnings)
|
require.ElementsMatch(expectWarns, result.Warnings)
|
||||||
|
|
||||||
rt := result.RuntimeConfig
|
rt := result.RuntimeConfig
|
||||||
require.Equal("token1", rt.ACLMasterToken)
|
require.Equal("token1", rt.ACLInitialManagementToken)
|
||||||
require.Equal("token2", rt.ACLTokens.ACLAgentMasterToken)
|
require.Equal("token2", rt.ACLTokens.ACLAgentRecoveryToken)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("embedded in tokens struct", func(t *testing.T) {
|
t.Run("embedded in tokens struct", func(t *testing.T) {
|
||||||
|
@ -141,8 +141,8 @@ func TestLoad_DeprecatedConfig_ACLMasterTokens(t *testing.T) {
|
||||||
require.ElementsMatch(expectWarns, result.Warnings)
|
require.ElementsMatch(expectWarns, result.Warnings)
|
||||||
|
|
||||||
rt := result.RuntimeConfig
|
rt := result.RuntimeConfig
|
||||||
require.Equal("token1", rt.ACLMasterToken)
|
require.Equal("token1", rt.ACLInitialManagementToken)
|
||||||
require.Equal("token2", rt.ACLTokens.ACLAgentMasterToken)
|
require.Equal("token2", rt.ACLTokens.ACLAgentRecoveryToken)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("both", func(t *testing.T) {
|
t.Run("both", func(t *testing.T) {
|
||||||
|
@ -169,7 +169,7 @@ func TestLoad_DeprecatedConfig_ACLMasterTokens(t *testing.T) {
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
rt := result.RuntimeConfig
|
rt := result.RuntimeConfig
|
||||||
require.Equal("token3", rt.ACLMasterToken)
|
require.Equal("token3", rt.ACLInitialManagementToken)
|
||||||
require.Equal("token4", rt.ACLTokens.ACLAgentMasterToken)
|
require.Equal("token4", rt.ACLTokens.ACLAgentRecoveryToken)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,12 +73,12 @@ type RuntimeConfig struct {
|
||||||
// hcl: acl.enable_key_list_policy = (true|false)
|
// hcl: acl.enable_key_list_policy = (true|false)
|
||||||
ACLEnableKeyListPolicy bool
|
ACLEnableKeyListPolicy bool
|
||||||
|
|
||||||
// ACLMasterToken is used to bootstrap the ACL system. It should be specified
|
// ACLInitialManagementToken is used to bootstrap the ACL system. It should be specified
|
||||||
// on the servers in the PrimaryDatacenter. When the leader comes online, it ensures
|
// on the servers in the PrimaryDatacenter. When the leader comes online, it ensures
|
||||||
// that the Master token is available. This provides the initial token.
|
// that the initial management token is available. This provides the initial token.
|
||||||
//
|
//
|
||||||
// hcl: acl.tokens.initial_management = string
|
// hcl: acl.tokens.initial_management = string
|
||||||
ACLMasterToken string
|
ACLInitialManagementToken string
|
||||||
|
|
||||||
// ACLtokenReplication is used to indicate that both tokens and policies
|
// ACLtokenReplication is used to indicate that both tokens and policies
|
||||||
// should be replicated instead of just policies
|
// should be replicated instead of just policies
|
||||||
|
@ -943,6 +943,8 @@ type RuntimeConfig struct {
|
||||||
// hcl: raft_trailing_logs = int
|
// hcl: raft_trailing_logs = int
|
||||||
RaftTrailingLogs int
|
RaftTrailingLogs int
|
||||||
|
|
||||||
|
RaftBoltDBConfig consul.RaftBoltDBConfig
|
||||||
|
|
||||||
// ReconnectTimeoutLAN specifies the amount of time to wait to reconnect with
|
// ReconnectTimeoutLAN specifies the amount of time to wait to reconnect with
|
||||||
// another agent before deciding it's permanently gone. This can be used to
|
// another agent before deciding it's permanently gone. This can be used to
|
||||||
// control the time it takes to reap failed nodes from the cluster.
|
// control the time it takes to reap failed nodes from the cluster.
|
||||||
|
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/hashicorp/consul/acl"
|
||||||
"github.com/hashicorp/consul/agent/cache"
|
"github.com/hashicorp/consul/agent/cache"
|
||||||
"github.com/hashicorp/consul/agent/checks"
|
"github.com/hashicorp/consul/agent/checks"
|
||||||
"github.com/hashicorp/consul/agent/consul"
|
"github.com/hashicorp/consul/agent/consul"
|
||||||
|
@ -4085,6 +4086,7 @@ func TestLoad_IntegrationWithFlags(t *testing.T) {
|
||||||
Service: "carrot",
|
Service: "carrot",
|
||||||
ServiceSubset: "kale",
|
ServiceSubset: "kale",
|
||||||
Namespace: "leek",
|
Namespace: "leek",
|
||||||
|
Partition: acl.DefaultPartitionName,
|
||||||
PrefixRewrite: "/alternate",
|
PrefixRewrite: "/alternate",
|
||||||
RequestTimeout: 99 * time.Second,
|
RequestTimeout: 99 * time.Second,
|
||||||
NumRetries: 12345,
|
NumRetries: 12345,
|
||||||
|
@ -5339,12 +5341,12 @@ func TestLoad_FullConfig(t *testing.T) {
|
||||||
// user configurable values
|
// user configurable values
|
||||||
|
|
||||||
ACLTokens: token.Config{
|
ACLTokens: token.Config{
|
||||||
EnablePersistence: true,
|
EnablePersistence: true,
|
||||||
DataDir: dataDir,
|
DataDir: dataDir,
|
||||||
ACLDefaultToken: "418fdff1",
|
ACLDefaultToken: "418fdff1",
|
||||||
ACLAgentToken: "bed2377c",
|
ACLAgentToken: "bed2377c",
|
||||||
ACLAgentMasterToken: "1dba6aba",
|
ACLAgentRecoveryToken: "1dba6aba",
|
||||||
ACLReplicationToken: "5795983a",
|
ACLReplicationToken: "5795983a",
|
||||||
},
|
},
|
||||||
|
|
||||||
ACLsEnabled: true,
|
ACLsEnabled: true,
|
||||||
|
@ -5361,7 +5363,7 @@ func TestLoad_FullConfig(t *testing.T) {
|
||||||
ACLRoleTTL: 9876 * time.Second,
|
ACLRoleTTL: 9876 * time.Second,
|
||||||
},
|
},
|
||||||
ACLEnableKeyListPolicy: true,
|
ACLEnableKeyListPolicy: true,
|
||||||
ACLMasterToken: "3820e09a",
|
ACLInitialManagementToken: "3820e09a",
|
||||||
ACLTokenReplication: true,
|
ACLTokenReplication: true,
|
||||||
AdvertiseAddrLAN: ipAddr("17.99.29.16"),
|
AdvertiseAddrLAN: ipAddr("17.99.29.16"),
|
||||||
AdvertiseAddrWAN: ipAddr("78.63.37.19"),
|
AdvertiseAddrWAN: ipAddr("78.63.37.19"),
|
||||||
|
@ -6015,6 +6017,7 @@ func TestLoad_FullConfig(t *testing.T) {
|
||||||
"args": []interface{}{"dltjDJ2a", "flEa7C2d"},
|
"args": []interface{}{"dltjDJ2a", "flEa7C2d"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
RaftBoltDBConfig: consul.RaftBoltDBConfig{NoFreelistSync: true},
|
||||||
}
|
}
|
||||||
entFullRuntimeConfig(expected)
|
entFullRuntimeConfig(expected)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"ACLEnableKeyListPolicy": false,
|
"ACLEnableKeyListPolicy": false,
|
||||||
"ACLMasterToken": "hidden",
|
"ACLInitialManagementToken": "hidden",
|
||||||
"ACLResolverSettings": {
|
"ACLResolverSettings": {
|
||||||
"ACLDefaultPolicy": "",
|
"ACLDefaultPolicy": "",
|
||||||
"ACLDownPolicy": "",
|
"ACLDownPolicy": "",
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
},
|
},
|
||||||
"ACLTokenReplication": false,
|
"ACLTokenReplication": false,
|
||||||
"ACLTokens": {
|
"ACLTokens": {
|
||||||
"ACLAgentMasterToken": "hidden",
|
"ACLAgentRecoveryToken": "hidden",
|
||||||
"ACLAgentToken": "hidden",
|
"ACLAgentToken": "hidden",
|
||||||
"ACLDefaultToken": "hidden",
|
"ACLDefaultToken": "hidden",
|
||||||
"ACLReplicationToken": "hidden",
|
"ACLReplicationToken": "hidden",
|
||||||
|
@ -252,6 +252,9 @@
|
||||||
"RPCMaxConnsPerClient": 0,
|
"RPCMaxConnsPerClient": 0,
|
||||||
"RPCProtocol": 0,
|
"RPCProtocol": 0,
|
||||||
"RPCRateLimit": 0,
|
"RPCRateLimit": 0,
|
||||||
|
"RaftBoltDBConfig": {
|
||||||
|
"NoFreelistSync": false
|
||||||
|
},
|
||||||
"RaftProtocol": 3,
|
"RaftProtocol": 3,
|
||||||
"RaftSnapshotInterval": "0s",
|
"RaftSnapshotInterval": "0s",
|
||||||
"RaftSnapshotThreshold": 0,
|
"RaftSnapshotThreshold": 0,
|
||||||
|
@ -421,4 +424,4 @@
|
||||||
"Version": "",
|
"Version": "",
|
||||||
"VersionPrerelease": "",
|
"VersionPrerelease": "",
|
||||||
"Watches": []
|
"Watches": []
|
||||||
}
|
}
|
|
@ -328,6 +328,9 @@ raft_protocol = 3
|
||||||
raft_snapshot_threshold = 16384
|
raft_snapshot_threshold = 16384
|
||||||
raft_snapshot_interval = "30s"
|
raft_snapshot_interval = "30s"
|
||||||
raft_trailing_logs = 83749
|
raft_trailing_logs = 83749
|
||||||
|
raft_boltdb {
|
||||||
|
NoFreelistSync = true
|
||||||
|
}
|
||||||
read_replica = true
|
read_replica = true
|
||||||
reconnect_timeout = "23739s"
|
reconnect_timeout = "23739s"
|
||||||
reconnect_timeout_wan = "26694s"
|
reconnect_timeout_wan = "26694s"
|
||||||
|
|
|
@ -326,6 +326,9 @@
|
||||||
"raft_snapshot_threshold": 16384,
|
"raft_snapshot_threshold": 16384,
|
||||||
"raft_snapshot_interval": "30s",
|
"raft_snapshot_interval": "30s",
|
||||||
"raft_trailing_logs": 83749,
|
"raft_trailing_logs": 83749,
|
||||||
|
"raft_boltdb": {
|
||||||
|
"NoFreelistSync": true
|
||||||
|
},
|
||||||
"read_replica": true,
|
"read_replica": true,
|
||||||
"reconnect_timeout": "23739s",
|
"reconnect_timeout": "23739s",
|
||||||
"reconnect_timeout_wan": "26694s",
|
"reconnect_timeout_wan": "26694s",
|
||||||
|
|
|
@ -1053,7 +1053,7 @@ func (r *ACLResolver) resolveLocallyManagedToken(token string) (structs.ACLIdent
|
||||||
return nil, nil, false
|
return nil, nil, false
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.tokens.IsAgentMasterToken(token) {
|
if r.tokens.IsAgentRecoveryToken(token) {
|
||||||
return structs.NewAgentMasterTokenIdentity(r.config.NodeName, token), r.agentMasterAuthz, true
|
return structs.NewAgentMasterTokenIdentity(r.config.NodeName, token), r.agentMasterAuthz, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1465,11 +1465,13 @@ func (f *aclFilter) filterIntentions(ixns *structs.Intentions) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterNodeDump is used to filter through all parts of a node dump and
|
// filterNodeDump is used to filter through all parts of a node dump and
|
||||||
// remove elements the provided ACL token cannot access.
|
// remove elements the provided ACL token cannot access. Returns true if
|
||||||
func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
|
// any elements were removed.
|
||||||
|
func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) bool {
|
||||||
nd := *dump
|
nd := *dump
|
||||||
|
|
||||||
var authzContext acl.AuthorizerContext
|
var authzContext acl.AuthorizerContext
|
||||||
|
var removed bool
|
||||||
for i := 0; i < len(nd); i++ {
|
for i := 0; i < len(nd); i++ {
|
||||||
info := nd[i]
|
info := nd[i]
|
||||||
|
|
||||||
|
@ -1477,6 +1479,7 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
|
||||||
info.FillAuthzContext(&authzContext)
|
info.FillAuthzContext(&authzContext)
|
||||||
if node := info.Node; !f.allowNode(node, &authzContext) {
|
if node := info.Node; !f.allowNode(node, &authzContext) {
|
||||||
f.logger.Debug("dropping node from result due to ACLs", "node", structs.NodeNameString(node, info.GetEnterpriseMeta()))
|
f.logger.Debug("dropping node from result due to ACLs", "node", structs.NodeNameString(node, info.GetEnterpriseMeta()))
|
||||||
|
removed = true
|
||||||
nd = append(nd[:i], nd[i+1:]...)
|
nd = append(nd[:i], nd[i+1:]...)
|
||||||
i--
|
i--
|
||||||
continue
|
continue
|
||||||
|
@ -1490,6 +1493,7 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f.logger.Debug("dropping service from result due to ACLs", "service", svc)
|
f.logger.Debug("dropping service from result due to ACLs", "service", svc)
|
||||||
|
removed = true
|
||||||
info.Services = append(info.Services[:j], info.Services[j+1:]...)
|
info.Services = append(info.Services[:j], info.Services[j+1:]...)
|
||||||
j--
|
j--
|
||||||
}
|
}
|
||||||
|
@ -1502,17 +1506,21 @@ func (f *aclFilter) filterNodeDump(dump *structs.NodeDump) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f.logger.Debug("dropping check from result due to ACLs", "check", chk.CheckID)
|
f.logger.Debug("dropping check from result due to ACLs", "check", chk.CheckID)
|
||||||
|
removed = true
|
||||||
info.Checks = append(info.Checks[:j], info.Checks[j+1:]...)
|
info.Checks = append(info.Checks[:j], info.Checks[j+1:]...)
|
||||||
j--
|
j--
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
*dump = nd
|
*dump = nd
|
||||||
|
return removed
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterServiceDump is used to filter nodes based on ACL rules.
|
// filterServiceDump is used to filter nodes based on ACL rules. Returns true
|
||||||
func (f *aclFilter) filterServiceDump(services *structs.ServiceDump) {
|
// if any elements were removed.
|
||||||
|
func (f *aclFilter) filterServiceDump(services *structs.ServiceDump) bool {
|
||||||
svcs := *services
|
svcs := *services
|
||||||
var authzContext acl.AuthorizerContext
|
var authzContext acl.AuthorizerContext
|
||||||
|
var removed bool
|
||||||
|
|
||||||
for i := 0; i < len(svcs); i++ {
|
for i := 0; i < len(svcs); i++ {
|
||||||
service := svcs[i]
|
service := svcs[i]
|
||||||
|
@ -1530,10 +1538,12 @@ func (f *aclFilter) filterServiceDump(services *structs.ServiceDump) {
|
||||||
}
|
}
|
||||||
|
|
||||||
f.logger.Debug("dropping service from result due to ACLs", "service", service.GatewayService.Service)
|
f.logger.Debug("dropping service from result due to ACLs", "service", service.GatewayService.Service)
|
||||||
|
removed = true
|
||||||
svcs = append(svcs[:i], svcs[i+1:]...)
|
svcs = append(svcs[:i], svcs[i+1:]...)
|
||||||
i--
|
i--
|
||||||
}
|
}
|
||||||
*services = svcs
|
*services = svcs
|
||||||
|
return removed
|
||||||
}
|
}
|
||||||
|
|
||||||
// filterNodes is used to filter through all parts of a node list and remove
|
// filterNodes is used to filter through all parts of a node list and remove
|
||||||
|
@ -1592,8 +1602,10 @@ func (f *aclFilter) redactPreparedQueryTokens(query **structs.PreparedQuery) {
|
||||||
|
|
||||||
// filterPreparedQueries is used to filter prepared queries based on ACL rules.
|
// filterPreparedQueries is used to filter prepared queries based on ACL rules.
|
||||||
// We prune entries the user doesn't have access to, and we redact any tokens
|
// We prune entries the user doesn't have access to, and we redact any tokens
|
||||||
// if the user doesn't have a management token.
|
// if the user doesn't have a management token. Returns true if any (named)
|
||||||
func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
|
// queries were removed - un-named queries are meant to be ephemeral and can
|
||||||
|
// only be enumerated by a management token
|
||||||
|
func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) bool {
|
||||||
var authzContext acl.AuthorizerContext
|
var authzContext acl.AuthorizerContext
|
||||||
structs.DefaultEnterpriseMetaInDefaultPartition().FillAuthzContext(&authzContext)
|
structs.DefaultEnterpriseMetaInDefaultPartition().FillAuthzContext(&authzContext)
|
||||||
// Management tokens can see everything with no filtering.
|
// Management tokens can see everything with no filtering.
|
||||||
|
@ -1601,17 +1613,22 @@ func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
|
||||||
// the 1.4 ACL rewrite. The global-management token will provide unrestricted query privileges
|
// the 1.4 ACL rewrite. The global-management token will provide unrestricted query privileges
|
||||||
// so asking for ACLWrite should be unnecessary.
|
// so asking for ACLWrite should be unnecessary.
|
||||||
if f.authorizer.ACLWrite(&authzContext) == acl.Allow {
|
if f.authorizer.ACLWrite(&authzContext) == acl.Allow {
|
||||||
return
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, we need to see what the token has access to.
|
// Otherwise, we need to see what the token has access to.
|
||||||
|
var namedQueriesRemoved bool
|
||||||
ret := make(structs.PreparedQueries, 0, len(*queries))
|
ret := make(structs.PreparedQueries, 0, len(*queries))
|
||||||
for _, query := range *queries {
|
for _, query := range *queries {
|
||||||
// If no prefix ACL applies to this query then filter it, since
|
// If no prefix ACL applies to this query then filter it, since
|
||||||
// we know at this point the user doesn't have a management
|
// we know at this point the user doesn't have a management
|
||||||
// token, otherwise see what the policy says.
|
// token, otherwise see what the policy says.
|
||||||
prefix, ok := query.GetACLPrefix()
|
prefix, hasName := query.GetACLPrefix()
|
||||||
if !ok || f.authorizer.PreparedQueryRead(prefix, &authzContext) != acl.Allow {
|
switch {
|
||||||
|
case hasName && f.authorizer.PreparedQueryRead(prefix, &authzContext) != acl.Allow:
|
||||||
|
namedQueriesRemoved = true
|
||||||
|
fallthrough
|
||||||
|
case !hasName:
|
||||||
f.logger.Debug("dropping prepared query from result due to ACLs", "query", query.ID)
|
f.logger.Debug("dropping prepared query from result due to ACLs", "query", query.ID)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -1623,6 +1640,7 @@ func (f *aclFilter) filterPreparedQueries(queries *structs.PreparedQueries) {
|
||||||
ret = append(ret, final)
|
ret = append(ret, final)
|
||||||
}
|
}
|
||||||
*queries = ret
|
*queries = ret
|
||||||
|
return namedQueriesRemoved
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *aclFilter) filterToken(token **structs.ACLToken) {
|
func (f *aclFilter) filterToken(token **structs.ACLToken) {
|
||||||
|
@ -1815,8 +1833,8 @@ func (f *aclFilter) filterServiceList(services *structs.ServiceList) bool {
|
||||||
// filterGatewayServices is used to filter gateway to service mappings based on ACL rules.
|
// filterGatewayServices is used to filter gateway to service mappings based on ACL rules.
|
||||||
// Returns true if any elements were removed.
|
// Returns true if any elements were removed.
|
||||||
func (f *aclFilter) filterGatewayServices(mappings *structs.GatewayServices) bool {
|
func (f *aclFilter) filterGatewayServices(mappings *structs.GatewayServices) bool {
|
||||||
var removed bool
|
|
||||||
ret := make(structs.GatewayServices, 0, len(*mappings))
|
ret := make(structs.GatewayServices, 0, len(*mappings))
|
||||||
|
var removed bool
|
||||||
for _, s := range *mappings {
|
for _, s := range *mappings {
|
||||||
// This filter only checks ServiceRead on the linked service.
|
// This filter only checks ServiceRead on the linked service.
|
||||||
// ServiceRead on the gateway is checked in the GatewayServices endpoint before filtering.
|
// ServiceRead on the gateway is checked in the GatewayServices endpoint before filtering.
|
||||||
|
@ -1847,6 +1865,9 @@ func filterACLWithAuthorizer(logger hclog.Logger, authorizer acl.Authorizer, sub
|
||||||
case *structs.IndexedCheckServiceNodes:
|
case *structs.IndexedCheckServiceNodes:
|
||||||
v.QueryMeta.ResultsFilteredByACLs = filt.filterCheckServiceNodes(&v.Nodes)
|
v.QueryMeta.ResultsFilteredByACLs = filt.filterCheckServiceNodes(&v.Nodes)
|
||||||
|
|
||||||
|
case *structs.PreparedQueryExecuteResponse:
|
||||||
|
v.QueryMeta.ResultsFilteredByACLs = filt.filterCheckServiceNodes(&v.Nodes)
|
||||||
|
|
||||||
case *structs.IndexedServiceTopology:
|
case *structs.IndexedServiceTopology:
|
||||||
filtered := filt.filterServiceTopology(v.ServiceTopology)
|
filtered := filt.filterServiceTopology(v.ServiceTopology)
|
||||||
if filtered {
|
if filtered {
|
||||||
|
@ -1867,10 +1888,10 @@ func filterACLWithAuthorizer(logger hclog.Logger, authorizer acl.Authorizer, sub
|
||||||
v.QueryMeta.ResultsFilteredByACLs = filt.filterIntentions(&v.Intentions)
|
v.QueryMeta.ResultsFilteredByACLs = filt.filterIntentions(&v.Intentions)
|
||||||
|
|
||||||
case *structs.IndexedNodeDump:
|
case *structs.IndexedNodeDump:
|
||||||
filt.filterNodeDump(&v.Dump)
|
v.QueryMeta.ResultsFilteredByACLs = filt.filterNodeDump(&v.Dump)
|
||||||
|
|
||||||
case *structs.IndexedServiceDump:
|
case *structs.IndexedServiceDump:
|
||||||
filt.filterServiceDump(&v.Dump)
|
v.QueryMeta.ResultsFilteredByACLs = filt.filterServiceDump(&v.Dump)
|
||||||
|
|
||||||
case *structs.IndexedNodes:
|
case *structs.IndexedNodes:
|
||||||
v.QueryMeta.ResultsFilteredByACLs = filt.filterNodes(&v.Nodes)
|
v.QueryMeta.ResultsFilteredByACLs = filt.filterNodes(&v.Nodes)
|
||||||
|
@ -1891,7 +1912,7 @@ func filterACLWithAuthorizer(logger hclog.Logger, authorizer acl.Authorizer, sub
|
||||||
v.QueryMeta.ResultsFilteredByACLs = filt.filterSessions(&v.Sessions)
|
v.QueryMeta.ResultsFilteredByACLs = filt.filterSessions(&v.Sessions)
|
||||||
|
|
||||||
case *structs.IndexedPreparedQueries:
|
case *structs.IndexedPreparedQueries:
|
||||||
filt.filterPreparedQueries(&v.Queries)
|
v.QueryMeta.ResultsFilteredByACLs = filt.filterPreparedQueries(&v.Queries)
|
||||||
|
|
||||||
case **structs.PreparedQuery:
|
case **structs.PreparedQuery:
|
||||||
filt.redactPreparedQueryTokens(v)
|
filt.redactPreparedQueryTokens(v)
|
||||||
|
@ -1959,6 +1980,6 @@ func filterACL(r *ACLResolver, token string, subj interface{}) error {
|
||||||
|
|
||||||
type partitionInfoNoop struct{}
|
type partitionInfoNoop struct{}
|
||||||
|
|
||||||
func (p *partitionInfoNoop) ExportsForPartition(partition string) acl.PartitionExports {
|
func (p *partitionInfoNoop) ExportsForPartition(partition string) acl.ExportedServices {
|
||||||
return acl.PartitionExports{}
|
return acl.ExportedServices{}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ func TestACLEndpoint_BootstrapTokens(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
dir, srv, codec := testACLServerWithConfig(t, func(c *Config) {
|
dir, srv, codec := testACLServerWithConfig(t, func(c *Config) {
|
||||||
// remove this as we are bootstrapping
|
// remove this as we are bootstrapping
|
||||||
c.ACLMasterToken = ""
|
c.ACLInitialManagementToken = ""
|
||||||
}, false)
|
}, false)
|
||||||
waitForLeaderEstablishment(t, srv)
|
waitForLeaderEstablishment(t, srv)
|
||||||
|
|
||||||
|
|
|
@ -301,7 +301,7 @@ func TestACLReplication_Tokens(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -513,7 +513,7 @@ func TestACLReplication_Policies(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -633,7 +633,7 @@ func TestACLReplication_TokensRedacted(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -783,7 +783,7 @@ func TestACLReplication_AllTypes(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
|
|
@ -2752,6 +2752,108 @@ func TestACL_filterCheckServiceNodes(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestACL_filterPreparedQueryExecuteResponse(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
logger := hclog.NewNullLogger()
|
||||||
|
|
||||||
|
makeList := func() *structs.PreparedQueryExecuteResponse {
|
||||||
|
return &structs.PreparedQueryExecuteResponse{
|
||||||
|
Nodes: structs.CheckServiceNodes{
|
||||||
|
{
|
||||||
|
Node: &structs.Node{
|
||||||
|
Node: "node1",
|
||||||
|
},
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
ID: "foo",
|
||||||
|
Service: "foo",
|
||||||
|
},
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
{
|
||||||
|
Node: "node1",
|
||||||
|
CheckID: "check1",
|
||||||
|
ServiceName: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("allowed", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
service "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxLegacy, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Len(list.Nodes, 1)
|
||||||
|
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("allowed to read the service, but not the node", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
service "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxLegacy, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Empty(list.Nodes)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("allowed to read the node, but not the service", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxLegacy, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Empty(list.Nodes)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("denied", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, acl.DenyAll(), list)
|
||||||
|
|
||||||
|
require.Empty(list.Nodes)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestACL_filterServiceTopology(t *testing.T) {
|
func TestACL_filterServiceTopology(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// Create some nodes.
|
// Create some nodes.
|
||||||
|
@ -3024,107 +3126,105 @@ func TestACL_filterSessions(t *testing.T) {
|
||||||
|
|
||||||
func TestACL_filterNodeDump(t *testing.T) {
|
func TestACL_filterNodeDump(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
// Create a node dump.
|
|
||||||
fill := func() structs.NodeDump {
|
logger := hclog.NewNullLogger()
|
||||||
return structs.NodeDump{
|
|
||||||
&structs.NodeInfo{
|
makeList := func() *structs.IndexedNodeDump {
|
||||||
Node: "node1",
|
return &structs.IndexedNodeDump{
|
||||||
Services: []*structs.NodeService{
|
Dump: structs.NodeDump{
|
||||||
{
|
{
|
||||||
ID: "foo",
|
Node: "node1",
|
||||||
Service: "foo",
|
Services: []*structs.NodeService{
|
||||||
|
{
|
||||||
|
ID: "foo",
|
||||||
|
Service: "foo",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
Checks: []*structs.HealthCheck{
|
||||||
Checks: []*structs.HealthCheck{
|
{
|
||||||
{
|
Node: "node1",
|
||||||
Node: "node1",
|
CheckID: "check1",
|
||||||
CheckID: "check1",
|
ServiceName: "foo",
|
||||||
ServiceName: "foo",
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try permissive filtering.
|
t.Run("allowed", func(t *testing.T) {
|
||||||
{
|
require := require.New(t)
|
||||||
dump := fill()
|
|
||||||
filt := newACLFilter(acl.AllowAll(), nil)
|
|
||||||
filt.filterNodeDump(&dump)
|
|
||||||
if len(dump) != 1 {
|
|
||||||
t.Fatalf("bad: %#v", dump)
|
|
||||||
}
|
|
||||||
if len(dump[0].Services) != 1 {
|
|
||||||
t.Fatalf("bad: %#v", dump[0].Services)
|
|
||||||
}
|
|
||||||
if len(dump[0].Checks) != 1 {
|
|
||||||
t.Fatalf("bad: %#v", dump[0].Checks)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try restrictive filtering.
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
{
|
service "foo" {
|
||||||
dump := fill()
|
policy = "read"
|
||||||
filt := newACLFilter(acl.DenyAll(), nil)
|
}
|
||||||
filt.filterNodeDump(&dump)
|
node "node1" {
|
||||||
if len(dump) != 0 {
|
policy = "read"
|
||||||
t.Fatalf("bad: %#v", dump)
|
}
|
||||||
}
|
`, acl.SyntaxLegacy, nil, nil)
|
||||||
}
|
require.NoError(err)
|
||||||
|
|
||||||
// Allowed to see the service but not the node.
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
policy, err := acl.NewPolicyFromSource(`
|
require.NoError(err)
|
||||||
service "foo" {
|
|
||||||
policy = "read"
|
|
||||||
}
|
|
||||||
`, acl.SyntaxLegacy, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err %v", err)
|
|
||||||
}
|
|
||||||
perms, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// But the node will block it.
|
list := makeList()
|
||||||
{
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
dump := fill()
|
|
||||||
filt := newACLFilter(perms, nil)
|
|
||||||
filt.filterNodeDump(&dump)
|
|
||||||
if len(dump) != 0 {
|
|
||||||
t.Fatalf("bad: %#v", dump)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chain on access to the node.
|
require.Len(list.Dump, 1)
|
||||||
policy, err = acl.NewPolicyFromSource(`
|
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||||
node "node1" {
|
})
|
||||||
policy = "read"
|
|
||||||
}
|
|
||||||
`, acl.SyntaxLegacy, nil, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err %v", err)
|
|
||||||
}
|
|
||||||
perms, err = acl.NewPolicyAuthorizerWithDefaults(perms, []*acl.Policy{policy}, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now it should go through.
|
t.Run("allowed to read the service, but not the node", func(t *testing.T) {
|
||||||
{
|
require := require.New(t)
|
||||||
dump := fill()
|
|
||||||
filt := newACLFilter(perms, nil)
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
filt.filterNodeDump(&dump)
|
service "foo" {
|
||||||
if len(dump) != 1 {
|
policy = "read"
|
||||||
t.Fatalf("bad: %#v", dump)
|
}
|
||||||
}
|
`, acl.SyntaxLegacy, nil, nil)
|
||||||
if len(dump[0].Services) != 1 {
|
require.NoError(err)
|
||||||
t.Fatalf("bad: %#v", dump[0].Services)
|
|
||||||
}
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
if len(dump[0].Checks) != 1 {
|
require.NoError(err)
|
||||||
t.Fatalf("bad: %#v", dump[0].Checks)
|
|
||||||
}
|
list := makeList()
|
||||||
}
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Empty(list.Dump)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("allowed to read the node, but not the service", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxLegacy, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Len(list.Dump, 1)
|
||||||
|
require.Empty(list.Dump[0].Services)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("denied", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, acl.DenyAll(), list)
|
||||||
|
|
||||||
|
require.Empty(list.Dump)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestACL_filterNodes(t *testing.T) {
|
func TestACL_filterNodes(t *testing.T) {
|
||||||
|
@ -3155,6 +3255,277 @@ func TestACL_filterNodes(t *testing.T) {
|
||||||
require.Len(nodes, 0)
|
require.Len(nodes, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestACL_filterIndexedNodesWithGateways(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
logger := hclog.NewNullLogger()
|
||||||
|
|
||||||
|
makeList := func() *structs.IndexedNodesWithGateways {
|
||||||
|
return &structs.IndexedNodesWithGateways{
|
||||||
|
Nodes: structs.CheckServiceNodes{
|
||||||
|
{
|
||||||
|
Node: &structs.Node{
|
||||||
|
Node: "node1",
|
||||||
|
},
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
ID: "foo",
|
||||||
|
Service: "foo",
|
||||||
|
},
|
||||||
|
Checks: structs.HealthChecks{
|
||||||
|
{
|
||||||
|
Node: "node1",
|
||||||
|
CheckID: "check1",
|
||||||
|
ServiceName: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Gateways: structs.GatewayServices{
|
||||||
|
{Service: structs.ServiceNameFromString("foo")},
|
||||||
|
{Service: structs.ServiceNameFromString("bar")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("allowed", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
service "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service "bar" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxLegacy, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Len(list.Nodes, 1)
|
||||||
|
require.Len(list.Gateways, 2)
|
||||||
|
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not allowed to read the node", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
service "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service "bar" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxLegacy, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Empty(list.Nodes)
|
||||||
|
require.Len(list.Gateways, 2)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("allowed to read the node, but not the service", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service "bar" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxLegacy, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Empty(list.Nodes)
|
||||||
|
require.Len(list.Gateways, 1)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not allowed to read the other gatway service", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
service "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxLegacy, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Len(list.Nodes, 1)
|
||||||
|
require.Len(list.Gateways, 1)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("denied", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, acl.DenyAll(), list)
|
||||||
|
|
||||||
|
require.Empty(list.Nodes)
|
||||||
|
require.Empty(list.Gateways)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestACL_filterIndexedServiceDump(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
logger := hclog.NewNullLogger()
|
||||||
|
|
||||||
|
makeList := func() *structs.IndexedServiceDump {
|
||||||
|
return &structs.IndexedServiceDump{
|
||||||
|
Dump: structs.ServiceDump{
|
||||||
|
{
|
||||||
|
Node: &structs.Node{
|
||||||
|
Node: "node1",
|
||||||
|
},
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Service: "foo",
|
||||||
|
},
|
||||||
|
GatewayService: &structs.GatewayService{
|
||||||
|
Service: structs.ServiceNameFromString("foo"),
|
||||||
|
Gateway: structs.ServiceNameFromString("foo-gateway"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// No node information.
|
||||||
|
{
|
||||||
|
GatewayService: &structs.GatewayService{
|
||||||
|
Service: structs.ServiceNameFromString("bar"),
|
||||||
|
Gateway: structs.ServiceNameFromString("bar-gateway"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("allowed", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service_prefix "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service_prefix "bar" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxCurrent, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Len(list.Dump, 2)
|
||||||
|
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not allowed to access node", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
service_prefix "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service_prefix "bar" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxCurrent, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Len(list.Dump, 1)
|
||||||
|
require.Equal("bar", list.Dump[0].GatewayService.Service.Name)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not allowed to access service", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service "foo-gateway" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxCurrent, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Empty(list.Dump)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("not allowed to access gateway", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service "foo" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxCurrent, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
require.Empty(list.Dump)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestACL_filterDatacenterCheckServiceNodes(t *testing.T) {
|
func TestACL_filterDatacenterCheckServiceNodes(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -3353,70 +3724,97 @@ func TestFilterACL_redactTokenSecrets(t *testing.T) {
|
||||||
|
|
||||||
func TestACL_filterPreparedQueries(t *testing.T) {
|
func TestACL_filterPreparedQueries(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
queries := structs.PreparedQueries{
|
|
||||||
&structs.PreparedQuery{
|
logger := hclog.NewNullLogger()
|
||||||
ID: "f004177f-2c28-83b7-4229-eacc25fe55d1",
|
|
||||||
},
|
makeList := func() *structs.IndexedPreparedQueries {
|
||||||
&structs.PreparedQuery{
|
return &structs.IndexedPreparedQueries{
|
||||||
ID: "f004177f-2c28-83b7-4229-eacc25fe55d2",
|
Queries: structs.PreparedQueries{
|
||||||
Name: "query-with-no-token",
|
{ID: "f004177f-2c28-83b7-4229-eacc25fe55d1"},
|
||||||
},
|
{
|
||||||
&structs.PreparedQuery{
|
ID: "f004177f-2c28-83b7-4229-eacc25fe55d2",
|
||||||
ID: "f004177f-2c28-83b7-4229-eacc25fe55d3",
|
Name: "query-with-no-token",
|
||||||
Name: "query-with-a-token",
|
},
|
||||||
Token: "root",
|
{
|
||||||
},
|
ID: "f004177f-2c28-83b7-4229-eacc25fe55d3",
|
||||||
|
Name: "query-with-a-token",
|
||||||
|
Token: "root",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
expected := structs.PreparedQueries{
|
t.Run("management token", func(t *testing.T) {
|
||||||
&structs.PreparedQuery{
|
require := require.New(t)
|
||||||
ID: "f004177f-2c28-83b7-4229-eacc25fe55d1",
|
|
||||||
},
|
|
||||||
&structs.PreparedQuery{
|
|
||||||
ID: "f004177f-2c28-83b7-4229-eacc25fe55d2",
|
|
||||||
Name: "query-with-no-token",
|
|
||||||
},
|
|
||||||
&structs.PreparedQuery{
|
|
||||||
ID: "f004177f-2c28-83b7-4229-eacc25fe55d3",
|
|
||||||
Name: "query-with-a-token",
|
|
||||||
Token: "root",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try permissive filtering with a management token. This will allow the
|
list := makeList()
|
||||||
// embedded token to be seen.
|
filterACLWithAuthorizer(logger, acl.ManageAll(), list)
|
||||||
filt := newACLFilter(acl.ManageAll(), nil)
|
|
||||||
filt.filterPreparedQueries(&queries)
|
|
||||||
if !reflect.DeepEqual(queries, expected) {
|
|
||||||
t.Fatalf("bad: %#v", queries)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hang on to the entry with a token, which needs to survive the next
|
// Check we get the un-named query.
|
||||||
// operation.
|
require.Len(list.Queries, 3)
|
||||||
original := queries[2]
|
|
||||||
|
|
||||||
// Now try permissive filtering with a client token, which should cause
|
// Check we get the un-redacted token.
|
||||||
// the embedded token to get redacted, and the query with no name to get
|
require.Equal("root", list.Queries[2].Token)
|
||||||
// filtered out.
|
|
||||||
filt = newACLFilter(acl.AllowAll(), nil)
|
|
||||||
filt.filterPreparedQueries(&queries)
|
|
||||||
expected[2].Token = redactedToken
|
|
||||||
expected = append(structs.PreparedQueries{}, expected[1], expected[2])
|
|
||||||
if !reflect.DeepEqual(queries, expected) {
|
|
||||||
t.Fatalf("bad: %#v", queries)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure that the original object didn't lose its token.
|
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||||
if original.Token != "root" {
|
})
|
||||||
t.Fatalf("bad token: %s", original.Token)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now try restrictive filtering.
|
t.Run("permissive filtering", func(t *testing.T) {
|
||||||
filt = newACLFilter(acl.DenyAll(), nil)
|
require := require.New(t)
|
||||||
filt.filterPreparedQueries(&queries)
|
|
||||||
if len(queries) != 0 {
|
list := makeList()
|
||||||
t.Fatalf("bad: %#v", queries)
|
queryWithToken := list.Queries[2]
|
||||||
}
|
|
||||||
|
filterACLWithAuthorizer(logger, acl.AllowAll(), list)
|
||||||
|
|
||||||
|
// Check the un-named query is filtered out.
|
||||||
|
require.Len(list.Queries, 2)
|
||||||
|
|
||||||
|
// Check the token is redacted.
|
||||||
|
require.Equal(redactedToken, list.Queries[1].Token)
|
||||||
|
|
||||||
|
// Check the original object is unmodified.
|
||||||
|
require.Equal("root", queryWithToken.Token)
|
||||||
|
|
||||||
|
// ResultsFilteredByACLs should not include un-named queries, which are only
|
||||||
|
// readable by a management token.
|
||||||
|
require.False(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("limited access", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
policy, err := acl.NewPolicyFromSource(`
|
||||||
|
query "query-with-a-token" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`, acl.SyntaxLegacy, nil, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
authz, err := acl.NewPolicyAuthorizerWithDefaults(acl.DenyAll(), []*acl.Policy{policy}, nil)
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, authz, list)
|
||||||
|
|
||||||
|
// Check we only get the query we have access to.
|
||||||
|
require.Len(list.Queries, 1)
|
||||||
|
|
||||||
|
// Check the token is redacted.
|
||||||
|
require.Equal(redactedToken, list.Queries[0].Token)
|
||||||
|
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("restrictive filtering", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
list := makeList()
|
||||||
|
filterACLWithAuthorizer(logger, acl.DenyAll(), list)
|
||||||
|
|
||||||
|
require.Empty(list.Queries)
|
||||||
|
require.True(list.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestACL_filterServiceList(t *testing.T) {
|
func TestACL_filterServiceList(t *testing.T) {
|
||||||
|
@ -3622,7 +4020,7 @@ func TestACLResolver_AgentMaster(t *testing.T) {
|
||||||
cfg.DisableDuration = 0
|
cfg.DisableDuration = 0
|
||||||
})
|
})
|
||||||
|
|
||||||
tokens.UpdateAgentMasterToken("9a184a11-5599-459e-b71a-550e5f9a5a23", token.TokenSourceConfig)
|
tokens.UpdateAgentRecoveryToken("9a184a11-5599-459e-b71a-550e5f9a5a23", token.TokenSourceConfig)
|
||||||
|
|
||||||
ident, authz, err := r.ResolveTokenToIdentityAndAuthorizer("9a184a11-5599-459e-b71a-550e5f9a5a23")
|
ident, authz, err := r.ResolveTokenToIdentityAndAuthorizer("9a184a11-5599-459e-b71a-550e5f9a5a23")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -44,7 +44,7 @@ func testACLTokenReap_Primary(t *testing.T, local, global bool) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
c.ACLTokenMinExpirationTTL = 10 * time.Millisecond
|
||||||
c.ACLTokenMaxExpirationTTL = 8 * time.Second
|
c.ACLTokenMaxExpirationTTL = 8 * time.Second
|
||||||
})
|
})
|
||||||
|
|
|
@ -71,76 +71,8 @@ type Catalog struct {
|
||||||
logger hclog.Logger
|
logger hclog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
// nodePreApply does the verification of a node before it is applied to Raft.
|
// Register a service and/or check(s) in a node, creating the node if it doesn't exist.
|
||||||
func nodePreApply(nodeName, nodeID string) error {
|
// It is valid to pass no service or checks to simply create the node itself.
|
||||||
if nodeName == "" {
|
|
||||||
return fmt.Errorf("Must provide node")
|
|
||||||
}
|
|
||||||
if nodeID != "" {
|
|
||||||
if _, err := uuid.ParseUUID(nodeID); err != nil {
|
|
||||||
return fmt.Errorf("Bad node ID: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func servicePreApply(service *structs.NodeService, authz acl.Authorizer, authzCtxFill func(*acl.AuthorizerContext)) error {
|
|
||||||
// Validate the service. This is in addition to the below since
|
|
||||||
// the above just hasn't been moved over yet. We should move it over
|
|
||||||
// in time.
|
|
||||||
if err := service.Validate(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no service id, but service name, use default
|
|
||||||
if service.ID == "" && service.Service != "" {
|
|
||||||
service.ID = service.Service
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify ServiceName provided if ID.
|
|
||||||
if service.ID != "" && service.Service == "" {
|
|
||||||
return fmt.Errorf("Must provide service name with ID")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the service address here and in the agent endpoint
|
|
||||||
// since service registration isn't synchronous.
|
|
||||||
if ipaddr.IsAny(service.Address) {
|
|
||||||
return fmt.Errorf("Invalid service address")
|
|
||||||
}
|
|
||||||
|
|
||||||
var authzContext acl.AuthorizerContext
|
|
||||||
authzCtxFill(&authzContext)
|
|
||||||
|
|
||||||
// Apply the ACL policy if any. The 'consul' service is excluded
|
|
||||||
// since it is managed automatically internally (that behavior
|
|
||||||
// is going away after version 0.8). We check this same policy
|
|
||||||
// later if version 0.8 is enabled, so we can eventually just
|
|
||||||
// delete this and do all the ACL checks down there.
|
|
||||||
if service.Service != structs.ConsulServiceName {
|
|
||||||
if authz.ServiceWrite(service.Service, &authzContext) != acl.Allow {
|
|
||||||
return acl.ErrPermissionDenied
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proxies must have write permission on their destination
|
|
||||||
if service.Kind == structs.ServiceKindConnectProxy {
|
|
||||||
if authz.ServiceWrite(service.Proxy.DestinationServiceName, &authzContext) != acl.Allow {
|
|
||||||
return acl.ErrPermissionDenied
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkPreApply does the verification of a check before it is applied to Raft.
|
|
||||||
func checkPreApply(check *structs.HealthCheck) {
|
|
||||||
if check.CheckID == "" && check.Name != "" {
|
|
||||||
check.CheckID = types.CheckID(check.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register is used register that a node is providing a given service.
|
|
||||||
func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error {
|
func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error {
|
||||||
if done, err := c.srv.ForwardRPC("Catalog.Register", args, reply); done {
|
if done, err := c.srv.ForwardRPC("Catalog.Register", args, reply); done {
|
||||||
return err
|
return err
|
||||||
|
@ -212,6 +144,75 @@ func (c *Catalog) Register(args *structs.RegisterRequest, reply *struct{}) error
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// nodePreApply does the verification of a node before it is applied to Raft.
|
||||||
|
func nodePreApply(nodeName, nodeID string) error {
|
||||||
|
if nodeName == "" {
|
||||||
|
return fmt.Errorf("Must provide node")
|
||||||
|
}
|
||||||
|
if nodeID != "" {
|
||||||
|
if _, err := uuid.ParseUUID(nodeID); err != nil {
|
||||||
|
return fmt.Errorf("Bad node ID: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func servicePreApply(service *structs.NodeService, authz acl.Authorizer, authzCtxFill func(*acl.AuthorizerContext)) error {
|
||||||
|
// Validate the service. This is in addition to the below since
|
||||||
|
// the above just hasn't been moved over yet. We should move it over
|
||||||
|
// in time.
|
||||||
|
if err := service.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no service id, but service name, use default
|
||||||
|
if service.ID == "" && service.Service != "" {
|
||||||
|
service.ID = service.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify ServiceName provided if ID.
|
||||||
|
if service.ID != "" && service.Service == "" {
|
||||||
|
return fmt.Errorf("Must provide service name with ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the service address here and in the agent endpoint
|
||||||
|
// since service registration isn't synchronous.
|
||||||
|
if ipaddr.IsAny(service.Address) {
|
||||||
|
return fmt.Errorf("Invalid service address")
|
||||||
|
}
|
||||||
|
|
||||||
|
var authzContext acl.AuthorizerContext
|
||||||
|
authzCtxFill(&authzContext)
|
||||||
|
|
||||||
|
// Apply the ACL policy if any. The 'consul' service is excluded
|
||||||
|
// since it is managed automatically internally (that behavior
|
||||||
|
// is going away after version 0.8). We check this same policy
|
||||||
|
// later if version 0.8 is enabled, so we can eventually just
|
||||||
|
// delete this and do all the ACL checks down there.
|
||||||
|
if service.Service != structs.ConsulServiceName {
|
||||||
|
if authz.ServiceWrite(service.Service, &authzContext) != acl.Allow {
|
||||||
|
return acl.ErrPermissionDenied
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Proxies must have write permission on their destination
|
||||||
|
if service.Kind == structs.ServiceKindConnectProxy {
|
||||||
|
if authz.ServiceWrite(service.Proxy.DestinationServiceName, &authzContext) != acl.Allow {
|
||||||
|
return acl.ErrPermissionDenied
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkPreApply does the verification of a check before it is applied to Raft.
|
||||||
|
func checkPreApply(check *structs.HealthCheck) {
|
||||||
|
if check.CheckID == "" && check.Name != "" {
|
||||||
|
check.CheckID = types.CheckID(check.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// vetRegisterWithACL applies the given ACL's policy to the catalog update and
|
// vetRegisterWithACL applies the given ACL's policy to the catalog update and
|
||||||
// determines if it is allowed. Since the catalog register request is so
|
// determines if it is allowed. Since the catalog register request is so
|
||||||
// dynamic, this is a pretty complex algorithm and was worth breaking out of the
|
// dynamic, this is a pretty complex algorithm and was worth breaking out of the
|
||||||
|
@ -330,7 +331,13 @@ func vetRegisterWithACL(
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deregister is used to remove a service registration for a given node.
|
// Deregister a service or check in a node, or the entire node itself.
|
||||||
|
//
|
||||||
|
// If a ServiceID is provided in the request, any associated Checks
|
||||||
|
// with that service are also deregistered.
|
||||||
|
//
|
||||||
|
// If a ServiceID or CheckID is not provided in the request, the entire
|
||||||
|
// node is deregistered.
|
||||||
func (c *Catalog) Deregister(args *structs.DeregisterRequest, reply *struct{}) error {
|
func (c *Catalog) Deregister(args *structs.DeregisterRequest, reply *struct{}) error {
|
||||||
if done, err := c.srv.ForwardRPC("Catalog.Deregister", args, reply); done {
|
if done, err := c.srv.ForwardRPC("Catalog.Deregister", args, reply); done {
|
||||||
return err
|
return err
|
||||||
|
@ -458,7 +465,7 @@ func (c *Catalog) ListDatacenters(args *structs.DatacentersRequest, reply *[]str
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListNodes is used to query the nodes in a DC
|
// ListNodes is used to query the nodes in a DC.
|
||||||
func (c *Catalog) ListNodes(args *structs.DCSpecificRequest, reply *structs.IndexedNodes) error {
|
func (c *Catalog) ListNodes(args *structs.DCSpecificRequest, reply *structs.IndexedNodes) error {
|
||||||
if done, err := c.srv.ForwardRPC("Catalog.ListNodes", args, reply); done {
|
if done, err := c.srv.ForwardRPC("Catalog.ListNodes", args, reply); done {
|
||||||
return err
|
return err
|
||||||
|
@ -509,7 +516,8 @@ func isUnmodified(opts structs.QueryOptions, index uint64) bool {
|
||||||
return opts.AllowNotModifiedResponse && opts.MinQueryIndex > 0 && opts.MinQueryIndex == index
|
return opts.AllowNotModifiedResponse && opts.MinQueryIndex > 0 && opts.MinQueryIndex == index
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListServices is used to query the services in a DC
|
// ListServices is used to query the services in a DC.
|
||||||
|
// Returns services as a map of service names to available tags.
|
||||||
func (c *Catalog) ListServices(args *structs.DCSpecificRequest, reply *structs.IndexedServices) error {
|
func (c *Catalog) ListServices(args *structs.DCSpecificRequest, reply *structs.IndexedServices) error {
|
||||||
if done, err := c.srv.ForwardRPC("Catalog.ListServices", args, reply); done {
|
if done, err := c.srv.ForwardRPC("Catalog.ListServices", args, reply); done {
|
||||||
return err
|
return err
|
||||||
|
@ -552,6 +560,8 @@ func (c *Catalog) ListServices(args *structs.DCSpecificRequest, reply *structs.I
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ServiceList is used to query the services in a DC.
|
||||||
|
// Returns services as a list of ServiceNames.
|
||||||
func (c *Catalog) ServiceList(args *structs.DCSpecificRequest, reply *structs.IndexedServiceList) error {
|
func (c *Catalog) ServiceList(args *structs.DCSpecificRequest, reply *structs.IndexedServiceList) error {
|
||||||
if done, err := c.srv.ForwardRPC("Catalog.ServiceList", args, reply); done {
|
if done, err := c.srv.ForwardRPC("Catalog.ServiceList", args, reply); done {
|
||||||
return err
|
return err
|
||||||
|
@ -570,7 +580,7 @@ func (c *Catalog) ServiceList(args *structs.DCSpecificRequest, reply *structs.In
|
||||||
&args.QueryOptions,
|
&args.QueryOptions,
|
||||||
&reply.QueryMeta,
|
&reply.QueryMeta,
|
||||||
func(ws memdb.WatchSet, state *state.Store) error {
|
func(ws memdb.WatchSet, state *state.Store) error {
|
||||||
index, services, err := state.ServiceList(ws, nil, &args.EnterpriseMeta)
|
index, services, err := state.ServiceList(ws, &args.EnterpriseMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -581,7 +591,7 @@ func (c *Catalog) ServiceList(args *structs.DCSpecificRequest, reply *structs.In
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceNodes returns all the nodes registered as part of a service
|
// ServiceNodes returns all the nodes registered as part of a service.
|
||||||
func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *structs.IndexedServiceNodes) error {
|
func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *structs.IndexedServiceNodes) error {
|
||||||
if done, err := c.srv.ForwardRPC("Catalog.ServiceNodes", args, reply); done {
|
if done, err := c.srv.ForwardRPC("Catalog.ServiceNodes", args, reply); done {
|
||||||
return err
|
return err
|
||||||
|
@ -721,7 +731,8 @@ func (c *Catalog) ServiceNodes(args *structs.ServiceSpecificRequest, reply *stru
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeServices returns all the services registered as part of a node
|
// NodeServices returns all the services registered as part of a node.
|
||||||
|
// Returns NodeServices as a map of service IDs to services.
|
||||||
func (c *Catalog) NodeServices(args *structs.NodeSpecificRequest, reply *structs.IndexedNodeServices) error {
|
func (c *Catalog) NodeServices(args *structs.NodeSpecificRequest, reply *structs.IndexedNodeServices) error {
|
||||||
if done, err := c.srv.ForwardRPC("Catalog.NodeServices", args, reply); done {
|
if done, err := c.srv.ForwardRPC("Catalog.NodeServices", args, reply); done {
|
||||||
return err
|
return err
|
||||||
|
@ -776,6 +787,8 @@ func (c *Catalog) NodeServices(args *structs.NodeSpecificRequest, reply *structs
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NodeServiceList returns all the services registered as part of a node.
|
||||||
|
// Returns NodeServices as a list of services.
|
||||||
func (c *Catalog) NodeServiceList(args *structs.NodeSpecificRequest, reply *structs.IndexedNodeServiceList) error {
|
func (c *Catalog) NodeServiceList(args *structs.NodeSpecificRequest, reply *structs.IndexedNodeServiceList) error {
|
||||||
if done, err := c.srv.ForwardRPC("Catalog.NodeServiceList", args, reply); done {
|
if done, err := c.srv.ForwardRPC("Catalog.NodeServiceList", args, reply); done {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -184,7 +184,7 @@ func TestCatalog_Register_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -452,7 +452,7 @@ func TestCatalog_Register_ConnectProxy_ACLDestinationServiceName(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -570,7 +570,7 @@ func TestCatalog_Deregister_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1297,7 +1297,7 @@ func TestCatalog_ListNodes_ACLFilter(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -2409,7 +2409,7 @@ func TestCatalog_ListServiceNodes_ConnectProxy_ACL(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -2699,7 +2699,7 @@ func testACLFilterServer(t *testing.T) (dir, token string, srv *Server, codec rp
|
||||||
dir, srv = testServerWithConfig(t, func(c *Config) {
|
dir, srv = testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -2855,7 +2855,7 @@ func TestCatalog_NodeServices_ACL(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -3258,7 +3258,7 @@ func TestCatalog_GatewayServices_ACLFiltering(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -3970,7 +3970,7 @@ func TestCatalog_VirtualIPForService_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
c.Build = "1.11.0"
|
c.Build = "1.11.0"
|
||||||
})
|
})
|
||||||
|
|
|
@ -180,10 +180,10 @@ type Config struct {
|
||||||
// ACLEnabled is used to enable ACLs
|
// ACLEnabled is used to enable ACLs
|
||||||
ACLsEnabled bool
|
ACLsEnabled bool
|
||||||
|
|
||||||
// ACLMasterToken is used to bootstrap the ACL system. It should be specified
|
// ACLInitialManagementToken is used to bootstrap the ACL system. It should be specified
|
||||||
// on the servers in the PrimaryDatacenter. When the leader comes online, it ensures
|
// on the servers in the PrimaryDatacenter. When the leader comes online, it ensures
|
||||||
// that the Master token is available. This provides the initial token.
|
// that the initial management token is available. This provides the initial token.
|
||||||
ACLMasterToken string
|
ACLInitialManagementToken string
|
||||||
|
|
||||||
// ACLTokenReplication is used to enabled token replication.
|
// ACLTokenReplication is used to enabled token replication.
|
||||||
//
|
//
|
||||||
|
@ -391,6 +391,8 @@ type Config struct {
|
||||||
|
|
||||||
RPCConfig RPCConfig
|
RPCConfig RPCConfig
|
||||||
|
|
||||||
|
RaftBoltDBConfig RaftBoltDBConfig
|
||||||
|
|
||||||
// Embedded Consul Enterprise specific configuration
|
// Embedded Consul Enterprise specific configuration
|
||||||
*EnterpriseConfig
|
*EnterpriseConfig
|
||||||
}
|
}
|
||||||
|
@ -603,3 +605,7 @@ type ReloadableConfig struct {
|
||||||
RaftSnapshotInterval time.Duration
|
RaftSnapshotInterval time.Duration
|
||||||
RaftTrailingLogs int
|
RaftTrailingLogs int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RaftBoltDBConfig struct {
|
||||||
|
NoFreelistSync bool
|
||||||
|
}
|
||||||
|
|
|
@ -594,10 +594,10 @@ func (c *ConfigEntry) ResolveServiceConfig(args *structs.ServiceConfigRequest, r
|
||||||
}
|
}
|
||||||
|
|
||||||
func gateWriteToSecondary(targetDC, localDC, primaryDC, kind string) error {
|
func gateWriteToSecondary(targetDC, localDC, primaryDC, kind string) error {
|
||||||
// Partition exports are gated from interactions from secondary DCs
|
// ExportedServices entries are gated from interactions from secondary DCs
|
||||||
// because non-default partitions cannot be created in secondaries
|
// because non-default partitions cannot be created in secondaries
|
||||||
// and services cannot be exported to another datacenter.
|
// and services cannot be exported to another datacenter.
|
||||||
if kind != structs.PartitionExports {
|
if kind != structs.ExportedServices {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if localDC == "" {
|
if localDC == "" {
|
||||||
|
@ -611,10 +611,10 @@ func gateWriteToSecondary(targetDC, localDC, primaryDC, kind string) error {
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case targetDC == "" && localDC != primaryDC:
|
case targetDC == "" && localDC != primaryDC:
|
||||||
return fmt.Errorf("partition-exports writes in secondary datacenters must target the primary datacenter explicitly.")
|
return fmt.Errorf("exported-services writes in secondary datacenters must target the primary datacenter explicitly.")
|
||||||
|
|
||||||
case targetDC != "" && targetDC != primaryDC:
|
case targetDC != "" && targetDC != primaryDC:
|
||||||
return fmt.Errorf("partition-exports writes must not target secondary datacenters.")
|
return fmt.Errorf("exported-services writes must not target secondary datacenters.")
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -155,7 +155,7 @@ func TestConfigEntry_Apply_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -271,7 +271,7 @@ func TestConfigEntry_Get_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -471,7 +471,7 @@ func TestConfigEntry_List_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -545,7 +545,7 @@ func TestConfigEntry_ListAll_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -750,7 +750,7 @@ func TestConfigEntry_Delete_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1959,7 +1959,7 @@ func TestConfigEntry_ResolveServiceConfig_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -2093,7 +2093,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
||||||
targetDC: "",
|
targetDC: "",
|
||||||
localDC: "dc1",
|
localDC: "dc1",
|
||||||
primaryDC: "",
|
primaryDC: "",
|
||||||
kind: structs.PartitionExports,
|
kind: structs.ExportedServices,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2102,7 +2102,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
||||||
targetDC: "",
|
targetDC: "",
|
||||||
localDC: "dc1",
|
localDC: "dc1",
|
||||||
primaryDC: "dc1",
|
primaryDC: "dc1",
|
||||||
kind: structs.PartitionExports,
|
kind: structs.ExportedServices,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2111,7 +2111,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
||||||
targetDC: "dc1",
|
targetDC: "dc1",
|
||||||
localDC: "dc1",
|
localDC: "dc1",
|
||||||
primaryDC: "dc1",
|
primaryDC: "dc1",
|
||||||
kind: structs.PartitionExports,
|
kind: structs.ExportedServices,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -2120,7 +2120,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
||||||
targetDC: "dc2",
|
targetDC: "dc2",
|
||||||
localDC: "dc1",
|
localDC: "dc1",
|
||||||
primaryDC: "",
|
primaryDC: "",
|
||||||
kind: structs.PartitionExports,
|
kind: structs.ExportedServices,
|
||||||
},
|
},
|
||||||
wantErr: "writes must not target secondary datacenters",
|
wantErr: "writes must not target secondary datacenters",
|
||||||
},
|
},
|
||||||
|
@ -2130,7 +2130,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
||||||
targetDC: "dc2",
|
targetDC: "dc2",
|
||||||
localDC: "dc1",
|
localDC: "dc1",
|
||||||
primaryDC: "dc1",
|
primaryDC: "dc1",
|
||||||
kind: structs.PartitionExports,
|
kind: structs.ExportedServices,
|
||||||
},
|
},
|
||||||
wantErr: "writes must not target secondary datacenters",
|
wantErr: "writes must not target secondary datacenters",
|
||||||
},
|
},
|
||||||
|
@ -2140,7 +2140,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
||||||
targetDC: "dc2",
|
targetDC: "dc2",
|
||||||
localDC: "dc2",
|
localDC: "dc2",
|
||||||
primaryDC: "dc1",
|
primaryDC: "dc1",
|
||||||
kind: structs.PartitionExports,
|
kind: structs.ExportedServices,
|
||||||
},
|
},
|
||||||
wantErr: "writes must not target secondary datacenters",
|
wantErr: "writes must not target secondary datacenters",
|
||||||
},
|
},
|
||||||
|
@ -2150,7 +2150,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
||||||
targetDC: "",
|
targetDC: "",
|
||||||
localDC: "dc2",
|
localDC: "dc2",
|
||||||
primaryDC: "dc1",
|
primaryDC: "dc1",
|
||||||
kind: structs.PartitionExports,
|
kind: structs.ExportedServices,
|
||||||
},
|
},
|
||||||
wantErr: "must target the primary datacenter explicitly",
|
wantErr: "must target the primary datacenter explicitly",
|
||||||
},
|
},
|
||||||
|
@ -2158,7 +2158,7 @@ func Test_gateWriteToSecondary(t *testing.T) {
|
||||||
name: "empty local DC",
|
name: "empty local DC",
|
||||||
args: args{
|
args: args{
|
||||||
localDC: "",
|
localDC: "",
|
||||||
kind: structs.PartitionExports,
|
kind: structs.ExportedServices,
|
||||||
},
|
},
|
||||||
wantErr: "unknown local datacenter",
|
wantErr: "unknown local datacenter",
|
||||||
},
|
},
|
||||||
|
@ -2179,7 +2179,7 @@ func Test_gateWriteToSecondary_AllowedKinds(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, kind := range structs.AllConfigEntryKinds {
|
for _, kind := range structs.AllConfigEntryKinds {
|
||||||
if kind == structs.PartitionExports {
|
if kind == structs.ExportedServices {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,8 +92,8 @@ func (s *Server) reconcileLocalConfig(ctx context.Context, configs []structs.Con
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
for i, entry := range configs {
|
for i, entry := range configs {
|
||||||
// Partition exports only apply to the primary datacenter.
|
// Exported services only apply to the primary datacenter.
|
||||||
if entry.GetKind() == structs.PartitionExports {
|
if entry.GetKind() == structs.ExportedServices {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
req := structs.ConfigEntryRequest{
|
req := structs.ConfigEntryRequest{
|
||||||
|
|
|
@ -92,107 +92,6 @@ func TestReplication_ConfigSort(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReplication_DisallowedConfigEntries(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("too slow for testing.Short")
|
|
||||||
}
|
|
||||||
|
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
|
||||||
c.PrimaryDatacenter = "dc1"
|
|
||||||
})
|
|
||||||
defer os.RemoveAll(dir1)
|
|
||||||
defer s1.Shutdown()
|
|
||||||
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
||||||
client := rpcClient(t, s1)
|
|
||||||
defer client.Close()
|
|
||||||
|
|
||||||
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
|
||||||
c.Datacenter = "dc2"
|
|
||||||
c.PrimaryDatacenter = "dc1"
|
|
||||||
c.ConfigReplicationRate = 100
|
|
||||||
c.ConfigReplicationBurst = 100
|
|
||||||
c.ConfigReplicationApplyLimit = 1000000
|
|
||||||
})
|
|
||||||
testrpc.WaitForLeader(t, s2.RPC, "dc2")
|
|
||||||
defer os.RemoveAll(dir2)
|
|
||||||
defer s2.Shutdown()
|
|
||||||
|
|
||||||
// Try to join.
|
|
||||||
joinWAN(t, s2, s1)
|
|
||||||
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
||||||
testrpc.WaitForLeader(t, s1.RPC, "dc2")
|
|
||||||
|
|
||||||
args := []structs.ConfigEntryRequest{
|
|
||||||
{
|
|
||||||
Datacenter: "dc1",
|
|
||||||
Op: structs.ConfigEntryUpsert,
|
|
||||||
Entry: &structs.ServiceConfigEntry{
|
|
||||||
Kind: structs.ServiceDefaults,
|
|
||||||
Name: "foo",
|
|
||||||
Protocol: "http2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Datacenter: "dc1",
|
|
||||||
Op: structs.ConfigEntryUpsert,
|
|
||||||
Entry: &structs.PartitionExportsConfigEntry{
|
|
||||||
Name: "default",
|
|
||||||
Services: []structs.ExportedService{
|
|
||||||
{
|
|
||||||
Name: structs.WildcardSpecifier,
|
|
||||||
Consumers: []structs.ServiceConsumer{
|
|
||||||
{
|
|
||||||
Partition: "non-default",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Datacenter: "dc1",
|
|
||||||
Op: structs.ConfigEntryUpsert,
|
|
||||||
Entry: &structs.ProxyConfigEntry{
|
|
||||||
Kind: structs.ProxyDefaults,
|
|
||||||
Name: "global",
|
|
||||||
Config: map[string]interface{}{
|
|
||||||
"Protocol": "http",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Datacenter: "dc1",
|
|
||||||
Op: structs.ConfigEntryUpsert,
|
|
||||||
Entry: &structs.MeshConfigEntry{
|
|
||||||
TransparentProxy: structs.TransparentProxyMeshConfig{
|
|
||||||
MeshDestinationsOnly: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for _, arg := range args {
|
|
||||||
out := false
|
|
||||||
require.NoError(t, s1.RPC("ConfigEntry.Apply", &arg, &out))
|
|
||||||
}
|
|
||||||
|
|
||||||
retry.Run(t, func(r *retry.R) {
|
|
||||||
_, local, err := s2.fsm.State().ConfigEntries(nil, structs.ReplicationEnterpriseMeta())
|
|
||||||
require.NoError(r, err)
|
|
||||||
require.Len(r, local, 3)
|
|
||||||
|
|
||||||
localKinds := make([]string, 0)
|
|
||||||
for _, entry := range local {
|
|
||||||
localKinds = append(localKinds, entry.GetKind())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should have all inserted kinds except for partition-exports.
|
|
||||||
expectKinds := []string{
|
|
||||||
structs.ProxyDefaults, structs.ServiceDefaults, structs.MeshConfig,
|
|
||||||
}
|
|
||||||
require.ElementsMatch(r, expectKinds, localKinds)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestReplication_ConfigEntries(t *testing.T) {
|
func TestReplication_ConfigEntries(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too slow for testing.Short")
|
t.Skip("too slow for testing.Short")
|
||||||
|
|
|
@ -163,7 +163,7 @@ func TestConnectCAConfig_GetSet_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = TestDefaultMasterToken
|
c.ACLInitialManagementToken = TestDefaultMasterToken
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1108,7 +1108,7 @@ func TestConnectCASignValidation(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
|
|
@ -191,7 +191,7 @@ func TestCoordinate_Update_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -349,7 +349,7 @@ func TestCoordinate_ListNodes_ACLFilter(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -524,7 +524,7 @@ func TestCoordinate_Node_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
|
|
@ -26,7 +26,7 @@ func TestDiscoveryChainEndpoint_Get(t *testing.T) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
|
|
@ -185,7 +185,7 @@ type customizationMarkers struct {
|
||||||
// the String() method on the type itself. It is this way to be more
|
// the String() method on the type itself. It is this way to be more
|
||||||
// consistent with other string ids within the discovery chain.
|
// consistent with other string ids within the discovery chain.
|
||||||
func serviceIDString(sid structs.ServiceID) string {
|
func serviceIDString(sid structs.ServiceID) string {
|
||||||
return fmt.Sprintf("%s.%s", sid.ID, sid.NamespaceOrDefault())
|
return fmt.Sprintf("%s.%s.%s", sid.ID, sid.NamespaceOrDefault(), sid.PartitionOrDefault())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *customizationMarkers) IsZero() bool {
|
func (m *customizationMarkers) IsZero() bool {
|
||||||
|
@ -213,10 +213,10 @@ func (c *compiler) recordServiceProtocol(sid structs.ServiceID) error {
|
||||||
if serviceDefault := c.entries.GetService(sid); serviceDefault != nil {
|
if serviceDefault := c.entries.GetService(sid); serviceDefault != nil {
|
||||||
return c.recordProtocol(sid, serviceDefault.Protocol)
|
return c.recordProtocol(sid, serviceDefault.Protocol)
|
||||||
}
|
}
|
||||||
if c.entries.GlobalProxy != nil {
|
if proxyDefault := c.entries.GetProxyDefaults(sid.PartitionOrDefault()); proxyDefault != nil {
|
||||||
var cfg proxyConfig
|
var cfg proxyConfig
|
||||||
// Ignore errors and fallback on defaults if it does happen.
|
// Ignore errors and fallback on defaults if it does happen.
|
||||||
_ = mapstructure.WeakDecode(c.entries.GlobalProxy.Config, &cfg)
|
_ = mapstructure.WeakDecode(proxyDefault.Config, &cfg)
|
||||||
if cfg.Protocol != "" {
|
if cfg.Protocol != "" {
|
||||||
return c.recordProtocol(sid, cfg.Protocol)
|
return c.recordProtocol(sid, cfg.Protocol)
|
||||||
}
|
}
|
||||||
|
@ -567,11 +567,12 @@ func (c *compiler) assembleChain() error {
|
||||||
dest = &structs.ServiceRouteDestination{
|
dest = &structs.ServiceRouteDestination{
|
||||||
Service: c.serviceName,
|
Service: c.serviceName,
|
||||||
Namespace: router.NamespaceOrDefault(),
|
Namespace: router.NamespaceOrDefault(),
|
||||||
|
Partition: router.PartitionOrDefault(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
svc := defaultIfEmpty(dest.Service, c.serviceName)
|
svc := defaultIfEmpty(dest.Service, c.serviceName)
|
||||||
destNamespace := defaultIfEmpty(dest.Namespace, router.NamespaceOrDefault())
|
destNamespace := defaultIfEmpty(dest.Namespace, router.NamespaceOrDefault())
|
||||||
destPartition := router.PartitionOrDefault()
|
destPartition := defaultIfEmpty(dest.Partition, router.PartitionOrDefault())
|
||||||
|
|
||||||
// Check to see if the destination is eligible for splitting.
|
// Check to see if the destination is eligible for splitting.
|
||||||
var (
|
var (
|
||||||
|
@ -602,7 +603,7 @@ func (c *compiler) assembleChain() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultRoute := &structs.DiscoveryRoute{
|
defaultRoute := &structs.DiscoveryRoute{
|
||||||
Definition: newDefaultServiceRoute(router.Name, router.NamespaceOrDefault()),
|
Definition: newDefaultServiceRoute(router.Name, router.NamespaceOrDefault(), router.PartitionOrDefault()),
|
||||||
NextNode: defaultDestinationNode.MapKey(),
|
NextNode: defaultDestinationNode.MapKey(),
|
||||||
}
|
}
|
||||||
routeNode.Routes = append(routeNode.Routes, defaultRoute)
|
routeNode.Routes = append(routeNode.Routes, defaultRoute)
|
||||||
|
@ -613,7 +614,7 @@ func (c *compiler) assembleChain() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDefaultServiceRoute(serviceName string, namespace string) *structs.ServiceRoute {
|
func newDefaultServiceRoute(serviceName, namespace, partition string) *structs.ServiceRoute {
|
||||||
return &structs.ServiceRoute{
|
return &structs.ServiceRoute{
|
||||||
Match: &structs.ServiceRouteMatch{
|
Match: &structs.ServiceRouteMatch{
|
||||||
HTTP: &structs.ServiceRouteHTTPMatch{
|
HTTP: &structs.ServiceRouteHTTPMatch{
|
||||||
|
@ -623,6 +624,7 @@ func newDefaultServiceRoute(serviceName string, namespace string) *structs.Servi
|
||||||
Destination: &structs.ServiceRouteDestination{
|
Destination: &structs.ServiceRouteDestination{
|
||||||
Service: serviceName,
|
Service: serviceName,
|
||||||
Namespace: namespace,
|
Namespace: namespace,
|
||||||
|
Partition: partition,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -836,7 +838,7 @@ RESOLVE_AGAIN:
|
||||||
target,
|
target,
|
||||||
redirect.Service,
|
redirect.Service,
|
||||||
redirect.ServiceSubset,
|
redirect.ServiceSubset,
|
||||||
target.Partition,
|
redirect.Partition,
|
||||||
redirect.Namespace,
|
redirect.Namespace,
|
||||||
redirect.Datacenter,
|
redirect.Datacenter,
|
||||||
)
|
)
|
||||||
|
@ -940,9 +942,9 @@ RESOLVE_AGAIN:
|
||||||
if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil {
|
if serviceDefault := c.entries.GetService(targetID); serviceDefault != nil {
|
||||||
target.MeshGateway = serviceDefault.MeshGateway
|
target.MeshGateway = serviceDefault.MeshGateway
|
||||||
}
|
}
|
||||||
|
proxyDefault := c.entries.GetProxyDefaults(targetID.PartitionOrDefault())
|
||||||
if c.entries.GlobalProxy != nil && target.MeshGateway.Mode == structs.MeshGatewayModeDefault {
|
if proxyDefault != nil && target.MeshGateway.Mode == structs.MeshGatewayModeDefault {
|
||||||
target.MeshGateway.Mode = c.entries.GlobalProxy.MeshGateway.Mode
|
target.MeshGateway.Mode = proxyDefault.MeshGateway.Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.overrideMeshGateway.Mode != structs.MeshGatewayModeDefault {
|
if c.overrideMeshGateway.Mode != structs.MeshGatewayModeDefault {
|
||||||
|
|
|
@ -158,14 +158,14 @@ func testcase_JustRouterWithDefaults() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "router:main.default",
|
StartNode: "router:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"router:main.default": {
|
"router:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Routes: []*structs.DiscoveryRoute{
|
Routes: []*structs.DiscoveryRoute{
|
||||||
{
|
{
|
||||||
Definition: newDefaultServiceRoute("main", "default"),
|
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||||
NextNode: "resolver:main.default.default.dc1",
|
NextNode: "resolver:main.default.default.dc1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -210,11 +210,11 @@ func testcase_JustRouterWithNoDestination() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "router:main.default",
|
StartNode: "router:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"router:main.default": {
|
"router:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Routes: []*structs.DiscoveryRoute{
|
Routes: []*structs.DiscoveryRoute{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceRoute{
|
Definition: &structs.ServiceRoute{
|
||||||
|
@ -227,7 +227,7 @@ func testcase_JustRouterWithNoDestination() compileTestCase {
|
||||||
NextNode: "resolver:main.default.default.dc1",
|
NextNode: "resolver:main.default.default.dc1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Definition: newDefaultServiceRoute("main", "default"),
|
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||||
NextNode: "resolver:main.default.default.dc1",
|
NextNode: "resolver:main.default.default.dc1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -270,14 +270,14 @@ func testcase_RouterWithDefaults_NoSplit_WithResolver() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "router:main.default",
|
StartNode: "router:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"router:main.default": {
|
"router:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Routes: []*structs.DiscoveryRoute{
|
Routes: []*structs.DiscoveryRoute{
|
||||||
{
|
{
|
||||||
Definition: newDefaultServiceRoute("main", "default"),
|
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||||
NextNode: "resolver:main.default.default.dc1",
|
NextNode: "resolver:main.default.default.dc1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -321,21 +321,21 @@ func testcase_RouterWithDefaults_WithNoopSplit_DefaultResolver() compileTestCase
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "router:main.default",
|
StartNode: "router:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"router:main.default": {
|
"router:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Routes: []*structs.DiscoveryRoute{
|
Routes: []*structs.DiscoveryRoute{
|
||||||
{
|
{
|
||||||
Definition: newDefaultServiceRoute("main", "default"),
|
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||||
NextNode: "splitter:main.default",
|
NextNode: "splitter:main.default.default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"splitter:main.default": {
|
"splitter:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Splits: []*structs.DiscoverySplit{
|
Splits: []*structs.DiscoverySplit{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceSplit{
|
Definition: &structs.ServiceSplit{
|
||||||
|
@ -386,21 +386,21 @@ func testcase_NoopSplit_DefaultResolver_ProtocolFromProxyDefaults() compileTestC
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "router:main.default",
|
StartNode: "router:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"router:main.default": {
|
"router:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Routes: []*structs.DiscoveryRoute{
|
Routes: []*structs.DiscoveryRoute{
|
||||||
{
|
{
|
||||||
Definition: newDefaultServiceRoute("main", "default"),
|
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||||
NextNode: "splitter:main.default",
|
NextNode: "splitter:main.default.default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"splitter:main.default": {
|
"splitter:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Splits: []*structs.DiscoverySplit{
|
Splits: []*structs.DiscoverySplit{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceSplit{
|
Definition: &structs.ServiceSplit{
|
||||||
|
@ -458,21 +458,21 @@ func testcase_RouterWithDefaults_WithNoopSplit_WithResolver() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "router:main.default",
|
StartNode: "router:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"router:main.default": {
|
"router:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Routes: []*structs.DiscoveryRoute{
|
Routes: []*structs.DiscoveryRoute{
|
||||||
{
|
{
|
||||||
Definition: newDefaultServiceRoute("main", "default"),
|
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||||
NextNode: "splitter:main.default",
|
NextNode: "splitter:main.default.default",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"splitter:main.default": {
|
"splitter:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Splits: []*structs.DiscoverySplit{
|
Splits: []*structs.DiscoverySplit{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceSplit{
|
Definition: &structs.ServiceSplit{
|
||||||
|
@ -542,18 +542,18 @@ func testcase_RouteBypassesSplit() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "router:main.default",
|
StartNode: "router:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"router:main.default": {
|
"router:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Routes: []*structs.DiscoveryRoute{
|
Routes: []*structs.DiscoveryRoute{
|
||||||
{
|
{
|
||||||
Definition: &router.Routes[0],
|
Definition: &router.Routes[0],
|
||||||
NextNode: "resolver:bypass.other.default.default.dc1",
|
NextNode: "resolver:bypass.other.default.default.dc1",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Definition: newDefaultServiceRoute("main", "default"),
|
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||||
NextNode: "resolver:main.default.default.dc1",
|
NextNode: "resolver:main.default.default.dc1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -605,11 +605,11 @@ func testcase_NoopSplit_DefaultResolver() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "splitter:main.default",
|
StartNode: "splitter:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"splitter:main.default": {
|
"splitter:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Splits: []*structs.DiscoverySplit{
|
Splits: []*structs.DiscoverySplit{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceSplit{
|
Definition: &structs.ServiceSplit{
|
||||||
|
@ -661,11 +661,11 @@ func testcase_NoopSplit_WithResolver() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "splitter:main.default",
|
StartNode: "splitter:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"splitter:main.default": {
|
"splitter:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Splits: []*structs.DiscoverySplit{
|
Splits: []*structs.DiscoverySplit{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceSplit{
|
Definition: &structs.ServiceSplit{
|
||||||
|
@ -724,11 +724,11 @@ func testcase_SubsetSplit() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "splitter:main.default",
|
StartNode: "splitter:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"splitter:main.default": {
|
"splitter:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Splits: []*structs.DiscoverySplit{
|
Splits: []*structs.DiscoverySplit{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceSplit{
|
Definition: &structs.ServiceSplit{
|
||||||
|
@ -801,11 +801,11 @@ func testcase_ServiceSplit() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "splitter:main.default",
|
StartNode: "splitter:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"splitter:main.default": {
|
"splitter:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Splits: []*structs.DiscoverySplit{
|
Splits: []*structs.DiscoverySplit{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceSplit{
|
Definition: &structs.ServiceSplit{
|
||||||
|
@ -898,11 +898,11 @@ func testcase_SplitBypassesSplit() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "splitter:main.default",
|
StartNode: "splitter:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"splitter:main.default": {
|
"splitter:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Splits: []*structs.DiscoverySplit{
|
Splits: []*structs.DiscoverySplit{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceSplit{
|
Definition: &structs.ServiceSplit{
|
||||||
|
@ -1053,13 +1053,14 @@ func testcase_DatacenterRedirect() compileTestCase {
|
||||||
|
|
||||||
func testcase_DatacenterRedirect_WithMeshGateways() compileTestCase {
|
func testcase_DatacenterRedirect_WithMeshGateways() compileTestCase {
|
||||||
entries := newEntries()
|
entries := newEntries()
|
||||||
entries.GlobalProxy = &structs.ProxyConfigEntry{
|
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
|
||||||
Kind: structs.ProxyDefaults,
|
Kind: structs.ProxyDefaults,
|
||||||
Name: structs.ProxyConfigGlobal,
|
Name: structs.ProxyConfigGlobal,
|
||||||
MeshGateway: structs.MeshGatewayConfig{
|
MeshGateway: structs.MeshGatewayConfig{
|
||||||
Mode: structs.MeshGatewayModeRemote,
|
Mode: structs.MeshGatewayModeRemote,
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
entries.AddResolvers(
|
entries.AddResolvers(
|
||||||
&structs.ServiceResolverConfigEntry{
|
&structs.ServiceResolverConfigEntry{
|
||||||
Kind: "service-resolver",
|
Kind: "service-resolver",
|
||||||
|
@ -1300,13 +1301,15 @@ func testcase_DatacenterFailover() compileTestCase {
|
||||||
|
|
||||||
func testcase_DatacenterFailover_WithMeshGateways() compileTestCase {
|
func testcase_DatacenterFailover_WithMeshGateways() compileTestCase {
|
||||||
entries := newEntries()
|
entries := newEntries()
|
||||||
entries.GlobalProxy = &structs.ProxyConfigEntry{
|
|
||||||
|
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
|
||||||
Kind: structs.ProxyDefaults,
|
Kind: structs.ProxyDefaults,
|
||||||
Name: structs.ProxyConfigGlobal,
|
Name: structs.ProxyConfigGlobal,
|
||||||
MeshGateway: structs.MeshGatewayConfig{
|
MeshGateway: structs.MeshGatewayConfig{
|
||||||
Mode: structs.MeshGatewayModeRemote,
|
Mode: structs.MeshGatewayModeRemote,
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
entries.AddResolvers(
|
entries.AddResolvers(
|
||||||
&structs.ServiceResolverConfigEntry{
|
&structs.ServiceResolverConfigEntry{
|
||||||
Kind: "service-resolver",
|
Kind: "service-resolver",
|
||||||
|
@ -1384,11 +1387,11 @@ func testcase_NoopSplit_WithDefaultSubset() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "splitter:main.default",
|
StartNode: "splitter:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"splitter:main.default": {
|
"splitter:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Splits: []*structs.DiscoverySplit{
|
Splits: []*structs.DiscoverySplit{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceSplit{
|
Definition: &structs.ServiceSplit{
|
||||||
|
@ -1446,7 +1449,8 @@ func testcase_DefaultResolver() compileTestCase {
|
||||||
|
|
||||||
func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
|
func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
|
||||||
entries := newEntries()
|
entries := newEntries()
|
||||||
entries.GlobalProxy = &structs.ProxyConfigEntry{
|
|
||||||
|
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
|
||||||
Kind: structs.ProxyDefaults,
|
Kind: structs.ProxyDefaults,
|
||||||
Name: structs.ProxyConfigGlobal,
|
Name: structs.ProxyConfigGlobal,
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
|
@ -1455,7 +1459,7 @@ func testcase_DefaultResolver_WithProxyDefaults() compileTestCase {
|
||||||
MeshGateway: structs.MeshGatewayConfig{
|
MeshGateway: structs.MeshGatewayConfig{
|
||||||
Mode: structs.MeshGatewayModeRemote,
|
Mode: structs.MeshGatewayModeRemote,
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "grpc",
|
Protocol: "grpc",
|
||||||
|
@ -1699,11 +1703,11 @@ func testcase_MultiDatacenterCanary() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "splitter:main.default",
|
StartNode: "splitter:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"splitter:main.default": {
|
"splitter:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Splits: []*structs.DiscoverySplit{
|
Splits: []*structs.DiscoverySplit{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceSplit{
|
Definition: &structs.ServiceSplit{
|
||||||
|
@ -1880,11 +1884,11 @@ func testcase_AllBellsAndWhistles() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "router:main.default",
|
StartNode: "router:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"router:main.default": {
|
"router:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeRouter,
|
Type: structs.DiscoveryGraphNodeTypeRouter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Routes: []*structs.DiscoveryRoute{
|
Routes: []*structs.DiscoveryRoute{
|
||||||
{
|
{
|
||||||
Definition: &router.Routes[0],
|
Definition: &router.Routes[0],
|
||||||
|
@ -1892,17 +1896,17 @@ func testcase_AllBellsAndWhistles() compileTestCase {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Definition: &router.Routes[1],
|
Definition: &router.Routes[1],
|
||||||
NextNode: "splitter:svc-split.default",
|
NextNode: "splitter:svc-split.default.default",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Definition: newDefaultServiceRoute("main", "default"),
|
Definition: newDefaultServiceRoute("main", "default", "default"),
|
||||||
NextNode: "resolver:default-subset.main.default.default.dc1",
|
NextNode: "resolver:default-subset.main.default.default.dc1",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"splitter:svc-split.default": {
|
"splitter:svc-split.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
Name: "svc-split.default",
|
Name: "svc-split.default.default",
|
||||||
Splits: []*structs.DiscoverySplit{
|
Splits: []*structs.DiscoverySplit{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceSplit{
|
Definition: &structs.ServiceSplit{
|
||||||
|
@ -2455,11 +2459,11 @@ func testcase_LBSplitterAndResolver() compileTestCase {
|
||||||
|
|
||||||
expect := &structs.CompiledDiscoveryChain{
|
expect := &structs.CompiledDiscoveryChain{
|
||||||
Protocol: "http",
|
Protocol: "http",
|
||||||
StartNode: "splitter:main.default",
|
StartNode: "splitter:main.default.default",
|
||||||
Nodes: map[string]*structs.DiscoveryGraphNode{
|
Nodes: map[string]*structs.DiscoveryGraphNode{
|
||||||
"splitter:main.default": {
|
"splitter:main.default.default": {
|
||||||
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
Type: structs.DiscoveryGraphNodeTypeSplitter,
|
||||||
Name: "main.default",
|
Name: "main.default.default",
|
||||||
Splits: []*structs.DiscoverySplit{
|
Splits: []*structs.DiscoverySplit{
|
||||||
{
|
{
|
||||||
Definition: &structs.ServiceSplit{
|
Definition: &structs.ServiceSplit{
|
||||||
|
@ -2642,13 +2646,13 @@ func newSimpleRoute(name string, muts ...func(*structs.ServiceRoute)) structs.Se
|
||||||
}
|
}
|
||||||
|
|
||||||
func setGlobalProxyProtocol(entries *structs.DiscoveryChainConfigEntries, protocol string) {
|
func setGlobalProxyProtocol(entries *structs.DiscoveryChainConfigEntries, protocol string) {
|
||||||
entries.GlobalProxy = &structs.ProxyConfigEntry{
|
entries.AddProxyDefaults(&structs.ProxyConfigEntry{
|
||||||
Kind: structs.ProxyDefaults,
|
Kind: structs.ProxyDefaults,
|
||||||
Name: structs.ProxyConfigGlobal,
|
Name: structs.ProxyConfigGlobal,
|
||||||
Config: map[string]interface{}{
|
Config: map[string]interface{}{
|
||||||
"protocol": protocol,
|
"protocol": protocol,
|
||||||
},
|
},
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func setServiceProtocol(entries *structs.DiscoveryChainConfigEntries, name, protocol string) {
|
func setServiceProtocol(entries *structs.DiscoveryChainConfigEntries, name, protocol string) {
|
||||||
|
|
|
@ -88,7 +88,7 @@ func (s *Server) validateEnterpriseIntentionNamespace(ns string, _ bool) error {
|
||||||
func (s *Server) setupSerfLAN(config *Config) error {
|
func (s *Server) setupSerfLAN(config *Config) error {
|
||||||
var err error
|
var err error
|
||||||
// Initialize the LAN Serf for the default network segment.
|
// Initialize the LAN Serf for the default network segment.
|
||||||
s.serfLAN, err = s.setupSerf(setupSerfOptions{
|
s.serfLAN, _, err = s.setupSerf(setupSerfOptions{
|
||||||
Config: config.SerfLANConfig,
|
Config: config.SerfLANConfig,
|
||||||
EventCh: s.eventChLAN,
|
EventCh: s.eventChLAN,
|
||||||
SnapshotPath: serfLANSnapshot,
|
SnapshotPath: serfLANSnapshot,
|
||||||
|
|
|
@ -116,7 +116,7 @@ func TestFederationState_Apply_Upsert_ACLDeny(t *testing.T) {
|
||||||
c.DisableFederationStateAntiEntropy = true
|
c.DisableFederationStateAntiEntropy = true
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -237,7 +237,7 @@ func TestFederationState_Get_ACLDeny(t *testing.T) {
|
||||||
c.DisableFederationStateAntiEntropy = true
|
c.DisableFederationStateAntiEntropy = true
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -409,7 +409,7 @@ func TestFederationState_List_ACLDeny(t *testing.T) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -425,7 +425,7 @@ func TestFederationState_List_ACLDeny(t *testing.T) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir2)
|
defer os.RemoveAll(dir2)
|
||||||
|
@ -695,7 +695,7 @@ func TestFederationState_Apply_Delete_ACLDeny(t *testing.T) {
|
||||||
c.DisableFederationStateAntiEntropy = true
|
c.DisableFederationStateAntiEntropy = true
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
|
|
@ -464,6 +464,14 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, vip, "240.0.0.2")
|
require.Equal(t, vip, "240.0.0.2")
|
||||||
|
|
||||||
|
_, serviceNames, err := fsm.state.ServiceNamesOfKind(nil, structs.ServiceKindTypical)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expect := []string{"backend", "db", "frontend", "web"}
|
||||||
|
for i, sn := range serviceNames {
|
||||||
|
require.Equal(t, expect[i], sn.Service.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
snap, err := fsm.Snapshot()
|
snap, err := fsm.Snapshot()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -690,10 +698,10 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
|
||||||
require.Len(t, roots, 2)
|
require.Len(t, roots, 2)
|
||||||
|
|
||||||
// Verify provider state is restored.
|
// Verify provider state is restored.
|
||||||
_, state, err := fsm2.state.CAProviderState("asdf")
|
_, provider, err := fsm2.state.CAProviderState("asdf")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "foo", state.PrivateKey)
|
require.Equal(t, "foo", provider.PrivateKey)
|
||||||
require.Equal(t, "bar", state.RootCert)
|
require.Equal(t, "bar", provider.RootCert)
|
||||||
|
|
||||||
// Verify CA configuration is restored.
|
// Verify CA configuration is restored.
|
||||||
_, caConf, err := fsm2.state.CAConfig(nil)
|
_, caConf, err := fsm2.state.CAConfig(nil)
|
||||||
|
@ -751,6 +759,14 @@ func TestFSM_SnapshotRestore_OSS(t *testing.T) {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, meshConfig, meshConfigEntry)
|
require.Equal(t, meshConfig, meshConfigEntry)
|
||||||
|
|
||||||
|
_, restoredServiceNames, err := fsm2.state.ServiceNamesOfKind(nil, structs.ServiceKindTypical)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
expect = []string{"backend", "db", "frontend", "web"}
|
||||||
|
for i, sn := range restoredServiceNames {
|
||||||
|
require.Equal(t, expect[i], sn.Service.Name)
|
||||||
|
}
|
||||||
|
|
||||||
// Snapshot
|
// Snapshot
|
||||||
snap, err = fsm2.Snapshot()
|
snap, err = fsm2.Snapshot()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
|
@ -983,7 +983,7 @@ func TestHealth_ServiceNodes_ConnectProxy_ACL(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1289,7 +1289,7 @@ func TestHealth_ServiceNodes_Ingress_ACL(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
|
|
@ -867,7 +867,7 @@ func TestIntentionApply_aclDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1257,7 +1257,7 @@ func TestIntentionApply_aclDelete(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1323,7 +1323,7 @@ func TestIntentionApply_aclUpdate(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1377,7 +1377,7 @@ func TestIntentionApply_aclManagement(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1422,7 +1422,7 @@ func TestIntentionApply_aclUpdateChange(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1472,7 +1472,7 @@ func TestIntentionGet_acl(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1879,7 +1879,7 @@ func TestIntentionCheck_defaultACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1915,7 +1915,7 @@ func TestIntentionCheck_defaultACLAllow(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1951,7 +1951,7 @@ func TestIntentionCheck_aclDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
|
|
@ -72,18 +72,21 @@ func (m *Internal) NodeDump(args *structs.DCSpecificRequest,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Index, reply.Dump = index, dump
|
reply.Index, reply.Dump = index, dump
|
||||||
if err := m.srv.filterACL(args.Token, reply); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
raw, err := filter.Execute(reply.Dump)
|
raw, err := filter.Execute(reply.Dump)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.Dump = raw.(structs.NodeDump)
|
reply.Dump = raw.(structs.NodeDump)
|
||||||
|
|
||||||
|
// Note: we filter the results with ACLs *after* applying the user-supplied
|
||||||
|
// bexpr filter, to ensure QueryMeta.ResultsFilteredByACLs does not include
|
||||||
|
// results that would be filtered out even if the user did have permission.
|
||||||
|
if err := m.srv.filterACL(args.Token, reply); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -114,10 +117,6 @@ func (m *Internal) ServiceDump(args *structs.ServiceDumpRequest, reply *structs.
|
||||||
}
|
}
|
||||||
reply.Nodes = nodes
|
reply.Nodes = nodes
|
||||||
|
|
||||||
if err := m.srv.filterACL(args.Token, &reply.Nodes); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get, store, and filter gateway services
|
// Get, store, and filter gateway services
|
||||||
idx, gatewayServices, err := state.DumpGatewayServices(ws)
|
idx, gatewayServices, err := state.DumpGatewayServices(ws)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -461,7 +461,7 @@ func TestInternal_NodeInfo_FilterACL(t *testing.T) {
|
||||||
QueryOptions: structs.QueryOptions{Token: token},
|
QueryOptions: structs.QueryOptions{Token: token},
|
||||||
}
|
}
|
||||||
reply := structs.IndexedNodeDump{}
|
reply := structs.IndexedNodeDump{}
|
||||||
if err := msgpackrpc.CallWithCodec(codec, "Health.NodeChecks", &opt, &reply); err != nil {
|
if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeInfo", &opt, &reply); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
for _, info := range reply.Dump {
|
for _, info := range reply.Dump {
|
||||||
|
@ -492,6 +492,10 @@ func TestInternal_NodeInfo_FilterACL(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !reply.QueryMeta.ResultsFilteredByACLs {
|
||||||
|
t.Fatal("ResultsFilteredByACLs should be true")
|
||||||
|
}
|
||||||
|
|
||||||
// We've already proven that we call the ACL filtering function so we
|
// We've already proven that we call the ACL filtering function so we
|
||||||
// test node filtering down in acl.go for node cases. This also proves
|
// test node filtering down in acl.go for node cases. This also proves
|
||||||
// that we respect the version 8 ACL flag, since the test server sets
|
// that we respect the version 8 ACL flag, since the test server sets
|
||||||
|
@ -515,7 +519,7 @@ func TestInternal_NodeDump_FilterACL(t *testing.T) {
|
||||||
QueryOptions: structs.QueryOptions{Token: token},
|
QueryOptions: structs.QueryOptions{Token: token},
|
||||||
}
|
}
|
||||||
reply := structs.IndexedNodeDump{}
|
reply := structs.IndexedNodeDump{}
|
||||||
if err := msgpackrpc.CallWithCodec(codec, "Health.NodeChecks", &opt, &reply); err != nil {
|
if err := msgpackrpc.CallWithCodec(codec, "Internal.NodeDump", &opt, &reply); err != nil {
|
||||||
t.Fatalf("err: %s", err)
|
t.Fatalf("err: %s", err)
|
||||||
}
|
}
|
||||||
for _, info := range reply.Dump {
|
for _, info := range reply.Dump {
|
||||||
|
@ -546,6 +550,10 @@ func TestInternal_NodeDump_FilterACL(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !reply.QueryMeta.ResultsFilteredByACLs {
|
||||||
|
t.Fatal("ResultsFilteredByACLs should be true")
|
||||||
|
}
|
||||||
|
|
||||||
// We've already proven that we call the ACL filtering function so we
|
// We've already proven that we call the ACL filtering function so we
|
||||||
// test node filtering down in acl.go for node cases. This also proves
|
// test node filtering down in acl.go for node cases. This also proves
|
||||||
// that we respect the version 8 ACL flag, since the test server sets
|
// that we respect the version 8 ACL flag, since the test server sets
|
||||||
|
@ -562,7 +570,7 @@ func TestInternal_EventFire_Token(t *testing.T) {
|
||||||
dir, srv := testServerWithConfig(t, func(c *Config) {
|
dir, srv := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDownPolicy = "deny"
|
c.ACLResolverSettings.ACLDownPolicy = "deny"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
|
@ -750,6 +758,217 @@ func TestInternal_ServiceDump_Kind(t *testing.T) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestInternal_ServiceDump_ACL(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("too slow for testing.Short")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
dir, s := testServerWithConfig(t, func(c *Config) {
|
||||||
|
c.PrimaryDatacenter = "dc1"
|
||||||
|
c.ACLsEnabled = true
|
||||||
|
c.ACLInitialManagementToken = "root"
|
||||||
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
|
})
|
||||||
|
defer os.RemoveAll(dir)
|
||||||
|
defer s.Shutdown()
|
||||||
|
codec := rpcClient(t, s)
|
||||||
|
defer codec.Close()
|
||||||
|
|
||||||
|
testrpc.WaitForLeader(t, s.RPC, "dc1")
|
||||||
|
|
||||||
|
registrations := []*structs.RegisterRequest{
|
||||||
|
// Service `redis` on `node1`
|
||||||
|
{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "node1",
|
||||||
|
ID: types.NodeID("e0155642-135d-4739-9853-a1ee6c9f945b"),
|
||||||
|
Address: "192.18.1.1",
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindTypical,
|
||||||
|
ID: "redis",
|
||||||
|
Service: "redis",
|
||||||
|
Port: 5678,
|
||||||
|
},
|
||||||
|
Check: &structs.HealthCheck{
|
||||||
|
Name: "redis check",
|
||||||
|
Status: api.HealthPassing,
|
||||||
|
ServiceID: "redis",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Ingress gateway `igw` on `node2`
|
||||||
|
{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Node: "node2",
|
||||||
|
ID: types.NodeID("3a9d7530-20d4-443a-98d3-c10fe78f09f4"),
|
||||||
|
Address: "192.18.1.2",
|
||||||
|
Service: &structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindIngressGateway,
|
||||||
|
ID: "igw",
|
||||||
|
Service: "igw",
|
||||||
|
},
|
||||||
|
Check: &structs.HealthCheck{
|
||||||
|
Name: "igw check",
|
||||||
|
Status: api.HealthPassing,
|
||||||
|
ServiceID: "igw",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, reg := range registrations {
|
||||||
|
reg.Token = "root"
|
||||||
|
err := msgpackrpc.CallWithCodec(codec, "Catalog.Register", reg, nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
req := structs.ConfigEntryRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
Entry: &structs.IngressGatewayConfigEntry{
|
||||||
|
Kind: structs.IngressGateway,
|
||||||
|
Name: "igw",
|
||||||
|
Listeners: []structs.IngressListener{
|
||||||
|
{
|
||||||
|
Port: 8765,
|
||||||
|
Protocol: "tcp",
|
||||||
|
Services: []structs.IngressService{
|
||||||
|
{Name: "redis"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
req.Token = "root"
|
||||||
|
|
||||||
|
var out bool
|
||||||
|
err := msgpackrpc.CallWithCodec(codec, "ConfigEntry.Apply", &req, &out)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenWithRules := func(t *testing.T, rules string) string {
|
||||||
|
t.Helper()
|
||||||
|
tok, err := upsertTestTokenWithPolicyRules(codec, "root", "dc1", rules)
|
||||||
|
require.NoError(t, err)
|
||||||
|
return tok.SecretID
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("can read all", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
token := tokenWithRules(t, `
|
||||||
|
node_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service_prefix "" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
args := structs.DCSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
QueryOptions: structs.QueryOptions{Token: token},
|
||||||
|
}
|
||||||
|
var out structs.IndexedNodesWithGateways
|
||||||
|
err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
|
||||||
|
require.NoError(err)
|
||||||
|
require.NotEmpty(out.Nodes)
|
||||||
|
require.NotEmpty(out.Gateways)
|
||||||
|
require.False(out.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be false")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("cannot read service node", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
token := tokenWithRules(t, `
|
||||||
|
node "node1" {
|
||||||
|
policy = "deny"
|
||||||
|
}
|
||||||
|
service "redis" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
args := structs.DCSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
QueryOptions: structs.QueryOptions{Token: token},
|
||||||
|
}
|
||||||
|
var out structs.IndexedNodesWithGateways
|
||||||
|
err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Empty(out.Nodes)
|
||||||
|
require.True(out.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("cannot read service", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
token := tokenWithRules(t, `
|
||||||
|
node "node1" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service "redis" {
|
||||||
|
policy = "deny"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
args := structs.DCSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
QueryOptions: structs.QueryOptions{Token: token},
|
||||||
|
}
|
||||||
|
var out structs.IndexedNodesWithGateways
|
||||||
|
err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Empty(out.Nodes)
|
||||||
|
require.True(out.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("cannot read gateway node", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
token := tokenWithRules(t, `
|
||||||
|
node "node2" {
|
||||||
|
policy = "deny"
|
||||||
|
}
|
||||||
|
service "mgw" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
args := structs.DCSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
QueryOptions: structs.QueryOptions{Token: token},
|
||||||
|
}
|
||||||
|
var out structs.IndexedNodesWithGateways
|
||||||
|
err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Empty(out.Gateways)
|
||||||
|
require.True(out.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("cannot read gateway", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
token := tokenWithRules(t, `
|
||||||
|
node "node2" {
|
||||||
|
policy = "read"
|
||||||
|
}
|
||||||
|
service "mgw" {
|
||||||
|
policy = "deny"
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
args := structs.DCSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
QueryOptions: structs.QueryOptions{Token: token},
|
||||||
|
}
|
||||||
|
var out structs.IndexedNodesWithGateways
|
||||||
|
err := msgpackrpc.CallWithCodec(codec, "Internal.ServiceDump", &args, &out)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Empty(out.Gateways)
|
||||||
|
require.True(out.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func TestInternal_GatewayServiceDump_Terminating(t *testing.T) {
|
func TestInternal_GatewayServiceDump_Terminating(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too slow for testing.Short")
|
t.Skip("too slow for testing.Short")
|
||||||
|
@ -963,7 +1182,7 @@ func TestInternal_GatewayServiceDump_Terminating_ACL(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1082,6 +1301,7 @@ func TestInternal_GatewayServiceDump_Terminating_ACL(t *testing.T) {
|
||||||
require.Equal(t, nodes[0].Node.Node, "bar")
|
require.Equal(t, nodes[0].Node.Node, "bar")
|
||||||
require.Equal(t, nodes[0].Service.Service, "db")
|
require.Equal(t, nodes[0].Service.Service, "db")
|
||||||
require.Equal(t, nodes[0].Checks[0].Status, api.HealthWarning)
|
require.Equal(t, nodes[0].Checks[0].Status, api.HealthWarning)
|
||||||
|
require.True(t, out.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInternal_GatewayServiceDump_Ingress(t *testing.T) {
|
func TestInternal_GatewayServiceDump_Ingress(t *testing.T) {
|
||||||
|
@ -1308,7 +1528,7 @@ func TestInternal_GatewayServiceDump_Ingress_ACL(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1972,7 +2192,7 @@ func TestInternal_ServiceTopology_ACL(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = TestDefaultMasterToken
|
c.ACLInitialManagementToken = TestDefaultMasterToken
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -2111,7 +2331,7 @@ func TestInternal_IntentionUpstreams_ACL(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = TestDefaultMasterToken
|
c.ACLInitialManagementToken = TestDefaultMasterToken
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
|
|
@ -84,7 +84,7 @@ func TestKVS_Apply_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -189,7 +189,7 @@ func TestKVS_Get_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -413,7 +413,7 @@ func TestKVSEndpoint_List_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -492,7 +492,7 @@ func TestKVSEndpoint_List_ACLEnableKeyListPolicy(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
c.ACLEnableKeyListPolicy = true
|
c.ACLEnableKeyListPolicy = true
|
||||||
})
|
})
|
||||||
|
@ -684,7 +684,7 @@ func TestKVSEndpoint_ListKeys_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
|
|
@ -431,28 +431,28 @@ func (s *Server) initializeACLs(ctx context.Context) error {
|
||||||
s.logger.Info("Created ACL 'global-management' policy")
|
s.logger.Info("Created ACL 'global-management' policy")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for configured master token.
|
// Check for configured initial management token.
|
||||||
if master := s.config.ACLMasterToken; len(master) > 0 {
|
if initialManagement := s.config.ACLInitialManagementToken; len(initialManagement) > 0 {
|
||||||
state := s.fsm.State()
|
state := s.fsm.State()
|
||||||
if _, err := uuid.ParseUUID(master); err != nil {
|
if _, err := uuid.ParseUUID(initialManagement); err != nil {
|
||||||
s.logger.Warn("Configuring a non-UUID master token is deprecated")
|
s.logger.Warn("Configuring a non-UUID initial management token is deprecated")
|
||||||
}
|
}
|
||||||
|
|
||||||
_, token, err := state.ACLTokenGetBySecret(nil, master, nil)
|
_, token, err := state.ACLTokenGetBySecret(nil, initialManagement, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to get master token: %v", err)
|
return fmt.Errorf("failed to get initial management token: %v", err)
|
||||||
}
|
}
|
||||||
// Ignoring expiration times to avoid an insertion collision.
|
// Ignoring expiration times to avoid an insertion collision.
|
||||||
if token == nil {
|
if token == nil {
|
||||||
accessor, err := lib.GenerateUUID(s.checkTokenUUID)
|
accessor, err := lib.GenerateUUID(s.checkTokenUUID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to generate the accessor ID for the master token: %v", err)
|
return fmt.Errorf("failed to generate the accessor ID for the initial management token: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
token := structs.ACLToken{
|
token := structs.ACLToken{
|
||||||
AccessorID: accessor,
|
AccessorID: accessor,
|
||||||
SecretID: master,
|
SecretID: initialManagement,
|
||||||
Description: "Master Token",
|
Description: "Initial Management Token",
|
||||||
Policies: []structs.ACLTokenPolicyLink{
|
Policies: []structs.ACLTokenPolicyLink{
|
||||||
{
|
{
|
||||||
ID: structs.ACLPolicyGlobalManagementID,
|
ID: structs.ACLPolicyGlobalManagementID,
|
||||||
|
@ -472,12 +472,12 @@ func (s *Server) initializeACLs(ctx context.Context) error {
|
||||||
ResetIndex: 0,
|
ResetIndex: 0,
|
||||||
}
|
}
|
||||||
if _, err := s.raftApply(structs.ACLBootstrapRequestType, &req); err == nil {
|
if _, err := s.raftApply(structs.ACLBootstrapRequestType, &req); err == nil {
|
||||||
s.logger.Info("Bootstrapped ACL master token from configuration")
|
s.logger.Info("Bootstrapped ACL initial management token from configuration")
|
||||||
done = true
|
done = true
|
||||||
} else {
|
} else {
|
||||||
if err.Error() != structs.ACLBootstrapNotAllowedErr.Error() &&
|
if err.Error() != structs.ACLBootstrapNotAllowedErr.Error() &&
|
||||||
err.Error() != structs.ACLBootstrapInvalidResetIndexErr.Error() {
|
err.Error() != structs.ACLBootstrapInvalidResetIndexErr.Error() {
|
||||||
return fmt.Errorf("failed to bootstrap master token: %v", err)
|
return fmt.Errorf("failed to bootstrap initial management token: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -489,10 +489,10 @@ func (s *Server) initializeACLs(ctx context.Context) error {
|
||||||
CAS: false,
|
CAS: false,
|
||||||
}
|
}
|
||||||
if _, err := s.raftApply(structs.ACLTokenSetRequestType, &req); err != nil {
|
if _, err := s.raftApply(structs.ACLTokenSetRequestType, &req); err != nil {
|
||||||
return fmt.Errorf("failed to create master token: %v", err)
|
return fmt.Errorf("failed to create initial management token: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Info("Created ACL master token from configuration")
|
s.logger.Info("Created ACL initial management token from configuration")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -204,7 +204,7 @@ func TestCAManager_Initialize_Secondary(t *testing.T) {
|
||||||
c.PrimaryDatacenter = "primary"
|
c.PrimaryDatacenter = "primary"
|
||||||
c.Build = "1.6.0"
|
c.Build = "1.6.0"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = masterToken
|
c.ACLInitialManagementToken = masterToken
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
c.CAConfig.Config["PrivateKeyType"] = tc.keyType
|
c.CAConfig.Config["PrivateKeyType"] = tc.keyType
|
||||||
c.CAConfig.Config["PrivateKeyBits"] = tc.keyBits
|
c.CAConfig.Config["PrivateKeyBits"] = tc.keyBits
|
||||||
|
|
|
@ -359,7 +359,7 @@ func TestLeader_FederationStateAntiEntropyPruning_ACLDeny(t *testing.T) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -373,7 +373,7 @@ func TestLeader_FederationStateAntiEntropyPruning_ACLDeny(t *testing.T) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir2)
|
defer os.RemoveAll(dir2)
|
||||||
|
|
|
@ -29,7 +29,7 @@ func TestLeader_ReplicateIntentions(t *testing.T) {
|
||||||
c.Datacenter = "dc1"
|
c.Datacenter = "dc1"
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
c.Build = "1.6.0"
|
c.Build = "1.6.0"
|
||||||
c.OverrideInitialSerfTags = func(tags map[string]string) {
|
c.OverrideInitialSerfTags = func(tags map[string]string) {
|
||||||
|
|
|
@ -31,7 +31,7 @@ func TestLeader_RegisterMember(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -106,7 +106,7 @@ func TestLeader_FailedMember(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -171,7 +171,7 @@ func TestLeader_LeftMember(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -221,7 +221,7 @@ func TestLeader_ReapMember(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -286,7 +286,7 @@ func TestLeader_CheckServersMeta(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||||
c.Bootstrap = true
|
c.Bootstrap = true
|
||||||
})
|
})
|
||||||
|
@ -296,7 +296,7 @@ func TestLeader_CheckServersMeta(t *testing.T) {
|
||||||
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||||
c.Bootstrap = false
|
c.Bootstrap = false
|
||||||
})
|
})
|
||||||
|
@ -306,7 +306,7 @@ func TestLeader_CheckServersMeta(t *testing.T) {
|
||||||
dir3, s3 := testServerWithConfig(t, func(c *Config) {
|
dir3, s3 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||||
c.Bootstrap = false
|
c.Bootstrap = false
|
||||||
})
|
})
|
||||||
|
@ -394,7 +394,7 @@ func TestLeader_ReapServer(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||||
c.Bootstrap = true
|
c.Bootstrap = true
|
||||||
})
|
})
|
||||||
|
@ -404,7 +404,7 @@ func TestLeader_ReapServer(t *testing.T) {
|
||||||
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
dir2, s2 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||||
c.Bootstrap = false
|
c.Bootstrap = false
|
||||||
})
|
})
|
||||||
|
@ -414,7 +414,7 @@ func TestLeader_ReapServer(t *testing.T) {
|
||||||
dir3, s3 := testServerWithConfig(t, func(c *Config) {
|
dir3, s3 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
c.ACLResolverSettings.ACLDefaultPolicy = "allow"
|
||||||
c.Bootstrap = false
|
c.Bootstrap = false
|
||||||
})
|
})
|
||||||
|
@ -473,7 +473,7 @@ func TestLeader_Reconcile_ReapMember(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -526,7 +526,7 @@ func TestLeader_Reconcile(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -875,7 +875,7 @@ func TestLeader_ReapTombstones(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
c.TombstoneTTL = 50 * time.Millisecond
|
c.TombstoneTTL = 50 * time.Millisecond
|
||||||
c.TombstoneTTLGranularity = 10 * time.Millisecond
|
c.TombstoneTTLGranularity = 10 * time.Millisecond
|
||||||
|
@ -1180,7 +1180,7 @@ func TestLeader_ACL_Initialization(t *testing.T) {
|
||||||
c.Datacenter = "dc1"
|
c.Datacenter = "dc1"
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = tt.master
|
c.ACLInitialManagementToken = tt.master
|
||||||
}
|
}
|
||||||
dir1, s1 := testServerWithConfig(t, conf)
|
dir1, s1 := testServerWithConfig(t, conf)
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1225,7 +1225,7 @@ func TestLeader_ACLUpgrade_IsStickyEvenIfSerfTagsRegress(t *testing.T) {
|
||||||
c.Datacenter = "dc1"
|
c.Datacenter = "dc1"
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
|
|
@ -2,6 +2,7 @@ package consul
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
"github.com/hashicorp/serf/serf"
|
"github.com/hashicorp/serf/serf"
|
||||||
|
@ -86,14 +87,41 @@ func (md *lanMergeDelegate) NotifyMerge(members []*serf.Member) error {
|
||||||
// ring. We check that the peers are server nodes and abort the merge
|
// ring. We check that the peers are server nodes and abort the merge
|
||||||
// otherwise.
|
// otherwise.
|
||||||
type wanMergeDelegate struct {
|
type wanMergeDelegate struct {
|
||||||
|
localDatacenter string
|
||||||
|
|
||||||
|
federationDisabledLock sync.Mutex
|
||||||
|
federationDisabled bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWANFederationDisabled selectively disables the wan pool from accepting
|
||||||
|
// non-local members. If the toggle changed the current value it returns true.
|
||||||
|
func (md *wanMergeDelegate) SetWANFederationDisabled(disabled bool) bool {
|
||||||
|
md.federationDisabledLock.Lock()
|
||||||
|
prior := md.federationDisabled
|
||||||
|
md.federationDisabled = disabled
|
||||||
|
md.federationDisabledLock.Unlock()
|
||||||
|
|
||||||
|
return prior != disabled
|
||||||
}
|
}
|
||||||
|
|
||||||
func (md *wanMergeDelegate) NotifyMerge(members []*serf.Member) error {
|
func (md *wanMergeDelegate) NotifyMerge(members []*serf.Member) error {
|
||||||
|
// Deliberately hold this lock during the entire merge so calls to
|
||||||
|
// SetWANFederationDisabled returning immediately imply that the flag takes
|
||||||
|
// effect for all future merges.
|
||||||
|
md.federationDisabledLock.Lock()
|
||||||
|
defer md.federationDisabledLock.Unlock()
|
||||||
|
|
||||||
for _, m := range members {
|
for _, m := range members {
|
||||||
ok, _ := metadata.IsConsulServer(*m)
|
ok, srv := metadata.IsConsulServer(*m)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("Member '%s' is not a server", m.Name)
|
return fmt.Errorf("Member '%s' is not a server", m.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if md.federationDisabled {
|
||||||
|
if srv.Datacenter != md.localDatacenter {
|
||||||
|
return fmt.Errorf("Member '%s' part of wrong datacenter '%s'; WAN federation is disabled", m.Name, srv.Datacenter)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,10 +138,16 @@ func TestMerge_WAN(t *testing.T) {
|
||||||
type testcase struct {
|
type testcase struct {
|
||||||
members []*serf.Member
|
members []*serf.Member
|
||||||
expect string
|
expect string
|
||||||
|
setupFn func(t *testing.T, delegate *wanMergeDelegate)
|
||||||
}
|
}
|
||||||
|
|
||||||
run := func(t *testing.T, tc testcase) {
|
run := func(t *testing.T, tc testcase) {
|
||||||
delegate := &wanMergeDelegate{}
|
delegate := &wanMergeDelegate{
|
||||||
|
localDatacenter: "dc1",
|
||||||
|
}
|
||||||
|
if tc.setupFn != nil {
|
||||||
|
tc.setupFn(t, delegate)
|
||||||
|
}
|
||||||
err := delegate.NotifyMerge(tc.members)
|
err := delegate.NotifyMerge(tc.members)
|
||||||
if tc.expect == "" {
|
if tc.expect == "" {
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -177,7 +183,33 @@ func TestMerge_WAN(t *testing.T) {
|
||||||
build: "0.7.5",
|
build: "0.7.5",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
expect: "",
|
},
|
||||||
|
"federation disabled and local join allowed": {
|
||||||
|
setupFn: func(t *testing.T, delegate *wanMergeDelegate) {
|
||||||
|
delegate.SetWANFederationDisabled(true)
|
||||||
|
},
|
||||||
|
members: []*serf.Member{
|
||||||
|
makeTestNode(t, testMember{
|
||||||
|
dc: "dc1",
|
||||||
|
name: "node1",
|
||||||
|
server: true,
|
||||||
|
build: "0.7.5",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"federation disabled and remote join blocked": {
|
||||||
|
setupFn: func(t *testing.T, delegate *wanMergeDelegate) {
|
||||||
|
delegate.SetWANFederationDisabled(true)
|
||||||
|
},
|
||||||
|
members: []*serf.Member{
|
||||||
|
makeTestNode(t, testMember{
|
||||||
|
dc: "dc2",
|
||||||
|
name: "node1",
|
||||||
|
server: true,
|
||||||
|
build: "0.7.5",
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
expect: `WAN federation is disabled`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,7 +54,7 @@ func TestOperator_Autopilot_GetConfiguration_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
c.AutopilotConfig.CleanupDeadServers = false
|
c.AutopilotConfig.CleanupDeadServers = false
|
||||||
})
|
})
|
||||||
|
@ -138,7 +138,7 @@ func TestOperator_Autopilot_SetConfiguration_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
c.AutopilotConfig.CleanupDeadServers = false
|
c.AutopilotConfig.CleanupDeadServers = false
|
||||||
})
|
})
|
||||||
|
|
|
@ -72,7 +72,7 @@ func TestOperator_RaftGetConfiguration_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -199,7 +199,7 @@ func TestOperator_RaftRemovePeerByAddress_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -305,7 +305,7 @@ func TestOperator_RaftRemovePeerByID_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
c.RaftConfig.ProtocolVersion = 3
|
c.RaftConfig.ProtocolVersion = 3
|
||||||
})
|
})
|
||||||
|
|
|
@ -373,7 +373,7 @@ func (p *PreparedQuery) Execute(args *structs.PreparedQueryExecuteRequest,
|
||||||
if query.Token != "" {
|
if query.Token != "" {
|
||||||
token = query.Token
|
token = query.Token
|
||||||
}
|
}
|
||||||
if err := p.srv.filterACL(token, &reply.Nodes); err != nil {
|
if err := p.srv.filterACL(token, reply); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,7 +500,7 @@ func (p *PreparedQuery) ExecuteRemote(args *structs.PreparedQueryExecuteRemoteRe
|
||||||
if args.Query.Token != "" {
|
if args.Query.Token != "" {
|
||||||
token = args.Query.Token
|
token = args.Query.Token
|
||||||
}
|
}
|
||||||
if err := p.srv.filterACL(token, &reply.Nodes); err != nil {
|
if err := p.srv.filterACL(token, reply); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -200,7 +200,7 @@ func TestPreparedQuery_Apply_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -629,7 +629,7 @@ func TestPreparedQuery_ACLDeny_Catchall_Template(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -831,7 +831,7 @@ func TestPreparedQuery_Get(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1072,7 +1072,7 @@ func TestPreparedQuery_List(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1167,6 +1167,31 @@ func TestPreparedQuery_List(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Same for a token without access to the query.
|
||||||
|
{
|
||||||
|
token := createTokenWithPolicyName(t, codec, "deny-queries", `
|
||||||
|
query_prefix "" {
|
||||||
|
policy = "deny"
|
||||||
|
}
|
||||||
|
`, "root")
|
||||||
|
|
||||||
|
req := &structs.DCSpecificRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
QueryOptions: structs.QueryOptions{Token: token},
|
||||||
|
}
|
||||||
|
var resp structs.IndexedPreparedQueries
|
||||||
|
if err := msgpackrpc.CallWithCodec(codec, "PreparedQuery.List", req, &resp); err != nil {
|
||||||
|
t.Fatalf("err: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(resp.Queries) != 0 {
|
||||||
|
t.Fatalf("bad: %v", resp)
|
||||||
|
}
|
||||||
|
if !resp.QueryMeta.ResultsFilteredByACLs {
|
||||||
|
t.Fatal("ResultsFilteredByACLs should be true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// But a management token should work, and be able to see the captured
|
// But a management token should work, and be able to see the captured
|
||||||
// token.
|
// token.
|
||||||
query.Query.Token = "le-token"
|
query.Query.Token = "le-token"
|
||||||
|
@ -1268,7 +1293,7 @@ func TestPreparedQuery_Explain(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -1392,7 +1417,7 @@ func TestPreparedQuery_Execute(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -2124,6 +2149,7 @@ func TestPreparedQuery_Execute(t *testing.T) {
|
||||||
require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Execute", &req, &reply))
|
require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Execute", &req, &reply))
|
||||||
|
|
||||||
expectNodes(t, &query, &reply, 0)
|
expectNodes(t, &query, &reply, 0)
|
||||||
|
require.True(t, reply.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("normal operation again with exec token", func(t *testing.T) {
|
t.Run("normal operation again with exec token", func(t *testing.T) {
|
||||||
|
@ -2246,6 +2272,20 @@ func TestPreparedQuery_Execute(t *testing.T) {
|
||||||
expectFailoverNodes(t, &query, &reply, 0)
|
expectFailoverNodes(t, &query, &reply, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("nodes in response from dc2 are filtered by ACL token", func(t *testing.T) {
|
||||||
|
req := structs.PreparedQueryExecuteRequest{
|
||||||
|
Datacenter: "dc1",
|
||||||
|
QueryIDOrName: query.Query.ID,
|
||||||
|
QueryOptions: structs.QueryOptions{Token: execNoNodesToken},
|
||||||
|
}
|
||||||
|
|
||||||
|
var reply structs.PreparedQueryExecuteResponse
|
||||||
|
require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Execute", &req, &reply))
|
||||||
|
|
||||||
|
expectFailoverNodes(t, &query, &reply, 0)
|
||||||
|
require.True(t, reply.QueryMeta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be true")
|
||||||
|
})
|
||||||
|
|
||||||
// Bake the exec token into the query.
|
// Bake the exec token into the query.
|
||||||
query.Query.Token = execToken
|
query.Query.Token = execToken
|
||||||
require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Apply", &query, &query.Query.ID))
|
require.NoError(t, msgpackrpc.CallWithCodec(codec1, "PreparedQuery.Apply", &query, &query.Query.ID))
|
||||||
|
@ -2659,7 +2699,7 @@ func TestPreparedQuery_Wrapper(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -2669,7 +2709,7 @@ func TestPreparedQuery_Wrapper(t *testing.T) {
|
||||||
c.Datacenter = "dc2"
|
c.Datacenter = "dc2"
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir2)
|
defer os.RemoveAll(dir2)
|
||||||
|
|
|
@ -882,7 +882,7 @@ func TestRPC_LocalTokenStrippedOnForward(t *testing.T) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
defer s1.Shutdown()
|
defer s1.Shutdown()
|
||||||
|
@ -1010,7 +1010,7 @@ func TestRPC_LocalTokenStrippedOnForward_GRPC(t *testing.T) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.RPCConfig.EnableStreaming = true
|
c.RPCConfig.EnableStreaming = true
|
||||||
})
|
})
|
||||||
s1.tokens.UpdateAgentToken("root", tokenStore.TokenSourceConfig)
|
s1.tokens.UpdateAgentToken("root", tokenStore.TokenSourceConfig)
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
|
|
||||||
"github.com/armon/go-metrics"
|
"github.com/armon/go-metrics"
|
||||||
connlimit "github.com/hashicorp/go-connlimit"
|
connlimit "github.com/hashicorp/go-connlimit"
|
||||||
|
@ -25,7 +26,7 @@ import (
|
||||||
"github.com/hashicorp/go-memdb"
|
"github.com/hashicorp/go-memdb"
|
||||||
"github.com/hashicorp/raft"
|
"github.com/hashicorp/raft"
|
||||||
autopilot "github.com/hashicorp/raft-autopilot"
|
autopilot "github.com/hashicorp/raft-autopilot"
|
||||||
raftboltdb "github.com/hashicorp/raft-boltdb"
|
raftboltdb "github.com/hashicorp/raft-boltdb/v2"
|
||||||
"github.com/hashicorp/serf/serf"
|
"github.com/hashicorp/serf/serf"
|
||||||
"golang.org/x/time/rate"
|
"golang.org/x/time/rate"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
@ -188,6 +189,12 @@ type Server struct {
|
||||||
// serf cluster that spans datacenters
|
// serf cluster that spans datacenters
|
||||||
eventChWAN chan serf.Event
|
eventChWAN chan serf.Event
|
||||||
|
|
||||||
|
// wanMembershipNotifyCh is used to receive notifications that the the
|
||||||
|
// serfWAN wan pool may have changed.
|
||||||
|
//
|
||||||
|
// If this is nil, notification is skipped.
|
||||||
|
wanMembershipNotifyCh chan struct{}
|
||||||
|
|
||||||
// fsm is the state machine used with Raft to provide
|
// fsm is the state machine used with Raft to provide
|
||||||
// strong consistency.
|
// strong consistency.
|
||||||
fsm *fsm.FSM
|
fsm *fsm.FSM
|
||||||
|
@ -265,6 +272,7 @@ type Server struct {
|
||||||
// serfWAN is the Serf cluster maintained between DC's
|
// serfWAN is the Serf cluster maintained between DC's
|
||||||
// which SHOULD only consist of Consul servers
|
// which SHOULD only consist of Consul servers
|
||||||
serfWAN *serf.Serf
|
serfWAN *serf.Serf
|
||||||
|
serfWANConfig *serf.Config
|
||||||
memberlistTransportWAN wanfed.IngestionAwareTransport
|
memberlistTransportWAN wanfed.IngestionAwareTransport
|
||||||
gatewayLocator *GatewayLocator
|
gatewayLocator *GatewayLocator
|
||||||
|
|
||||||
|
@ -492,7 +500,7 @@ func NewServer(config *Config, flat Deps) (*Server, error) {
|
||||||
|
|
||||||
// Initialize the WAN Serf if enabled
|
// Initialize the WAN Serf if enabled
|
||||||
if config.SerfWANConfig != nil {
|
if config.SerfWANConfig != nil {
|
||||||
s.serfWAN, err = s.setupSerf(setupSerfOptions{
|
s.serfWAN, s.serfWANConfig, err = s.setupSerf(setupSerfOptions{
|
||||||
Config: config.SerfWANConfig,
|
Config: config.SerfWANConfig,
|
||||||
EventCh: s.eventChWAN,
|
EventCh: s.eventChWAN,
|
||||||
SnapshotPath: serfWANSnapshot,
|
SnapshotPath: serfWANSnapshot,
|
||||||
|
@ -547,7 +555,7 @@ func NewServer(config *Config, flat Deps) (*Server, error) {
|
||||||
s.Shutdown()
|
s.Shutdown()
|
||||||
return nil, fmt.Errorf("Failed to add WAN serf route: %v", err)
|
return nil, fmt.Errorf("Failed to add WAN serf route: %v", err)
|
||||||
}
|
}
|
||||||
go router.HandleSerfEvents(s.logger, s.router, types.AreaWAN, s.serfWAN.ShutdownCh(), s.eventChWAN)
|
go router.HandleSerfEvents(s.logger, s.router, types.AreaWAN, s.serfWAN.ShutdownCh(), s.eventChWAN, s.wanMembershipNotifyCh)
|
||||||
|
|
||||||
// Fire up the LAN <-> WAN join flooder.
|
// Fire up the LAN <-> WAN join flooder.
|
||||||
addrFn := func(s *metadata.Server) (string, error) {
|
addrFn := func(s *metadata.Server) (string, error) {
|
||||||
|
@ -646,7 +654,7 @@ func newGRPCHandlerFromConfig(deps Deps, config *Config, s *Server) connHandler
|
||||||
s.registerEnterpriseGRPCServices(deps, srv)
|
s.registerEnterpriseGRPCServices(deps, srv)
|
||||||
}
|
}
|
||||||
|
|
||||||
return agentgrpc.NewHandler(config.RPCAddr, register)
|
return agentgrpc.NewHandler(deps.Logger, config.RPCAddr, register)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) connectCARootsMonitor(ctx context.Context) {
|
func (s *Server) connectCARootsMonitor(ctx context.Context) {
|
||||||
|
@ -729,13 +737,21 @@ func (s *Server) setupRaft() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the backend raft store for logs and stable storage.
|
// Create the backend raft store for logs and stable storage.
|
||||||
store, err := raftboltdb.NewBoltStore(filepath.Join(path, "raft.db"))
|
store, err := raftboltdb.New(raftboltdb.Options{
|
||||||
|
BoltOptions: &bbolt.Options{
|
||||||
|
NoFreelistSync: s.config.RaftBoltDBConfig.NoFreelistSync,
|
||||||
|
},
|
||||||
|
Path: filepath.Join(path, "raft.db"),
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.raftStore = store
|
s.raftStore = store
|
||||||
stable = store
|
stable = store
|
||||||
|
|
||||||
|
// start publishing boltdb metrics
|
||||||
|
go store.RunMetrics(&lib.StopChannelContext{StopCh: s.shutdownCh}, 0)
|
||||||
|
|
||||||
// Wrap the store in a LogCache to improve performance.
|
// Wrap the store in a LogCache to improve performance.
|
||||||
cacheStore, err := raft.NewLogCache(raftLogCacheSize, store)
|
cacheStore, err := raft.NewLogCache(raftLogCacheSize, store)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1115,6 +1131,11 @@ func (s *Server) JoinWAN(addrs []string) (int, error) {
|
||||||
if s.serfWAN == nil {
|
if s.serfWAN == nil {
|
||||||
return 0, ErrWANFederationDisabled
|
return 0, ErrWANFederationDisabled
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := s.enterpriseValidateJoinWAN(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
return s.serfWAN.Join(addrs, true)
|
return s.serfWAN.Join(addrs, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,10 @@ import (
|
||||||
|
|
||||||
func (s *Server) registerEnterpriseGRPCServices(deps Deps, srv *grpc.Server) {}
|
func (s *Server) registerEnterpriseGRPCServices(deps Deps, srv *grpc.Server) {}
|
||||||
|
|
||||||
|
func (s *Server) enterpriseValidateJoinWAN() error {
|
||||||
|
return nil // no-op
|
||||||
|
}
|
||||||
|
|
||||||
// JoinLAN is used to have Consul join the inner-DC pool The target address
|
// JoinLAN is used to have Consul join the inner-DC pool The target address
|
||||||
// should be another node inside the DC listening on the Serf LAN address
|
// should be another node inside the DC listening on the Serf LAN address
|
||||||
func (s *Server) JoinLAN(addrs []string, entMeta *structs.EnterpriseMeta) (int, error) {
|
func (s *Server) JoinLAN(addrs []string, entMeta *structs.EnterpriseMeta) (int, error) {
|
||||||
|
|
|
@ -48,12 +48,18 @@ type setupSerfOptions struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// setupSerf is used to setup and initialize a Serf
|
// setupSerf is used to setup and initialize a Serf
|
||||||
func (s *Server) setupSerf(opts setupSerfOptions) (*serf.Serf, error) {
|
func (s *Server) setupSerf(opts setupSerfOptions) (*serf.Serf, *serf.Config, error) {
|
||||||
conf, err := s.setupSerfConfig(opts)
|
conf, err := s.setupSerfConfig(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
return serf.Create(conf)
|
|
||||||
|
cluster, err := serf.Create(conf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return cluster, conf, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) setupSerfConfig(opts setupSerfOptions) (*serf.Config, error) {
|
func (s *Server) setupSerfConfig(opts setupSerfOptions) (*serf.Config, error) {
|
||||||
|
@ -152,7 +158,9 @@ func (s *Server) setupSerfConfig(opts setupSerfOptions) (*serf.Config, error) {
|
||||||
conf.ProtocolVersion = protocolVersionMap[s.config.ProtocolVersion]
|
conf.ProtocolVersion = protocolVersionMap[s.config.ProtocolVersion]
|
||||||
conf.RejoinAfterLeave = s.config.RejoinAfterLeave
|
conf.RejoinAfterLeave = s.config.RejoinAfterLeave
|
||||||
if opts.WAN {
|
if opts.WAN {
|
||||||
conf.Merge = &wanMergeDelegate{}
|
conf.Merge = &wanMergeDelegate{
|
||||||
|
localDatacenter: s.config.Datacenter,
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
conf.Merge = &lanMergeDelegate{
|
conf.Merge = &lanMergeDelegate{
|
||||||
dc: s.config.Datacenter,
|
dc: s.config.Datacenter,
|
||||||
|
|
|
@ -74,7 +74,7 @@ func testServerACLConfig(cb func(*Config)) func(*Config) {
|
||||||
return func(c *Config) {
|
return func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = TestDefaultMasterToken
|
c.ACLInitialManagementToken = TestDefaultMasterToken
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
|
|
||||||
if cb != nil {
|
if cb != nil {
|
||||||
|
|
|
@ -151,7 +151,7 @@ func (s *Session) Apply(args *structs.SessionRequest, reply *string) error {
|
||||||
|
|
||||||
if args.Op == structs.SessionCreate && args.Session.TTL != "" {
|
if args.Op == structs.SessionCreate && args.Session.TTL != "" {
|
||||||
// If we created a session with a TTL, reset the expiration timer
|
// If we created a session with a TTL, reset the expiration timer
|
||||||
s.srv.resetSessionTimer(args.Session.ID, &args.Session)
|
s.srv.resetSessionTimer(&args.Session)
|
||||||
} else if args.Op == structs.SessionDestroy {
|
} else if args.Op == structs.SessionDestroy {
|
||||||
// If we destroyed a session, it might potentially have a TTL,
|
// If we destroyed a session, it might potentially have a TTL,
|
||||||
// and we need to clear the timer
|
// and we need to clear the timer
|
||||||
|
@ -308,7 +308,7 @@ func (s *Session) Renew(args *structs.SessionSpecificRequest,
|
||||||
|
|
||||||
// Reset the session TTL timer.
|
// Reset the session TTL timer.
|
||||||
reply.Sessions = structs.Sessions{session}
|
reply.Sessions = structs.Sessions{session}
|
||||||
if err := s.srv.resetSessionTimer(args.SessionID, session); err != nil {
|
if err := s.srv.resetSessionTimer(session); err != nil {
|
||||||
s.logger.Error("Session renew failed", "error", err)
|
s.logger.Error("Session renew failed", "error", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -157,7 +157,7 @@ func TestSession_Apply_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -382,7 +382,7 @@ func TestSession_Get_List_NodeSessions_ACLFilter(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -731,7 +731,7 @@ func TestSession_Renew_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
|
|
@ -47,13 +47,12 @@ func (s *Server) initializeSessionTimers() error {
|
||||||
// Scan all sessions and reset their timer
|
// Scan all sessions and reset their timer
|
||||||
state := s.fsm.State()
|
state := s.fsm.State()
|
||||||
|
|
||||||
// TODO(partitions): track all session timers in all partitions
|
_, sessions, err := state.SessionListAll(nil)
|
||||||
_, sessions, err := state.SessionList(nil, structs.WildcardEnterpriseMetaInDefaultPartition())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, session := range sessions {
|
for _, session := range sessions {
|
||||||
if err := s.resetSessionTimer(session.ID, session); err != nil {
|
if err := s.resetSessionTimer(session); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,20 +62,7 @@ func (s *Server) initializeSessionTimers() error {
|
||||||
// resetSessionTimer is used to renew the TTL of a session.
|
// resetSessionTimer is used to renew the TTL of a session.
|
||||||
// This can be used for new sessions and existing ones. A session
|
// This can be used for new sessions and existing ones. A session
|
||||||
// will be faulted in if not given.
|
// will be faulted in if not given.
|
||||||
func (s *Server) resetSessionTimer(id string, session *structs.Session) error {
|
func (s *Server) resetSessionTimer(session *structs.Session) error {
|
||||||
// Fault the session in if not given
|
|
||||||
if session == nil {
|
|
||||||
state := s.fsm.State()
|
|
||||||
_, s, err := state.SessionGet(nil, id, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if s == nil {
|
|
||||||
return fmt.Errorf("Session '%s' not found", id)
|
|
||||||
}
|
|
||||||
session = s
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bail if the session has no TTL, fast-path some common inputs
|
// Bail if the session has no TTL, fast-path some common inputs
|
||||||
switch session.TTL {
|
switch session.TTL {
|
||||||
case "", "0", "0s", "0m", "0h":
|
case "", "0", "0s", "0m", "0h":
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
"github.com/hashicorp/consul/sdk/testutil/retry"
|
"github.com/hashicorp/consul/sdk/testutil/retry"
|
||||||
"github.com/hashicorp/consul/testrpc"
|
"github.com/hashicorp/consul/testrpc"
|
||||||
"github.com/hashicorp/go-uuid"
|
"github.com/hashicorp/go-uuid"
|
||||||
"github.com/hashicorp/net-rpc-msgpackrpc"
|
msgpackrpc "github.com/hashicorp/net-rpc-msgpackrpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func generateUUID() (ret string) {
|
func generateUUID() (ret string) {
|
||||||
|
@ -59,50 +59,6 @@ func TestInitializeSessionTimers(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResetSessionTimer_Fault(t *testing.T) {
|
|
||||||
if testing.Short() {
|
|
||||||
t.Skip("too slow for testing.Short")
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Parallel()
|
|
||||||
dir1, s1 := testServer(t)
|
|
||||||
defer os.RemoveAll(dir1)
|
|
||||||
defer s1.Shutdown()
|
|
||||||
|
|
||||||
testrpc.WaitForLeader(t, s1.RPC, "dc1")
|
|
||||||
|
|
||||||
// Should not exist
|
|
||||||
err := s1.resetSessionTimer(generateUUID(), nil)
|
|
||||||
if err == nil || !strings.Contains(err.Error(), "not found") {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a session
|
|
||||||
state := s1.fsm.State()
|
|
||||||
if err := state.EnsureNode(1, &structs.Node{Node: "foo", Address: "127.0.0.1"}); err != nil {
|
|
||||||
t.Fatalf("err: %s", err)
|
|
||||||
}
|
|
||||||
session := &structs.Session{
|
|
||||||
ID: generateUUID(),
|
|
||||||
Node: "foo",
|
|
||||||
TTL: "10s",
|
|
||||||
}
|
|
||||||
if err := state.SessionCreate(100, session); err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the session timer
|
|
||||||
err = s1.resetSessionTimer(session.ID, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("err: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check that we have a timer
|
|
||||||
if s1.sessionTimers.Get(session.ID) == nil {
|
|
||||||
t.Fatalf("missing session timer")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestResetSessionTimer_NoTTL(t *testing.T) {
|
func TestResetSessionTimer_NoTTL(t *testing.T) {
|
||||||
if testing.Short() {
|
if testing.Short() {
|
||||||
t.Skip("too slow for testing.Short")
|
t.Skip("too slow for testing.Short")
|
||||||
|
@ -130,7 +86,7 @@ func TestResetSessionTimer_NoTTL(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the session timer
|
// Reset the session timer
|
||||||
err := s1.resetSessionTimer(session.ID, session)
|
err := s1.resetSessionTimer(session)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -155,7 +111,7 @@ func TestResetSessionTimer_InvalidTTL(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the session timer
|
// Reset the session timer
|
||||||
err := s1.resetSessionTimer(session.ID, session)
|
err := s1.resetSessionTimer(session)
|
||||||
if err == nil || !strings.Contains(err.Error(), "Invalid Session TTL") {
|
if err == nil || !strings.Contains(err.Error(), "Invalid Session TTL") {
|
||||||
t.Fatalf("err: %v", err)
|
t.Fatalf("err: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -271,7 +271,7 @@ func TestSnapshot_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
|
|
@ -741,6 +741,9 @@ func ensureServiceTxn(tx WriteTxn, idx uint64, node string, preserveIndexes bool
|
||||||
if err = checkGatewayWildcardsAndUpdate(tx, idx, svc); err != nil {
|
if err = checkGatewayWildcardsAndUpdate(tx, idx, svc); err != nil {
|
||||||
return fmt.Errorf("failed updating gateway mapping: %s", err)
|
return fmt.Errorf("failed updating gateway mapping: %s", err)
|
||||||
}
|
}
|
||||||
|
if err := upsertKindServiceName(tx, idx, svc.Kind, svc.CompoundServiceName()); err != nil {
|
||||||
|
return fmt.Errorf("failed to persist service name: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Update upstream/downstream mappings if it's a connect service
|
// Update upstream/downstream mappings if it's a connect service
|
||||||
if svc.Kind == structs.ServiceKindConnectProxy || svc.Connect.Native {
|
if svc.Kind == structs.ServiceKindConnectProxy || svc.Connect.Native {
|
||||||
|
@ -965,16 +968,14 @@ func (s *Store) Services(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (ui
|
||||||
return idx, results, nil
|
return idx, results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Store) ServiceList(ws memdb.WatchSet,
|
func (s *Store) ServiceList(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
|
||||||
include func(svc *structs.ServiceNode) bool, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
|
|
||||||
tx := s.db.Txn(false)
|
tx := s.db.Txn(false)
|
||||||
defer tx.Abort()
|
defer tx.Abort()
|
||||||
|
|
||||||
return serviceListTxn(tx, ws, include, entMeta)
|
return serviceListTxn(tx, ws, entMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
func serviceListTxn(tx ReadTxn, ws memdb.WatchSet,
|
func serviceListTxn(tx ReadTxn, ws memdb.WatchSet, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
|
||||||
include func(svc *structs.ServiceNode) bool, entMeta *structs.EnterpriseMeta) (uint64, structs.ServiceList, error) {
|
|
||||||
idx := catalogServicesMaxIndex(tx, entMeta)
|
idx := catalogServicesMaxIndex(tx, entMeta)
|
||||||
|
|
||||||
services, err := tx.Get(tableServices, indexID+"_prefix", entMeta)
|
services, err := tx.Get(tableServices, indexID+"_prefix", entMeta)
|
||||||
|
@ -986,11 +987,7 @@ func serviceListTxn(tx ReadTxn, ws memdb.WatchSet,
|
||||||
unique := make(map[structs.ServiceName]struct{})
|
unique := make(map[structs.ServiceName]struct{})
|
||||||
for service := services.Next(); service != nil; service = services.Next() {
|
for service := services.Next(); service != nil; service = services.Next() {
|
||||||
svc := service.(*structs.ServiceNode)
|
svc := service.(*structs.ServiceNode)
|
||||||
// TODO (freddy) This is a hack to exclude certain kinds.
|
unique[svc.CompoundServiceName()] = struct{}{}
|
||||||
// Need a new index to query by kind and namespace, have to coordinate with consul foundations first
|
|
||||||
if include == nil || include(svc) {
|
|
||||||
unique[svc.CompoundServiceName()] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
results := make(structs.ServiceList, 0, len(unique))
|
results := make(structs.ServiceList, 0, len(unique))
|
||||||
|
@ -1691,6 +1688,9 @@ func (s *Store) deleteServiceTxn(tx WriteTxn, idx uint64, nodeName, serviceID st
|
||||||
if err := freeServiceVirtualIP(tx, svc.ServiceName, entMeta); err != nil {
|
if err := freeServiceVirtualIP(tx, svc.ServiceName, entMeta); err != nil {
|
||||||
return fmt.Errorf("failed to clean up virtual IP for %q: %v", name.String(), err)
|
return fmt.Errorf("failed to clean up virtual IP for %q: %v", name.String(), err)
|
||||||
}
|
}
|
||||||
|
if err := cleanupKindServiceName(tx, idx, svc.CompoundServiceName(), svc.ServiceKind); err != nil {
|
||||||
|
return fmt.Errorf("failed to persist service name: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Could not find any service %s: %s", svc.ServiceName, err)
|
return fmt.Errorf("Could not find any service %s: %s", svc.ServiceName, err)
|
||||||
|
@ -2526,6 +2526,30 @@ func (s *Store) VirtualIPForService(sn structs.ServiceName) (string, error) {
|
||||||
return result.String(), nil
|
return result.String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) ServiceNamesOfKind(ws memdb.WatchSet, kind structs.ServiceKind) (uint64, []*KindServiceName, error) {
|
||||||
|
tx := s.db.Txn(false)
|
||||||
|
defer tx.Abort()
|
||||||
|
|
||||||
|
return serviceNamesOfKindTxn(tx, ws, kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serviceNamesOfKindTxn(tx ReadTxn, ws memdb.WatchSet, kind structs.ServiceKind) (uint64, []*KindServiceName, error) {
|
||||||
|
var names []*KindServiceName
|
||||||
|
iter, err := tx.Get(tableKindServiceNames, indexKindOnly, kind)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
ws.Add(iter.WatchCh())
|
||||||
|
|
||||||
|
idx := kindServiceNamesMaxIndex(tx, ws, kind)
|
||||||
|
for name := iter.Next(); name != nil; name = iter.Next() {
|
||||||
|
ksn := name.(*KindServiceName)
|
||||||
|
names = append(names, ksn)
|
||||||
|
}
|
||||||
|
|
||||||
|
return idx, names, nil
|
||||||
|
}
|
||||||
|
|
||||||
// parseCheckServiceNodes is used to parse through a given set of services,
|
// parseCheckServiceNodes is used to parse through a given set of services,
|
||||||
// and query for an associated node and a set of checks. This is the inner
|
// and query for an associated node and a set of checks. This is the inner
|
||||||
// method used to return a rich set of results from a more simple query.
|
// method used to return a rich set of results from a more simple query.
|
||||||
|
@ -3862,3 +3886,44 @@ func truncateGatewayServiceTopologyMappings(tx WriteTxn, idx uint64, gateway str
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func upsertKindServiceName(tx WriteTxn, idx uint64, kind structs.ServiceKind, name structs.ServiceName) error {
|
||||||
|
q := KindServiceNameQuery{Name: name.Name, Kind: kind, EnterpriseMeta: name.EnterpriseMeta}
|
||||||
|
existing, err := tx.First(tableKindServiceNames, indexID, q)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service name is already known. Nothing to do.
|
||||||
|
if existing != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
ksn := KindServiceName{
|
||||||
|
Kind: kind,
|
||||||
|
Service: name,
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: idx,
|
||||||
|
ModifyIndex: idx,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if err := tx.Insert(tableKindServiceNames, &ksn); err != nil {
|
||||||
|
return fmt.Errorf("failed inserting %s/%s into %s: %s", kind, name.String(), tableKindServiceNames, err)
|
||||||
|
}
|
||||||
|
if err := indexUpdateMaxTxn(tx, idx, kindServiceNameIndexName(kind)); err != nil {
|
||||||
|
return fmt.Errorf("failed updating %s index: %v", tableKindServiceNames, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cleanupKindServiceName(tx WriteTxn, idx uint64, name structs.ServiceName, kind structs.ServiceKind) error {
|
||||||
|
q := KindServiceNameQuery{Name: name.Name, Kind: kind, EnterpriseMeta: name.EnterpriseMeta}
|
||||||
|
if _, err := tx.DeleteAll(tableKindServiceNames, indexID, q); err != nil {
|
||||||
|
return fmt.Errorf("failed to delete %s from %s: %s", name, tableKindServiceNames, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := indexUpdateMaxTxn(tx, idx, kindServiceNameIndexName(kind)); err != nil {
|
||||||
|
return fmt.Errorf("failed updating %s index: %v", tableKindServiceNames, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ package state
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
memdb "github.com/hashicorp/go-memdb"
|
memdb "github.com/hashicorp/go-memdb"
|
||||||
|
|
||||||
|
@ -18,13 +19,7 @@ func serviceIndexName(name string, _ *structs.EnterpriseMeta) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func serviceKindIndexName(kind structs.ServiceKind, _ *structs.EnterpriseMeta) string {
|
func serviceKindIndexName(kind structs.ServiceKind, _ *structs.EnterpriseMeta) string {
|
||||||
switch kind {
|
return "service_kind." + kind.Normalized()
|
||||||
case structs.ServiceKindTypical:
|
|
||||||
// needs a special case here
|
|
||||||
return "service_kind.typical"
|
|
||||||
default:
|
|
||||||
return "service_kind." + string(kind)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func catalogUpdateNodesIndexes(tx WriteTxn, idx uint64, entMeta *structs.EnterpriseMeta) error {
|
func catalogUpdateNodesIndexes(tx WriteTxn, idx uint64, entMeta *structs.EnterpriseMeta) error {
|
||||||
|
@ -192,3 +187,22 @@ func validateRegisterRequestTxn(_ ReadTxn, _ *structs.RegisterRequest, _ bool) (
|
||||||
func (s *Store) ValidateRegisterRequest(_ *structs.RegisterRequest) (*structs.EnterpriseMeta, error) {
|
func (s *Store) ValidateRegisterRequest(_ *structs.RegisterRequest) (*structs.EnterpriseMeta, error) {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func indexFromKindServiceName(arg interface{}) ([]byte, error) {
|
||||||
|
var b indexBuilder
|
||||||
|
|
||||||
|
switch n := arg.(type) {
|
||||||
|
case KindServiceNameQuery:
|
||||||
|
b.String(strings.ToLower(string(n.Kind)))
|
||||||
|
b.String(strings.ToLower(n.Name))
|
||||||
|
return b.Bytes(), nil
|
||||||
|
|
||||||
|
case *KindServiceName:
|
||||||
|
b.String(strings.ToLower(string(n.Kind)))
|
||||||
|
b.String(strings.ToLower(n.Service.Name))
|
||||||
|
return b.Bytes(), nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("type must be KindServiceNameQuery or *KindServiceName: %T", arg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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"
|
tableMeshTopology = "mesh-topology"
|
||||||
tableServiceVirtualIPs = "service-virtual-ips"
|
tableServiceVirtualIPs = "service-virtual-ips"
|
||||||
tableFreeVirtualIPs = "free-virtual-ips"
|
tableFreeVirtualIPs = "free-virtual-ips"
|
||||||
|
tableKindServiceNames = "kind-service-names"
|
||||||
|
|
||||||
indexID = "id"
|
indexID = "id"
|
||||||
indexService = "service"
|
indexService = "service"
|
||||||
|
@ -661,3 +662,80 @@ func freeVirtualIPTableSchema() *memdb.TableSchema {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type KindServiceName struct {
|
||||||
|
Kind structs.ServiceKind
|
||||||
|
Service structs.ServiceName
|
||||||
|
|
||||||
|
structs.RaftIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
func kindServiceNameTableSchema() *memdb.TableSchema {
|
||||||
|
return &memdb.TableSchema{
|
||||||
|
Name: tableKindServiceNames,
|
||||||
|
Indexes: map[string]*memdb.IndexSchema{
|
||||||
|
indexID: {
|
||||||
|
Name: indexID,
|
||||||
|
AllowMissing: false,
|
||||||
|
Unique: true,
|
||||||
|
Indexer: indexerSingle{
|
||||||
|
readIndex: indexFromKindServiceName,
|
||||||
|
writeIndex: indexFromKindServiceName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
indexKindOnly: {
|
||||||
|
Name: indexKindOnly,
|
||||||
|
AllowMissing: false,
|
||||||
|
Unique: false,
|
||||||
|
Indexer: indexerSingle{
|
||||||
|
readIndex: indexFromKindServiceNameKindOnly,
|
||||||
|
writeIndex: indexFromKindServiceNameKindOnly,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// KindServiceNameQuery is used to lookup service names by kind or enterprise meta.
|
||||||
|
type KindServiceNameQuery struct {
|
||||||
|
Kind structs.ServiceKind
|
||||||
|
Name string
|
||||||
|
structs.EnterpriseMeta
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceOrDefault exists because structs.EnterpriseMeta uses a pointer
|
||||||
|
// receiver for this method. Remove once that is fixed.
|
||||||
|
func (q KindServiceNameQuery) NamespaceOrDefault() string {
|
||||||
|
return q.EnterpriseMeta.NamespaceOrDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
// PartitionOrDefault exists because structs.EnterpriseMeta uses a pointer
|
||||||
|
// receiver for this method. Remove once that is fixed.
|
||||||
|
func (q KindServiceNameQuery) PartitionOrDefault() string {
|
||||||
|
return q.EnterpriseMeta.PartitionOrDefault()
|
||||||
|
}
|
||||||
|
|
||||||
|
func indexFromKindServiceNameKindOnly(raw interface{}) ([]byte, error) {
|
||||||
|
switch x := raw.(type) {
|
||||||
|
case *KindServiceName:
|
||||||
|
var b indexBuilder
|
||||||
|
b.String(strings.ToLower(string(x.Kind)))
|
||||||
|
return b.Bytes(), nil
|
||||||
|
|
||||||
|
case structs.ServiceKind:
|
||||||
|
var b indexBuilder
|
||||||
|
b.String(strings.ToLower(string(x)))
|
||||||
|
return b.Bytes(), nil
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("type must be *KindServiceName or structs.ServiceKind: %T", raw)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func kindServiceNamesMaxIndex(tx ReadTxn, ws memdb.WatchSet, kind structs.ServiceKind) uint64 {
|
||||||
|
return maxIndexWatchTxn(tx, ws, kindServiceNameIndexName(kind))
|
||||||
|
}
|
||||||
|
|
||||||
|
func kindServiceNameIndexName(kind structs.ServiceKind) string {
|
||||||
|
return "kind_service_names." + kind.Normalized()
|
||||||
|
}
|
||||||
|
|
|
@ -7656,6 +7656,143 @@ func TestProtocolForIngressGateway(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStateStore_EnsureService_ServiceNames(t *testing.T) {
|
||||||
|
s := testStateStore(t)
|
||||||
|
|
||||||
|
// Create the service registration.
|
||||||
|
entMeta := structs.DefaultEnterpriseMetaInDefaultPartition()
|
||||||
|
|
||||||
|
services := []structs.NodeService{
|
||||||
|
{
|
||||||
|
Kind: structs.ServiceKindIngressGateway,
|
||||||
|
ID: "ingress-gateway",
|
||||||
|
Service: "ingress-gateway",
|
||||||
|
Address: "2.2.2.2",
|
||||||
|
Port: 2222,
|
||||||
|
EnterpriseMeta: *entMeta,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Kind: structs.ServiceKindMeshGateway,
|
||||||
|
ID: "mesh-gateway",
|
||||||
|
Service: "mesh-gateway",
|
||||||
|
Address: "4.4.4.4",
|
||||||
|
Port: 4444,
|
||||||
|
EnterpriseMeta: *entMeta,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Kind: structs.ServiceKindConnectProxy,
|
||||||
|
ID: "connect-proxy",
|
||||||
|
Service: "connect-proxy",
|
||||||
|
Address: "1.1.1.1",
|
||||||
|
Port: 1111,
|
||||||
|
Proxy: structs.ConnectProxyConfig{DestinationServiceName: "foo"},
|
||||||
|
EnterpriseMeta: *entMeta,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Kind: structs.ServiceKindTerminatingGateway,
|
||||||
|
ID: "terminating-gateway",
|
||||||
|
Service: "terminating-gateway",
|
||||||
|
Address: "3.3.3.3",
|
||||||
|
Port: 3333,
|
||||||
|
EnterpriseMeta: *entMeta,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Kind: structs.ServiceKindTypical,
|
||||||
|
ID: "web",
|
||||||
|
Service: "web",
|
||||||
|
Address: "5.5.5.5",
|
||||||
|
Port: 5555,
|
||||||
|
EnterpriseMeta: *entMeta,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var idx uint64
|
||||||
|
testRegisterNode(t, s, idx, "node1")
|
||||||
|
|
||||||
|
for _, svc := range services {
|
||||||
|
idx++
|
||||||
|
require.NoError(t, s.EnsureService(idx, "node1", &svc))
|
||||||
|
|
||||||
|
// Ensure the service name was stored for all of them under the appropriate kind
|
||||||
|
gotIdx, gotNames, err := s.ServiceNamesOfKind(nil, svc.Kind)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, idx, gotIdx)
|
||||||
|
require.Len(t, gotNames, 1)
|
||||||
|
require.Equal(t, svc.CompoundServiceName(), gotNames[0].Service)
|
||||||
|
require.Equal(t, svc.Kind, gotNames[0].Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register another ingress gateway and there should be two names under the kind index
|
||||||
|
newIngress := structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindIngressGateway,
|
||||||
|
ID: "new-ingress-gateway",
|
||||||
|
Service: "new-ingress-gateway",
|
||||||
|
Address: "6.6.6.6",
|
||||||
|
Port: 6666,
|
||||||
|
EnterpriseMeta: *entMeta,
|
||||||
|
}
|
||||||
|
idx++
|
||||||
|
require.NoError(t, s.EnsureService(idx, "node1", &newIngress))
|
||||||
|
|
||||||
|
gotIdx, got, err := s.ServiceNamesOfKind(nil, structs.ServiceKindIngressGateway)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, idx, gotIdx)
|
||||||
|
|
||||||
|
expect := []*KindServiceName{
|
||||||
|
{
|
||||||
|
Kind: structs.ServiceKindIngressGateway,
|
||||||
|
Service: structs.NewServiceName("ingress-gateway", nil),
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: 1,
|
||||||
|
ModifyIndex: 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Kind: structs.ServiceKindIngressGateway,
|
||||||
|
Service: structs.NewServiceName("new-ingress-gateway", nil),
|
||||||
|
RaftIndex: structs.RaftIndex{
|
||||||
|
CreateIndex: idx,
|
||||||
|
ModifyIndex: idx,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.Equal(t, expect, got)
|
||||||
|
|
||||||
|
// Deregister an ingress gateway and the index should not slide back
|
||||||
|
idx++
|
||||||
|
require.NoError(t, s.DeleteService(idx, "node1", "new-ingress-gateway", entMeta))
|
||||||
|
|
||||||
|
gotIdx, got, err = s.ServiceNamesOfKind(nil, structs.ServiceKindIngressGateway)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, idx, gotIdx)
|
||||||
|
require.Equal(t, expect[:1], got)
|
||||||
|
|
||||||
|
// Registering another instance of a known service should not bump the kind index
|
||||||
|
newMGW := structs.NodeService{
|
||||||
|
Kind: structs.ServiceKindMeshGateway,
|
||||||
|
ID: "mesh-gateway-1",
|
||||||
|
Service: "mesh-gateway",
|
||||||
|
Address: "7.7.7.7",
|
||||||
|
Port: 7777,
|
||||||
|
EnterpriseMeta: *entMeta,
|
||||||
|
}
|
||||||
|
idx++
|
||||||
|
require.NoError(t, s.EnsureService(idx, "node1", &newMGW))
|
||||||
|
|
||||||
|
gotIdx, _, err = s.ServiceNamesOfKind(nil, structs.ServiceKindMeshGateway)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, uint64(2), gotIdx)
|
||||||
|
|
||||||
|
// Deregister the single typical service and the service name should also be dropped
|
||||||
|
idx++
|
||||||
|
require.NoError(t, s.DeleteService(idx, "node1", "web", entMeta))
|
||||||
|
|
||||||
|
gotIdx, got, err = s.ServiceNamesOfKind(nil, structs.ServiceKindTypical)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, idx, gotIdx)
|
||||||
|
require.Empty(t, got)
|
||||||
|
}
|
||||||
|
|
||||||
func runStep(t *testing.T, name string, fn func(t *testing.T)) {
|
func runStep(t *testing.T, name string, fn func(t *testing.T)) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if !t.Run(name, fn) {
|
if !t.Run(name, fn) {
|
||||||
|
|
|
@ -395,7 +395,7 @@ func validateProposedConfigEntryInGraph(
|
||||||
}
|
}
|
||||||
case structs.ServiceIntentions:
|
case structs.ServiceIntentions:
|
||||||
case structs.MeshConfig:
|
case structs.MeshConfig:
|
||||||
case structs.PartitionExports:
|
case structs.ExportedServices:
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unhandled kind %q during validation of %q", kindName.Kind, kindName.Name)
|
return fmt.Errorf("unhandled kind %q during validation of %q", kindName.Kind, kindName.Name)
|
||||||
}
|
}
|
||||||
|
@ -880,24 +880,21 @@ func readDiscoveryChainConfigEntriesTxn(
|
||||||
|
|
||||||
sid := structs.NewServiceID(serviceName, entMeta)
|
sid := structs.NewServiceID(serviceName, entMeta)
|
||||||
|
|
||||||
// Grab the proxy defaults if they exist.
|
// At every step we'll need service and proxy defaults.
|
||||||
idx, proxy, err := getProxyConfigEntryTxn(tx, ws, structs.ProxyConfigGlobal, overrides, entMeta)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
} else if proxy != nil {
|
|
||||||
res.GlobalProxy = proxy
|
|
||||||
}
|
|
||||||
|
|
||||||
// At every step we'll need service defaults.
|
|
||||||
todoDefaults[sid] = struct{}{}
|
todoDefaults[sid] = struct{}{}
|
||||||
|
|
||||||
|
var maxIdx uint64
|
||||||
|
|
||||||
// first fetch the router, of which we only collect 1 per chain eval
|
// first fetch the router, of which we only collect 1 per chain eval
|
||||||
_, router, err := getRouterConfigEntryTxn(tx, ws, serviceName, overrides, entMeta)
|
idx, router, err := getRouterConfigEntryTxn(tx, ws, serviceName, overrides, entMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
} else if router != nil {
|
} else if router != nil {
|
||||||
res.Routers[sid] = router
|
res.Routers[sid] = router
|
||||||
}
|
}
|
||||||
|
if idx > maxIdx {
|
||||||
|
maxIdx = idx
|
||||||
|
}
|
||||||
|
|
||||||
if router != nil {
|
if router != nil {
|
||||||
for _, svc := range router.ListRelatedServices() {
|
for _, svc := range router.ListRelatedServices() {
|
||||||
|
@ -922,10 +919,13 @@ func readDiscoveryChainConfigEntriesTxn(
|
||||||
// Yes, even for splitters.
|
// Yes, even for splitters.
|
||||||
todoDefaults[splitID] = struct{}{}
|
todoDefaults[splitID] = struct{}{}
|
||||||
|
|
||||||
_, splitter, err := getSplitterConfigEntryTxn(tx, ws, splitID.ID, overrides, &splitID.EnterpriseMeta)
|
idx, splitter, err := getSplitterConfigEntryTxn(tx, ws, splitID.ID, overrides, &splitID.EnterpriseMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
if idx > maxIdx {
|
||||||
|
maxIdx = idx
|
||||||
|
}
|
||||||
|
|
||||||
if splitter == nil {
|
if splitter == nil {
|
||||||
res.Splitters[splitID] = nil
|
res.Splitters[splitID] = nil
|
||||||
|
@ -959,10 +959,13 @@ func readDiscoveryChainConfigEntriesTxn(
|
||||||
// And resolvers, too.
|
// And resolvers, too.
|
||||||
todoDefaults[resolverID] = struct{}{}
|
todoDefaults[resolverID] = struct{}{}
|
||||||
|
|
||||||
_, resolver, err := getResolverConfigEntryTxn(tx, ws, resolverID.ID, overrides, &resolverID.EnterpriseMeta)
|
idx, resolver, err := getResolverConfigEntryTxn(tx, ws, resolverID.ID, overrides, &resolverID.EnterpriseMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
if idx > maxIdx {
|
||||||
|
maxIdx = idx
|
||||||
|
}
|
||||||
|
|
||||||
if resolver == nil {
|
if resolver == nil {
|
||||||
res.Resolvers[resolverID] = nil
|
res.Resolvers[resolverID] = nil
|
||||||
|
@ -987,16 +990,31 @@ func readDiscoveryChainConfigEntriesTxn(
|
||||||
continue // already fetched
|
continue // already fetched
|
||||||
}
|
}
|
||||||
|
|
||||||
_, entry, err := getServiceConfigEntryTxn(tx, ws, svcID.ID, overrides, &svcID.EnterpriseMeta)
|
if _, ok := res.ProxyDefaults[svcID.PartitionOrDefault()]; !ok {
|
||||||
|
idx, proxy, err := getProxyConfigEntryTxn(tx, ws, structs.ProxyConfigGlobal, overrides, &svcID.EnterpriseMeta)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
if idx > maxIdx {
|
||||||
|
maxIdx = idx
|
||||||
|
}
|
||||||
|
if proxy != nil {
|
||||||
|
res.ProxyDefaults[proxy.PartitionOrDefault()] = proxy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
idx, entry, err := getServiceConfigEntryTxn(tx, ws, svcID.ID, overrides, &svcID.EnterpriseMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
|
if idx > maxIdx {
|
||||||
|
maxIdx = idx
|
||||||
|
}
|
||||||
|
|
||||||
if entry == nil {
|
if entry == nil {
|
||||||
res.Services[svcID] = nil
|
res.Services[svcID] = nil
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
res.Services[svcID] = entry
|
res.Services[svcID] = entry
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1022,7 +1040,7 @@ func readDiscoveryChainConfigEntriesTxn(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return idx, res, nil
|
return maxIdx, res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// anyKey returns any key from the provided map if any exist. Useful for using
|
// anyKey returns any key from the provided map if any exist. Useful for using
|
||||||
|
|
|
@ -1347,6 +1347,13 @@ func entrySetToKindNames(entrySet *structs.DiscoveryChainConfigEntries) []Config
|
||||||
&entry.EnterpriseMeta,
|
&entry.EnterpriseMeta,
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
for _, entry := range entrySet.ProxyDefaults {
|
||||||
|
out = append(out, NewConfigEntryKindName(
|
||||||
|
entry.Kind,
|
||||||
|
entry.Name,
|
||||||
|
&entry.EnterpriseMeta,
|
||||||
|
))
|
||||||
|
}
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -995,36 +995,29 @@ func (s *Store) intentionTopologyTxn(tx ReadTxn, ws memdb.WatchSet,
|
||||||
maxIdx = index
|
maxIdx = index
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for a wildcard intention (* -> *) since it overrides the default decision from ACLs
|
// TODO(tproxy): One remaining improvement is that this includes non-Connect services (typical services without a proxy)
|
||||||
if len(intentions) > 0 {
|
// Ideally those should be excluded as well, since they can't be upstreams/downstreams without a proxy.
|
||||||
// Intentions with wildcard source and destination have the lowest precedence, so they are last in the list
|
// Maybe narrow serviceNamesOfKindTxn to services represented by proxies? (ingress, sidecar-proxy, terminating)
|
||||||
ixn := intentions[len(intentions)-1]
|
index, services, err := serviceNamesOfKindTxn(tx, ws, structs.ServiceKindTypical)
|
||||||
|
|
||||||
if ixn.HasWildcardSource() && ixn.HasWildcardDestination() {
|
|
||||||
defaultDecision = acl.Allow
|
|
||||||
if ixn.Action == structs.IntentionActionDeny {
|
|
||||||
defaultDecision = acl.Deny
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
index, allServices, err := serviceListTxn(tx, ws, func(svc *structs.ServiceNode) bool {
|
|
||||||
// Only include ingress gateways as downstreams, since they cannot receive service mesh traffic
|
|
||||||
// TODO(freddy): One remaining issue is that this includes non-Connect services (typical services without a proxy)
|
|
||||||
// Ideally those should be excluded as well, since they can't be upstreams/downstreams without a proxy.
|
|
||||||
// Maybe start tracking services represented by proxies? (both sidecar and ingress)
|
|
||||||
if svc.ServiceKind == structs.ServiceKindTypical || (svc.ServiceKind == structs.ServiceKindIngressGateway && downstreams) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}, target.WithWildcardNamespace())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return index, nil, fmt.Errorf("failed to fetch catalog service list: %v", err)
|
return index, nil, fmt.Errorf("failed to list ingress service names: %v", err)
|
||||||
}
|
}
|
||||||
if index > maxIdx {
|
if index > maxIdx {
|
||||||
maxIdx = index
|
maxIdx = index
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if downstreams {
|
||||||
|
// Ingress gateways can only ever be downstreams, since mesh services don't dial them.
|
||||||
|
index, ingress, err := serviceNamesOfKindTxn(tx, ws, structs.ServiceKindIngressGateway)
|
||||||
|
if err != nil {
|
||||||
|
return index, nil, fmt.Errorf("failed to list ingress service names: %v", err)
|
||||||
|
}
|
||||||
|
if index > maxIdx {
|
||||||
|
maxIdx = index
|
||||||
|
}
|
||||||
|
services = append(services, ingress...)
|
||||||
|
}
|
||||||
|
|
||||||
// When checking authorization to upstreams, the match type for the decision is `destination` because we are deciding
|
// When checking authorization to upstreams, the match type for the decision is `destination` because we are deciding
|
||||||
// if upstream candidates are covered by intentions that have the target service as a source.
|
// if upstream candidates are covered by intentions that have the target service as a source.
|
||||||
// The reverse is true for downstreams.
|
// The reverse is true for downstreams.
|
||||||
|
@ -1032,11 +1025,13 @@ func (s *Store) intentionTopologyTxn(tx ReadTxn, ws memdb.WatchSet,
|
||||||
if downstreams {
|
if downstreams {
|
||||||
decisionMatchType = structs.IntentionMatchSource
|
decisionMatchType = structs.IntentionMatchSource
|
||||||
}
|
}
|
||||||
result := make([]ServiceWithDecision, 0, len(allServices))
|
result := make([]ServiceWithDecision, 0, len(services))
|
||||||
for _, candidate := range allServices {
|
for _, svc := range services {
|
||||||
|
candidate := svc.Service
|
||||||
if candidate.Name == structs.ConsulServiceName {
|
if candidate.Name == structs.ConsulServiceName {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := IntentionDecisionOpts{
|
opts := IntentionDecisionOpts{
|
||||||
Target: candidate.Name,
|
Target: candidate.Name,
|
||||||
Namespace: candidate.NamespaceOrDefault(),
|
Namespace: candidate.NamespaceOrDefault(),
|
||||||
|
|
|
@ -40,6 +40,7 @@ func newDBSchema() *memdb.DBSchema {
|
||||||
tombstonesTableSchema,
|
tombstonesTableSchema,
|
||||||
usageTableSchema,
|
usageTableSchema,
|
||||||
freeVirtualIPTableSchema,
|
freeVirtualIPTableSchema,
|
||||||
|
kindServiceNameTableSchema,
|
||||||
)
|
)
|
||||||
withEnterpriseSchema(db)
|
withEnterpriseSchema(db)
|
||||||
return db
|
return db
|
||||||
|
|
|
@ -50,6 +50,7 @@ func TestNewDBSchema_Indexers(t *testing.T) {
|
||||||
tableMeshTopology: testIndexerTableMeshTopology,
|
tableMeshTopology: testIndexerTableMeshTopology,
|
||||||
tableGatewayServices: testIndexerTableGatewayServices,
|
tableGatewayServices: testIndexerTableGatewayServices,
|
||||||
tableServiceVirtualIPs: testIndexerTableServiceVirtualIPs,
|
tableServiceVirtualIPs: testIndexerTableServiceVirtualIPs,
|
||||||
|
tableKindServiceNames: testIndexerTableKindServiceNames,
|
||||||
// KV
|
// KV
|
||||||
tableKVs: testIndexerTableKVs,
|
tableKVs: testIndexerTableKVs,
|
||||||
tableTombstones: testIndexerTableTombstones,
|
tableTombstones: testIndexerTableTombstones,
|
||||||
|
|
|
@ -187,3 +187,7 @@ func (s *Store) SessionList(ws memdb.WatchSet, entMeta *structs.EnterpriseMeta)
|
||||||
func maxIndexTxnSessions(tx *memdb.Txn, _ *structs.EnterpriseMeta) uint64 {
|
func maxIndexTxnSessions(tx *memdb.Txn, _ *structs.EnterpriseMeta) uint64 {
|
||||||
return maxIndexTxn(tx, tableSessions)
|
return maxIndexTxn(tx, tableSessions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Store) SessionListAll(ws memdb.WatchSet) (uint64, structs.Sessions, error) {
|
||||||
|
return s.SessionList(ws, nil)
|
||||||
|
}
|
||||||
|
|
|
@ -319,7 +319,7 @@ func TestTxn_Apply_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
@ -838,7 +838,7 @@ func TestTxn_Read_ACLDeny(t *testing.T) {
|
||||||
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
dir1, s1 := testServerWithConfig(t, func(c *Config) {
|
||||||
c.PrimaryDatacenter = "dc1"
|
c.PrimaryDatacenter = "dc1"
|
||||||
c.ACLsEnabled = true
|
c.ACLsEnabled = true
|
||||||
c.ACLMasterToken = "root"
|
c.ACLInitialManagementToken = "root"
|
||||||
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
c.ACLResolverSettings.ACLDefaultPolicy = "deny"
|
||||||
})
|
})
|
||||||
defer os.RemoveAll(dir1)
|
defer os.RemoveAll(dir1)
|
||||||
|
|
|
@ -178,12 +178,12 @@ func TestUsageReporter_emitNodeUsage_OSS(t *testing.T) {
|
||||||
{Name: "kind", Value: "terminating-gateway"},
|
{Name: "kind", Value: "terminating-gateway"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=partition-exports": {
|
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": {
|
||||||
Name: "consul.usage.test.consul.state.config_entries",
|
Name: "consul.usage.test.consul.state.config_entries",
|
||||||
Value: 0,
|
Value: 0,
|
||||||
Labels: []metrics.Label{
|
Labels: []metrics.Label{
|
||||||
{Name: "datacenter", Value: "dc1"},
|
{Name: "datacenter", Value: "dc1"},
|
||||||
{Name: "kind", Value: "partition-exports"},
|
{Name: "kind", Value: "exported-services"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -363,12 +363,12 @@ func TestUsageReporter_emitNodeUsage_OSS(t *testing.T) {
|
||||||
{Name: "kind", Value: "terminating-gateway"},
|
{Name: "kind", Value: "terminating-gateway"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=partition-exports": {
|
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": {
|
||||||
Name: "consul.usage.test.consul.state.config_entries",
|
Name: "consul.usage.test.consul.state.config_entries",
|
||||||
Value: 0,
|
Value: 0,
|
||||||
Labels: []metrics.Label{
|
Labels: []metrics.Label{
|
||||||
{Name: "datacenter", Value: "dc1"},
|
{Name: "datacenter", Value: "dc1"},
|
||||||
{Name: "kind", Value: "partition-exports"},
|
{Name: "kind", Value: "exported-services"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -576,12 +576,12 @@ func TestUsageReporter_emitServiceUsage_OSS(t *testing.T) {
|
||||||
{Name: "kind", Value: "terminating-gateway"},
|
{Name: "kind", Value: "terminating-gateway"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=partition-exports": {
|
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": {
|
||||||
Name: "consul.usage.test.consul.state.config_entries",
|
Name: "consul.usage.test.consul.state.config_entries",
|
||||||
Value: 0,
|
Value: 0,
|
||||||
Labels: []metrics.Label{
|
Labels: []metrics.Label{
|
||||||
{Name: "datacenter", Value: "dc1"},
|
{Name: "datacenter", Value: "dc1"},
|
||||||
{Name: "kind", Value: "partition-exports"},
|
{Name: "kind", Value: "exported-services"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -803,12 +803,12 @@ func TestUsageReporter_emitServiceUsage_OSS(t *testing.T) {
|
||||||
{Name: "kind", Value: "terminating-gateway"},
|
{Name: "kind", Value: "terminating-gateway"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=partition-exports": {
|
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": {
|
||||||
Name: "consul.usage.test.consul.state.config_entries",
|
Name: "consul.usage.test.consul.state.config_entries",
|
||||||
Value: 0,
|
Value: 0,
|
||||||
Labels: []metrics.Label{
|
Labels: []metrics.Label{
|
||||||
{Name: "datacenter", Value: "dc1"},
|
{Name: "datacenter", Value: "dc1"},
|
||||||
{Name: "kind", Value: "partition-exports"},
|
{Name: "kind", Value: "exported-services"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1007,12 +1007,12 @@ func TestUsageReporter_emitKVUsage_OSS(t *testing.T) {
|
||||||
{Name: "kind", Value: "terminating-gateway"},
|
{Name: "kind", Value: "terminating-gateway"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=partition-exports": {
|
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": {
|
||||||
Name: "consul.usage.test.consul.state.config_entries",
|
Name: "consul.usage.test.consul.state.config_entries",
|
||||||
Value: 0,
|
Value: 0,
|
||||||
Labels: []metrics.Label{
|
Labels: []metrics.Label{
|
||||||
{Name: "datacenter", Value: "dc1"},
|
{Name: "datacenter", Value: "dc1"},
|
||||||
{Name: "kind", Value: "partition-exports"},
|
{Name: "kind", Value: "exported-services"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1201,12 +1201,12 @@ func TestUsageReporter_emitKVUsage_OSS(t *testing.T) {
|
||||||
{Name: "kind", Value: "terminating-gateway"},
|
{Name: "kind", Value: "terminating-gateway"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=partition-exports": {
|
"consul.usage.test.consul.state.config_entries;datacenter=dc1;kind=exported-services": {
|
||||||
Name: "consul.usage.test.consul.state.config_entries",
|
Name: "consul.usage.test.consul.state.config_entries",
|
||||||
Value: 0,
|
Value: 0,
|
||||||
Labels: []metrics.Label{
|
Labels: []metrics.Label{
|
||||||
{Name: "datacenter", Value: "dc1"},
|
{Name: "datacenter", Value: "dc1"},
|
||||||
{Name: "kind", Value: "partition-exports"},
|
{Name: "kind", Value: "exported-services"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -6224,11 +6224,18 @@ func TestDNS_ServiceLookup_FilterACL(t *testing.T) {
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run("ACLToken == "+tt.token, func(t *testing.T) {
|
t.Run("ACLToken == "+tt.token, func(t *testing.T) {
|
||||||
a := NewTestAgent(t, `
|
a := NewTestAgent(t, `
|
||||||
acl_token = "`+tt.token+`"
|
primary_datacenter = "dc1"
|
||||||
acl_master_token = "root"
|
|
||||||
acl_datacenter = "dc1"
|
acl {
|
||||||
acl_down_policy = "deny"
|
enabled = true
|
||||||
acl_default_policy = "deny"
|
default_policy = "deny"
|
||||||
|
down_policy = "deny"
|
||||||
|
|
||||||
|
tokens {
|
||||||
|
initial_management = "root"
|
||||||
|
default = "`+tt.token+`"
|
||||||
|
}
|
||||||
|
}
|
||||||
`)
|
`)
|
||||||
defer a.Shutdown()
|
defer a.Shutdown()
|
||||||
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
testrpc.WaitForLeader(t, a.RPC, "dc1")
|
||||||
|
|
|
@ -20,6 +20,7 @@ import (
|
||||||
"github.com/hashicorp/consul/ipaddr"
|
"github.com/hashicorp/consul/ipaddr"
|
||||||
"github.com/hashicorp/consul/sdk/freeport"
|
"github.com/hashicorp/consul/sdk/freeport"
|
||||||
"github.com/hashicorp/consul/tlsutil"
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
|
"github.com/hashicorp/consul/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// useTLSForDcAlwaysTrue tell GRPC to always return the TLS is enabled
|
// useTLSForDcAlwaysTrue tell GRPC to always return the TLS is enabled
|
||||||
|
@ -33,7 +34,7 @@ func TestNewDialer_WithTLSWrapper(t *testing.T) {
|
||||||
t.Cleanup(logError(t, lis.Close))
|
t.Cleanup(logError(t, lis.Close))
|
||||||
|
|
||||||
builder := resolver.NewServerResolverBuilder(newConfig(t))
|
builder := resolver.NewServerResolverBuilder(newConfig(t))
|
||||||
builder.AddServer(&metadata.Server{
|
builder.AddServer(types.AreaWAN, &metadata.Server{
|
||||||
Name: "server-1",
|
Name: "server-1",
|
||||||
ID: "ID1",
|
ID: "ID1",
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
|
@ -84,14 +85,14 @@ func TestNewDialer_WithALPNWrapper(t *testing.T) {
|
||||||
}()
|
}()
|
||||||
|
|
||||||
builder := resolver.NewServerResolverBuilder(newConfig(t))
|
builder := resolver.NewServerResolverBuilder(newConfig(t))
|
||||||
builder.AddServer(&metadata.Server{
|
builder.AddServer(types.AreaWAN, &metadata.Server{
|
||||||
Name: "server-1",
|
Name: "server-1",
|
||||||
ID: "ID1",
|
ID: "ID1",
|
||||||
Datacenter: "dc1",
|
Datacenter: "dc1",
|
||||||
Addr: lis1.Addr(),
|
Addr: lis1.Addr(),
|
||||||
UseTLS: true,
|
UseTLS: true,
|
||||||
})
|
})
|
||||||
builder.AddServer(&metadata.Server{
|
builder.AddServer(types.AreaWAN, &metadata.Server{
|
||||||
Name: "server-2",
|
Name: "server-2",
|
||||||
ID: "ID2",
|
ID: "ID2",
|
||||||
Datacenter: "dc2",
|
Datacenter: "dc2",
|
||||||
|
@ -150,10 +151,10 @@ func TestNewDialer_IntegrationWithTLSEnabledHandler(t *testing.T) {
|
||||||
}, hclog.New(nil))
|
}, hclog.New(nil))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
srv := newTestServer(t, "server-1", "dc1", tlsConf)
|
srv := newSimpleTestServer(t, "server-1", "dc1", tlsConf)
|
||||||
|
|
||||||
md := srv.Metadata()
|
md := srv.Metadata()
|
||||||
res.AddServer(md)
|
res.AddServer(types.AreaWAN, md)
|
||||||
t.Cleanup(srv.shutdown)
|
t.Cleanup(srv.shutdown)
|
||||||
|
|
||||||
pool := NewClientConnPool(ClientConnPoolConfig{
|
pool := NewClientConnPool(ClientConnPoolConfig{
|
||||||
|
@ -198,7 +199,7 @@ func TestNewDialer_IntegrationWithTLSEnabledHandler_viaMeshGateway(t *testing.T)
|
||||||
}, hclog.New(nil))
|
}, hclog.New(nil))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
srv := newTestServer(t, "bob", "dc1", tlsConf)
|
srv := newSimpleTestServer(t, "bob", "dc1", tlsConf)
|
||||||
|
|
||||||
// Send all of the traffic to dc1's server
|
// Send all of the traffic to dc1's server
|
||||||
var p tcpproxy.Proxy
|
var p tcpproxy.Proxy
|
||||||
|
@ -211,7 +212,7 @@ func TestNewDialer_IntegrationWithTLSEnabledHandler_viaMeshGateway(t *testing.T)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
md := srv.Metadata()
|
md := srv.Metadata()
|
||||||
res.AddServer(md)
|
res.AddServer(types.AreaWAN, md)
|
||||||
t.Cleanup(srv.shutdown)
|
t.Cleanup(srv.shutdown)
|
||||||
|
|
||||||
clientTLSConf, err := tlsutil.NewConfigurator(tlsutil.Config{
|
clientTLSConf, err := tlsutil.NewConfigurator(tlsutil.Config{
|
||||||
|
@ -265,8 +266,8 @@ func TestClientConnPool_IntegrationWithGRPCResolver_Failover(t *testing.T) {
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
name := fmt.Sprintf("server-%d", i)
|
name := fmt.Sprintf("server-%d", i)
|
||||||
srv := newTestServer(t, name, "dc1", nil)
|
srv := newSimpleTestServer(t, name, "dc1", nil)
|
||||||
res.AddServer(srv.Metadata())
|
res.AddServer(types.AreaWAN, srv.Metadata())
|
||||||
t.Cleanup(srv.shutdown)
|
t.Cleanup(srv.shutdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,7 +281,7 @@ func TestClientConnPool_IntegrationWithGRPCResolver_Failover(t *testing.T) {
|
||||||
first, err := client.Something(ctx, &testservice.Req{})
|
first, err := client.Something(ctx, &testservice.Req{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
res.RemoveServer(&metadata.Server{ID: first.ServerName, Datacenter: "dc1"})
|
res.RemoveServer(types.AreaWAN, &metadata.Server{ID: first.ServerName, Datacenter: "dc1"})
|
||||||
|
|
||||||
resp, err := client.Something(ctx, &testservice.Req{})
|
resp, err := client.Something(ctx, &testservice.Req{})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -301,8 +302,8 @@ func TestClientConnPool_ForwardToLeader_Failover(t *testing.T) {
|
||||||
var servers []testServer
|
var servers []testServer
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
name := fmt.Sprintf("server-%d", i)
|
name := fmt.Sprintf("server-%d", i)
|
||||||
srv := newTestServer(t, name, "dc1", nil)
|
srv := newSimpleTestServer(t, name, "dc1", nil)
|
||||||
res.AddServer(srv.Metadata())
|
res.AddServer(types.AreaWAN, srv.Metadata())
|
||||||
servers = append(servers, srv)
|
servers = append(servers, srv)
|
||||||
t.Cleanup(srv.shutdown)
|
t.Cleanup(srv.shutdown)
|
||||||
}
|
}
|
||||||
|
@ -351,8 +352,8 @@ func TestClientConnPool_IntegrationWithGRPCResolver_Rebalance(t *testing.T) {
|
||||||
|
|
||||||
for i := 0; i < count; i++ {
|
for i := 0; i < count; i++ {
|
||||||
name := fmt.Sprintf("server-%d", i)
|
name := fmt.Sprintf("server-%d", i)
|
||||||
srv := newTestServer(t, name, "dc1", nil)
|
srv := newSimpleTestServer(t, name, "dc1", nil)
|
||||||
res.AddServer(srv.Metadata())
|
res.AddServer(types.AreaWAN, srv.Metadata())
|
||||||
t.Cleanup(srv.shutdown)
|
t.Cleanup(srv.shutdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -405,8 +406,8 @@ func TestClientConnPool_IntegrationWithGRPCResolver_MultiDC(t *testing.T) {
|
||||||
|
|
||||||
for _, dc := range dcs {
|
for _, dc := range dcs {
|
||||||
name := "server-0-" + dc
|
name := "server-0-" + dc
|
||||||
srv := newTestServer(t, name, dc, nil)
|
srv := newSimpleTestServer(t, name, dc, nil)
|
||||||
res.AddServer(srv.Metadata())
|
res.AddServer(types.AreaWAN, srv.Metadata())
|
||||||
t.Cleanup(srv.shutdown)
|
t.Cleanup(srv.shutdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,29 +9,73 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
|
middleware "github.com/grpc-ecosystem/go-grpc-middleware"
|
||||||
|
recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery"
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewHandler returns a gRPC server that accepts connections from Handle(conn).
|
// NewHandler returns a gRPC server that accepts connections from Handle(conn).
|
||||||
// The register function will be called with the grpc.Server to register
|
// The register function will be called with the grpc.Server to register
|
||||||
// gRPC services with the server.
|
// gRPC services with the server.
|
||||||
func NewHandler(addr net.Addr, register func(server *grpc.Server)) *Handler {
|
func NewHandler(logger Logger, addr net.Addr, register func(server *grpc.Server)) *Handler {
|
||||||
metrics := defaultMetrics()
|
metrics := defaultMetrics()
|
||||||
|
|
||||||
// We don't need to pass tls.Config to the server since it's multiplexed
|
// We don't need to pass tls.Config to the server since it's multiplexed
|
||||||
// behind the RPC listener, which already has TLS configured.
|
// behind the RPC listener, which already has TLS configured.
|
||||||
srv := grpc.NewServer(
|
recoveryOpts := PanicHandlerMiddlewareOpts(logger)
|
||||||
|
|
||||||
|
opts := []grpc.ServerOption{
|
||||||
grpc.StatsHandler(newStatsHandler(metrics)),
|
grpc.StatsHandler(newStatsHandler(metrics)),
|
||||||
grpc.StreamInterceptor((&activeStreamCounter{metrics: metrics}).Intercept),
|
middleware.WithUnaryServerChain(
|
||||||
|
// Add middlware interceptors to recover in case of panics.
|
||||||
|
recovery.UnaryServerInterceptor(recoveryOpts...),
|
||||||
|
),
|
||||||
|
middleware.WithStreamServerChain(
|
||||||
|
// Add middlware interceptors to recover in case of panics.
|
||||||
|
recovery.StreamServerInterceptor(recoveryOpts...),
|
||||||
|
(&activeStreamCounter{metrics: metrics}).Intercept,
|
||||||
|
),
|
||||||
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
|
grpc.KeepaliveEnforcementPolicy(keepalive.EnforcementPolicy{
|
||||||
MinTime: 15 * time.Second,
|
MinTime: 15 * time.Second,
|
||||||
}),
|
}),
|
||||||
)
|
}
|
||||||
|
|
||||||
|
// We don't need to pass tls.Config to the server since it's multiplexed
|
||||||
|
// behind the RPC listener, which already has TLS configured.
|
||||||
|
srv := grpc.NewServer(opts...)
|
||||||
register(srv)
|
register(srv)
|
||||||
|
|
||||||
lis := &chanListener{addr: addr, conns: make(chan net.Conn), done: make(chan struct{})}
|
lis := &chanListener{addr: addr, conns: make(chan net.Conn), done: make(chan struct{})}
|
||||||
return &Handler{srv: srv, listener: lis}
|
return &Handler{srv: srv, listener: lis}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PanicHandlerMiddlewareOpts returns the []recovery.Option containing
|
||||||
|
// recovery handler function.
|
||||||
|
func PanicHandlerMiddlewareOpts(logger Logger) []recovery.Option {
|
||||||
|
return []recovery.Option{
|
||||||
|
recovery.WithRecoveryHandler(NewPanicHandler(logger)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPanicHandler returns a recovery.RecoveryHandlerFunc closure function
|
||||||
|
// to handle panic in GRPC server's handlers.
|
||||||
|
func NewPanicHandler(logger Logger) recovery.RecoveryHandlerFunc {
|
||||||
|
return func(p interface{}) (err error) {
|
||||||
|
// Log the panic and the stack trace of the Goroutine that caused the panic.
|
||||||
|
stacktrace := hclog.Stacktrace()
|
||||||
|
logger.Error("panic serving grpc request",
|
||||||
|
"panic", p,
|
||||||
|
"stack", stacktrace,
|
||||||
|
)
|
||||||
|
|
||||||
|
return status.Errorf(codes.Internal, "grpc: panic serving request")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handler implements a handler for the rpc server listener, and the
|
// Handler implements a handler for the rpc server listener, and the
|
||||||
// agent.Component interface for managing the lifecycle of the grpc.Server.
|
// agent.Component interface for managing the lifecycle of the grpc.Server.
|
||||||
type Handler struct {
|
type Handler struct {
|
||||||
|
|
|
@ -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"
|
"google.golang.org/grpc/resolver"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/metadata"
|
"github.com/hashicorp/consul/agent/metadata"
|
||||||
|
"github.com/hashicorp/consul/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ServerResolverBuilder tracks the current server list and keeps any
|
// ServerResolverBuilder tracks the current server list and keeps any
|
||||||
|
@ -18,9 +19,9 @@ type ServerResolverBuilder struct {
|
||||||
cfg Config
|
cfg Config
|
||||||
// leaderResolver is used to track the address of the leader in the local DC.
|
// leaderResolver is used to track the address of the leader in the local DC.
|
||||||
leaderResolver leaderResolver
|
leaderResolver leaderResolver
|
||||||
// servers is an index of Servers by Server.ID. The map contains server IDs
|
// servers is an index of Servers by area and Server.ID. The map contains server IDs
|
||||||
// for all datacenters.
|
// for all datacenters.
|
||||||
servers map[string]*metadata.Server
|
servers map[types.AreaID]map[string]*metadata.Server
|
||||||
// resolvers is an index of connections to the serverResolver which manages
|
// resolvers is an index of connections to the serverResolver which manages
|
||||||
// addresses of servers for that connection.
|
// addresses of servers for that connection.
|
||||||
resolvers map[resolver.ClientConn]*serverResolver
|
resolvers map[resolver.ClientConn]*serverResolver
|
||||||
|
@ -37,7 +38,7 @@ type Config struct {
|
||||||
func NewServerResolverBuilder(cfg Config) *ServerResolverBuilder {
|
func NewServerResolverBuilder(cfg Config) *ServerResolverBuilder {
|
||||||
return &ServerResolverBuilder{
|
return &ServerResolverBuilder{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
servers: make(map[string]*metadata.Server),
|
servers: make(map[types.AreaID]map[string]*metadata.Server),
|
||||||
resolvers: make(map[resolver.ClientConn]*serverResolver),
|
resolvers: make(map[resolver.ClientConn]*serverResolver),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,9 +73,11 @@ func (s *ServerResolverBuilder) ServerForGlobalAddr(globalAddr string) (*metadat
|
||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
for _, server := range s.servers {
|
for _, areaServers := range s.servers {
|
||||||
if DCPrefix(server.Datacenter, server.Addr.String()) == globalAddr {
|
for _, server := range areaServers {
|
||||||
return server, nil
|
if DCPrefix(server.Datacenter, server.Addr.String()) == globalAddr {
|
||||||
|
return server, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("failed to find Consul server for global address %q", globalAddr)
|
return nil, fmt.Errorf("failed to find Consul server for global address %q", globalAddr)
|
||||||
|
@ -138,11 +141,17 @@ func (s *ServerResolverBuilder) Authority() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddServer updates the resolvers' states to include the new server's address.
|
// AddServer updates the resolvers' states to include the new server's address.
|
||||||
func (s *ServerResolverBuilder) AddServer(server *metadata.Server) {
|
func (s *ServerResolverBuilder) AddServer(areaID types.AreaID, server *metadata.Server) {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
s.servers[uniqueID(server)] = server
|
areaServers, ok := s.servers[areaID]
|
||||||
|
if !ok {
|
||||||
|
areaServers = make(map[string]*metadata.Server)
|
||||||
|
s.servers[areaID] = areaServers
|
||||||
|
}
|
||||||
|
|
||||||
|
areaServers[uniqueID(server)] = server
|
||||||
|
|
||||||
addrs := s.getDCAddrs(server.Datacenter)
|
addrs := s.getDCAddrs(server.Datacenter)
|
||||||
for _, resolver := range s.resolvers {
|
for _, resolver := range s.resolvers {
|
||||||
|
@ -168,11 +177,19 @@ func DCPrefix(datacenter, suffix string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveServer updates the resolvers' states with the given server removed.
|
// RemoveServer updates the resolvers' states with the given server removed.
|
||||||
func (s *ServerResolverBuilder) RemoveServer(server *metadata.Server) {
|
func (s *ServerResolverBuilder) RemoveServer(areaID types.AreaID, server *metadata.Server) {
|
||||||
s.lock.Lock()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
delete(s.servers, uniqueID(server))
|
areaServers, ok := s.servers[areaID]
|
||||||
|
if !ok {
|
||||||
|
return // already gone
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(areaServers, uniqueID(server))
|
||||||
|
if len(areaServers) == 0 {
|
||||||
|
delete(s.servers, areaID)
|
||||||
|
}
|
||||||
|
|
||||||
addrs := s.getDCAddrs(server.Datacenter)
|
addrs := s.getDCAddrs(server.Datacenter)
|
||||||
for _, resolver := range s.resolvers {
|
for _, resolver := range s.resolvers {
|
||||||
|
@ -185,18 +202,29 @@ func (s *ServerResolverBuilder) RemoveServer(server *metadata.Server) {
|
||||||
// getDCAddrs returns a list of the server addresses for the given datacenter.
|
// getDCAddrs returns a list of the server addresses for the given datacenter.
|
||||||
// This method requires that lock is held for reads.
|
// This method requires that lock is held for reads.
|
||||||
func (s *ServerResolverBuilder) getDCAddrs(dc string) []resolver.Address {
|
func (s *ServerResolverBuilder) getDCAddrs(dc string) []resolver.Address {
|
||||||
var addrs []resolver.Address
|
var (
|
||||||
for _, server := range s.servers {
|
addrs []resolver.Address
|
||||||
if server.Datacenter != dc {
|
keptServerIDs = make(map[string]struct{})
|
||||||
continue
|
)
|
||||||
}
|
for _, areaServers := range s.servers {
|
||||||
|
for _, server := range areaServers {
|
||||||
|
if server.Datacenter != dc {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
addrs = append(addrs, resolver.Address{
|
// Servers may be part of multiple areas, so only include each one once.
|
||||||
// NOTE: the address persisted here is only dialable using our custom dialer
|
if _, ok := keptServerIDs[server.ID]; ok {
|
||||||
Addr: DCPrefix(server.Datacenter, server.Addr.String()),
|
continue
|
||||||
Type: resolver.Backend,
|
}
|
||||||
ServerName: server.Name,
|
keptServerIDs[server.ID] = struct{}{}
|
||||||
})
|
|
||||||
|
addrs = append(addrs, resolver.Address{
|
||||||
|
// NOTE: the address persisted here is only dialable using our custom dialer
|
||||||
|
Addr: DCPrefix(server.Datacenter, server.Addr.String()),
|
||||||
|
Type: resolver.Backend,
|
||||||
|
ServerName: server.Name,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return addrs
|
return addrs
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
"github.com/hashicorp/consul/agent/metadata"
|
"github.com/hashicorp/consul/agent/metadata"
|
||||||
"github.com/hashicorp/consul/agent/pool"
|
"github.com/hashicorp/consul/agent/pool"
|
||||||
"github.com/hashicorp/consul/tlsutil"
|
"github.com/hashicorp/consul/tlsutil"
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
)
|
)
|
||||||
|
|
||||||
type testServer struct {
|
type testServer struct {
|
||||||
|
@ -39,11 +40,22 @@ func (s testServer) Metadata() *metadata.Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func newTestServer(t *testing.T, name string, dc string, tlsConf *tlsutil.Configurator) testServer {
|
func newSimpleTestServer(t *testing.T, name, dc string, tlsConf *tlsutil.Configurator) testServer {
|
||||||
addr := &net.IPAddr{IP: net.ParseIP("127.0.0.1")}
|
return newTestServer(t, hclog.Default(), name, dc, tlsConf, func(server *grpc.Server) {
|
||||||
handler := NewHandler(addr, func(server *grpc.Server) {
|
|
||||||
testservice.RegisterSimpleServer(server, &simple{name: name, dc: dc})
|
testservice.RegisterSimpleServer(server, &simple{name: name, dc: dc})
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// newPanicTestServer sets up a simple server with handlers that panic.
|
||||||
|
func newPanicTestServer(t *testing.T, logger hclog.Logger, name, dc string, tlsConf *tlsutil.Configurator) testServer {
|
||||||
|
return newTestServer(t, logger, name, dc, tlsConf, func(server *grpc.Server) {
|
||||||
|
testservice.RegisterSimpleServer(server, &simplePanic{name: name, dc: dc})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestServer(t *testing.T, logger hclog.Logger, name, dc string, tlsConf *tlsutil.Configurator, register func(server *grpc.Server)) testServer {
|
||||||
|
addr := &net.IPAddr{IP: net.ParseIP("127.0.0.1")}
|
||||||
|
handler := NewHandler(logger, addr, register)
|
||||||
|
|
||||||
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
lis, err := net.Listen("tcp", "127.0.0.1:0")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -103,6 +115,23 @@ func (s *simple) Something(_ context.Context, _ *testservice.Req) (*testservice.
|
||||||
return &testservice.Resp{ServerName: s.name, Datacenter: s.dc}, nil
|
return &testservice.Resp{ServerName: s.name, Datacenter: s.dc}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type simplePanic struct {
|
||||||
|
name, dc string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *simplePanic) Flow(_ *testservice.Req, flow testservice.Simple_FlowServer) error {
|
||||||
|
for flow.Context().Err() == nil {
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
panic("panic from Flow")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *simplePanic) Something(_ context.Context, _ *testservice.Req) (*testservice.Resp, error) {
|
||||||
|
time.Sleep(time.Millisecond)
|
||||||
|
panic("panic from Something")
|
||||||
|
}
|
||||||
|
|
||||||
// fakeRPCListener mimics agent/consul.Server.listen to handle the RPCType byte.
|
// fakeRPCListener mimics agent/consul.Server.listen to handle the RPCType byte.
|
||||||
// In the future we should be able to refactor Server and extract this RPC
|
// In the future we should be able to refactor Server and extract this RPC
|
||||||
// handling logic so that we don't need to use a fake.
|
// handling logic so that we don't need to use a fake.
|
||||||
|
|
|
@ -15,6 +15,7 @@ import (
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"github.com/hashicorp/consul/agent/grpc/internal/testservice"
|
"github.com/hashicorp/consul/agent/grpc/internal/testservice"
|
||||||
|
"github.com/hashicorp/go-hclog"
|
||||||
)
|
)
|
||||||
|
|
||||||
func noopRegister(*grpc.Server) {}
|
func noopRegister(*grpc.Server) {}
|
||||||
|
@ -23,7 +24,7 @@ func TestHandler_EmitsStats(t *testing.T) {
|
||||||
sink, reset := patchGlobalMetrics(t)
|
sink, reset := patchGlobalMetrics(t)
|
||||||
|
|
||||||
addr := &net.IPAddr{IP: net.ParseIP("127.0.0.1")}
|
addr := &net.IPAddr{IP: net.ParseIP("127.0.0.1")}
|
||||||
handler := NewHandler(addr, noopRegister)
|
handler := NewHandler(hclog.Default(), addr, noopRegister)
|
||||||
reset()
|
reset()
|
||||||
|
|
||||||
testservice.RegisterSimpleServer(handler.srv, &simple{})
|
testservice.RegisterSimpleServer(handler.srv, &simple{})
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue