diff --git a/.changelog/20910.txt b/.changelog/20910.txt new file mode 100644 index 0000000000..2ec948ee6c --- /dev/null +++ b/.changelog/20910.txt @@ -0,0 +1,4 @@ +```release-note:security +Update `vault/api` to v1.12.2 to address [CVE-2024-28180](https://nvd.nist.gov/vuln/detail/CVE-2024-28180) +(removes indirect dependency on impacted `go-jose.v2`) +``` diff --git a/.changelog/21017.txt b/.changelog/21017.txt new file mode 100644 index 0000000000..0091047f7f --- /dev/null +++ b/.changelog/21017.txt @@ -0,0 +1,9 @@ +```release-note:security +Upgrade to support Envoy `1.27.5 and 1.28.3`. This resolves CVE +[CVE-2024-32475](https://nvd.nist.gov/vuln/detail/CVE-2024-32475) (`auto_sni`). +``` + +```release-note:security +Upgrade to support k8s.io/apimachinery `v0.18.7 or higher`. This resolves CVE +[CVE-2020-8559](https://nvd.nist.gov/vuln/detail/CVE-2020-8559). +``` diff --git a/.changelog/21074.txt b/.changelog/21074.txt new file mode 100644 index 0000000000..9b2bd21f42 --- /dev/null +++ b/.changelog/21074.txt @@ -0,0 +1,5 @@ +```release-note:security +Upgrade Go to use 1.21.10. This addresses CVEs +[CVE-2024-24787](https://nvd.nist.gov/vuln/detail/CVE-2024-24787) and +[CVE-2024-24788](https://nvd.nist.gov/vuln/detail/CVE-2024-24788) +``` diff --git a/.changelog/21113.txt b/.changelog/21113.txt new file mode 100644 index 0000000000..e1e153f0ac --- /dev/null +++ b/.changelog/21113.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +upgrade go version to v1.22.3. +``` diff --git a/.changelog/21142.txt b/.changelog/21142.txt new file mode 100644 index 0000000000..6119fb2674 --- /dev/null +++ b/.changelog/21142.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +mesh: update supported envoy version 1.29.4 in addition to 1.28.3, 1.27.5, 1.26.8. +``` \ No newline at end of file diff --git a/.changelog/21230.txt b/.changelog/21230.txt new file mode 100644 index 0000000000..5a57333afa --- /dev/null +++ b/.changelog/21230.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +dns: new version was not supporting partition or namespace being set to 'default' in CE version. +``` \ No newline at end of file diff --git a/.changelog/21251.txt b/.changelog/21251.txt new file mode 100644 index 0000000000..ff4ef7cf23 --- /dev/null +++ b/.changelog/21251.txt @@ -0,0 +1,3 @@ +```release-note:bug +core: Fix multiple incorrect type conversion for potential overflows +``` diff --git a/.changelog/21265.txt b/.changelog/21265.txt new file mode 100644 index 0000000000..fdf600e70c --- /dev/null +++ b/.changelog/21265.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +upgrade go version to v1.22.4. +``` diff --git a/.changelog/21277.txt b/.changelog/21277.txt new file mode 100644 index 0000000000..a14a5aab20 --- /dev/null +++ b/.changelog/21277.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +mesh: update supported envoy version 1.29.5 in addition to 1.28.4, 1.27.6. +``` \ No newline at end of file diff --git a/.changelog/21339.txt b/.changelog/21339.txt new file mode 100644 index 0000000000..8e8379ada0 --- /dev/null +++ b/.changelog/21339.txt @@ -0,0 +1,3 @@ +```release-note:bug +core: Fix panic runtime error on AliasCheck +``` diff --git a/.changelog/21342.txt b/.changelog/21342.txt new file mode 100644 index 0000000000..d2850bc4fd --- /dev/null +++ b/.changelog/21342.txt @@ -0,0 +1,3 @@ +```release-note:security +agent: removed reflected cross-site scripting vulnerability +``` \ No newline at end of file diff --git a/.changelog/21361.txt b/.changelog/21361.txt new file mode 100644 index 0000000000..5cfa906bcd --- /dev/null +++ b/.changelog/21361.txt @@ -0,0 +1,8 @@ +```release-note:bug +dns: Fix a regression where DNS tags using the standard lookup syntax, `tag.name.service.consul`, were being disregarded. +``` + +```release-note:bug +dns: Fix a regression where DNS SRV questions were returning duplicate hostnames instead of encoded IPs. +This affected Nomad integrations with Consul. +``` \ No newline at end of file diff --git a/.changelog/21378.txt b/.changelog/21378.txt new file mode 100644 index 0000000000..c9fd8820dc --- /dev/null +++ b/.changelog/21378.txt @@ -0,0 +1,3 @@ +```release-note:security +ui: Pin and namespace sub-module dependencies related to the Consul UI +``` diff --git a/.changelog/21381.txt b/.changelog/21381.txt new file mode 100644 index 0000000000..e69989f032 --- /dev/null +++ b/.changelog/21381.txt @@ -0,0 +1,4 @@ +```release-note:bug +dns: Fixes a spam log message "Failed to parse TTL for prepared query..." +that was always being logged on each prepared query evaluation. +``` \ No newline at end of file diff --git a/.changelog/21382.txt b/.changelog/21382.txt new file mode 100644 index 0000000000..dc59445371 --- /dev/null +++ b/.changelog/21382.txt @@ -0,0 +1,3 @@ +```release-note:bug +terminating-gateway: **(Enterprise Only)** Fixed issue where enterprise metadata applied to linked services was the terminating-gateways enterprise metadata and not the linked services enterprise metadata. +``` diff --git a/.changelog/21384.txt b/.changelog/21384.txt new file mode 100644 index 0000000000..c5ba2347e5 --- /dev/null +++ b/.changelog/21384.txt @@ -0,0 +1,3 @@ +```release-note:security +Upgrade go-retryablehttp to address [CVE-2024-6104](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-6104) +``` \ No newline at end of file diff --git a/.changelog/21507.txt b/.changelog/21507.txt new file mode 100644 index 0000000000..e06736fdbc --- /dev/null +++ b/.changelog/21507.txt @@ -0,0 +1,3 @@ +```release-note:security +Upgrade go version to 1.22.5 to address [CVE-2024-24791](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-24791) +``` \ No newline at end of file diff --git a/.changelog/21519.txt b/.changelog/21519.txt new file mode 100644 index 0000000000..2913443b1c --- /dev/null +++ b/.changelog/21519.txt @@ -0,0 +1,3 @@ +```release-note:bug +txn: Fix a bug where mismatched Consul server versions could result in undetected data loss for when using newer Transaction verbs. +``` diff --git a/.changelog/21524.txt b/.changelog/21524.txt new file mode 100644 index 0000000000..5f064e5c94 --- /dev/null +++ b/.changelog/21524.txt @@ -0,0 +1,3 @@ +```release-note:security +Upgrade envoy module dependencies to version 1.27.7, 1.28.5 and 1.29.7 or higher to resolve [CVE-2024-39305](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-39305) +``` diff --git a/.changelog/21587.txt b/.changelog/21587.txt new file mode 100644 index 0000000000..f83db38c95 --- /dev/null +++ b/.changelog/21587.txt @@ -0,0 +1,3 @@ +```release-note:improvement +Use Envoy's default for a route's validate_clusters option, which is false. This fixes a case where non-existent clusters could cause a route to no longer route to any of its backends, including existing ones. +``` diff --git a/.changelog/21588.txt b/.changelog/21588.txt new file mode 100644 index 0000000000..073901f88b --- /dev/null +++ b/.changelog/21588.txt @@ -0,0 +1,3 @@ +```release-note:security +ui: Upgrade modules with d3-color as a dependency to address denial of service issue in d3-color < 3.1.0 +``` \ No newline at end of file diff --git a/.changelog/21592.txt b/.changelog/21592.txt new file mode 100644 index 0000000000..a8a69f0d9b --- /dev/null +++ b/.changelog/21592.txt @@ -0,0 +1,3 @@ +```release-note:feature +server: remove v2 tenancy, catalog, and mesh experiments +``` diff --git a/.changelog/21604.txt b/.changelog/21604.txt new file mode 100644 index 0000000000..f544140343 --- /dev/null +++ b/.changelog/21604.txt @@ -0,0 +1,3 @@ +```release-note:bug +api-gateway: **(Enterprise only)** ensure clusters are properly created for JWT providers with a remote URI for the JWKS endpoint +``` diff --git a/.changelog/21616.txt b/.changelog/21616.txt new file mode 100644 index 0000000000..f26b47c711 --- /dev/null +++ b/.changelog/21616.txt @@ -0,0 +1,3 @@ +```release-note: improvement +connect: Add Envoy 1.31 and 1.30 to support matrix +``` diff --git a/.changelog/21684.txt b/.changelog/21684.txt new file mode 100644 index 0000000000..3702737cb0 --- /dev/null +++ b/.changelog/21684.txt @@ -0,0 +1,6 @@ +```release-note:security +Upgrade to support aws/aws-sdk-go `v1.55.5 or higher`. This resolves CVEs +[CVE-2020-8911](https://nvd.nist.gov/vuln/detail/cve-2020-8911) and +[CVE-2020-8912](https://nvd.nist.gov/vuln/detail/cve-2020-8912). +``` + diff --git a/.changelog/21703.txt b/.changelog/21703.txt new file mode 100644 index 0000000000..41d226e489 --- /dev/null +++ b/.changelog/21703.txt @@ -0,0 +1,3 @@ +```release-note:bug +jwt-provider: change dns lookup family from the default of AUTO which would prefer ipv6 to ALL if LOGICAL_DNS is used or PREFER_IPV4 if STRICT_DNS is used to gracefully handle transitions to ipv6. +``` diff --git a/.changelog/21704.txt b/.changelog/21704.txt new file mode 100644 index 0000000000..4e42741ebb --- /dev/null +++ b/.changelog/21704.txt @@ -0,0 +1,3 @@ +```release-note:security +Explicitly set 'Content-Type' header to mitigate XSS vulnerability. +``` \ No newline at end of file diff --git a/.changelog/21705.txt b/.changelog/21705.txt new file mode 100644 index 0000000000..e6ab5ba811 --- /dev/null +++ b/.changelog/21705.txt @@ -0,0 +1,4 @@ +```release-note:security +Upgrade Go to use 1.22.7. This addresses CVE +[CVE-2024-34155](https://nvd.nist.gov/vuln/detail/CVE-2024-34155) +``` \ No newline at end of file diff --git a/.changelog/21710.txt b/.changelog/21710.txt new file mode 100644 index 0000000000..d557407635 --- /dev/null +++ b/.changelog/21710.txt @@ -0,0 +1,3 @@ +```release-note:security +ui: Pin a newer resolution of Braces +``` diff --git a/.changelog/21711.txt b/.changelog/21711.txt new file mode 100644 index 0000000000..b3ab185a2a --- /dev/null +++ b/.changelog/21711.txt @@ -0,0 +1,3 @@ +```release-note:security +Implement HTML sanitization for user-generated content to prevent XSS attacks in the UI. +``` diff --git a/.changelog/21715.txt b/.changelog/21715.txt new file mode 100644 index 0000000000..1b94021932 --- /dev/null +++ b/.changelog/21715.txt @@ -0,0 +1,3 @@ +```release-note:security +ui: Pin a newer resolution of Codemirror +``` diff --git a/.changelog/21717.txt b/.changelog/21717.txt new file mode 100644 index 0000000000..2b51e64302 --- /dev/null +++ b/.changelog/21717.txt @@ -0,0 +1,3 @@ +```release-note:security +ui: Pin a newer resolution of Markdown-it +``` diff --git a/.changelog/21726.txt b/.changelog/21726.txt new file mode 100644 index 0000000000..20251e740c --- /dev/null +++ b/.changelog/21726.txt @@ -0,0 +1,3 @@ +```release-note:security +UI: Remove codemirror linting due to package dependency +``` diff --git a/.changelog/21729.txt b/.changelog/21729.txt new file mode 100644 index 0000000000..ce334fdfe9 --- /dev/null +++ b/.changelog/21729.txt @@ -0,0 +1,4 @@ +```release-notes:security +Bump Dockerfile base image to `alpine:3.20`. +This resolves CVE-2024-7264 and CVE-2024-8096 (curl). +``` diff --git a/.changelog/21735.txt b/.changelog/21735.txt new file mode 100644 index 0000000000..223b84b480 --- /dev/null +++ b/.changelog/21735.txt @@ -0,0 +1,3 @@ +```release-note:security +ui: Pin a newer resolution of ansi-html +``` diff --git a/.changelog/21750.txt b/.changelog/21750.txt new file mode 100644 index 0000000000..c30c08179b --- /dev/null +++ b/.changelog/21750.txt @@ -0,0 +1,3 @@ +```release-note:enhancement +security: upgrade ubi base image to 9.4 +``` diff --git a/.changelog/21758.txt b/.changelog/21758.txt new file mode 100644 index 0000000000..05f97e2c4b --- /dev/null +++ b/.changelog/21758.txt @@ -0,0 +1,7 @@ +```release-note:enhancement +raft: update raft library to 1.7.0 which include pre-vote extension +``` + +```release-note:enhancement +raft: add a configuration `raft_prevote_disabled` to allow disabling raft prevote +``` diff --git a/.changelog/21780.txt b/.changelog/21780.txt new file mode 100644 index 0000000000..72550e549a --- /dev/null +++ b/.changelog/21780.txt @@ -0,0 +1,3 @@ +```release-note:improvement +api: remove dependency on proto-public, protobuf, and grpc +``` diff --git a/.changelog/21806.txt b/.changelog/21806.txt new file mode 100644 index 0000000000..0f47c49bd9 --- /dev/null +++ b/.changelog/21806.txt @@ -0,0 +1,3 @@ +```release-note:feature +grafana: added the dashboards service-to-service dashboard, service dashboard, and consul dataplane dashboard +``` diff --git a/.changelog/21816.txt b/.changelog/21816.txt new file mode 100644 index 0000000000..40bc844bee --- /dev/null +++ b/.changelog/21816.txt @@ -0,0 +1,9 @@ +```release-note:security +mesh: Add `http.incoming.requestNormalization` to Mesh configuration entry to support inbound service traffic request normalization. This resolves [CVE-2024-10005](https://nvd.nist.gov/vuln/detail/CVE-2024-10005) and [CVE-2024-10006](https://nvd.nist.gov/vuln/detail/CVE-2024-10006). +``` +```release-note:security +mesh: Add `contains` and `ignoreCase` to L7 Intentions HTTP header matching criteria to support configuration resilient to variable casing and multiple values. This resolves [CVE-2024-10006](https://nvd.nist.gov/vuln/detail/CVE-2024-10006). +``` +```release-note:breaking-change +mesh: Enable Envoy `HttpConnectionManager.normalize_path` by default on inbound traffic to mesh proxies. This resolves [CVE-2024-10005](https://nvd.nist.gov/vuln/detail/CVE-2024-10005). +``` diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index c6c09d402a..8d5dd6dc4b 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -156,13 +156,16 @@ When you're ready to submit a pull request: 5. If there's any reason Consul users might need to know about this change, [add a changelog entry](../docs/contributing/add-a-changelog-entry.md). 6. Add labels to your pull request. A table of commonly use labels is below. - If you have any questions about which to apply, feel free to call it out in the PR or comments. - | Label | When to Use | - | --- | --- | - | `pr/no-changelog` | This PR does not have an intended changelog entry | + If you have any questions about which to apply, feel free to call it out in the PR or comments. Other labels may automatically be added by GitHub Actions CI. + + | Label | When to Use | + |----------------------| --- | + | `pr/no-changelog` | This PR does not have an intended changelog entry | + | `pr/no-backport` | This PR does not have an intended backport target | | `pr/no-metrics-test` | This PR does not require any testing for metrics | - | `backport/1.12.x` | Backport the changes in this PR to the targeted release branch. Consult the [Consul Release Notes](https://www.consul.io/docs/release-notes) page to view active releases. Website documentation merged to the latest release branch is deployed immediately | - Other labels may automatically be added by the Github Action CI. + | `backport/1.12.x` | Backport the changes in this PR to the targeted release branch. Consult the [Consul Release Notes](https://www.consul.io/docs/release-notes) page and [`versions.hcl`](/.release/versions.hcl) to view active releases. Website documentation merged to the latest release branch is deployed immediately. See [backport policy](#backport-policy) for more information. | + | `backport/all` | If contributing a bug fix or other change applicable to all branches, use `backport/all` to target all active branches automatically. See [backport policy](#backport-policy) for more information. | + 7. After you submit, the Consul maintainers team needs time to carefully review your contribution and ensure it is production-ready, considering factors such as: security, backwards-compatibility, potential regressions, etc. @@ -174,6 +177,10 @@ When you're ready to submit a pull request: Assuming the tests pass, the PR will be merged automatically. If the tests fail, it is you responsibility to resolve the issues with backports and request another reviewer. +### Backport Policy + +Consul is maintained as a Community Edition (CE) and an Enterprise product. Bug fixes and patches may be backported to the current major release in CE. In Enterprise, bug fixes and patches may be backported to all maintained releases: the N-2 releases and the 2 latest Long-Term Support (LTS) releases. For more information, refer to Consul’s [LTS documentation](https://developer.hashicorp.com/consul/docs/enterprise/long-term-support). + #### Checklists Some common changes that many PRs require are documented through checklists as diff --git a/.github/pr-labeler.yml b/.github/pr-labeler.yml index fd39f2ccab..10db39816d 100644 --- a/.github/pr-labeler.yml +++ b/.github/pr-labeler.yml @@ -2,45 +2,75 @@ # SPDX-License-Identifier: BUSL-1.1 pr/dependencies: - - vendor/**/* - - go.* + - changed-files: + - any-glob-to-any-file: + - vendor/**/* + - go.* theme/acls: - - acl/**/* + - changed-files: + - any-glob-to-any-file: + - acl/**/* theme/agent-cache: - - agent/cache/**/* + - changed-files: + - any-glob-to-any-file: + - agent/cache/**/* theme/api: - - api/**/* + - changed-files: + - any-glob-to-any-file: + - api/**/* theme/catalog: - - agent/catalog/**/* + - changed-files: + - any-glob-to-any-file: + - agent/catalog/**/* theme/certificates: - - tlsutil/**/* + - changed-files: + - any-glob-to-any-file: + - tlsutil/**/* theme/cli: - - command/**/* + - changed-files: + - any-glob-to-any-file: + - command/**/* theme/config: - - agent/config/**/* + - changed-files: + - any-glob-to-any-file: + - agent/config/**/* theme/connect: - - connect/**/* - - agent/connect/**/* + - changed-files: + - any-glob-to-any-file: + - connect/**/* + - agent/connect/**/* # theme/consul-nomad: theme/consul-terraform-sync: - - website/content/docs/nia/**/* - - website/content/docs/integrate/nia* + - changed-files: + - any-glob-to-any-file: + - website/content/docs/nia/**/* + - website/content/docs/integrate/nia* # theme/consul-vault: theme/contributing: - - .github/**/* + - changed-files: + - any-glob-to-any-file: + - .github/**/* theme/dns: - - dns/**/* + - changed-files: + - any-glob-to-any-file: + - dns/**/* theme/envoy/xds: - - agent/xds/**/* + - changed-files: + - any-glob-to-any-file: + - agent/xds/**/* # theme/federation-usability: theme/health-checks: - - agent/health* - - api/health* + - changed-files: + - any-glob-to-any-file: + - agent/health* + - api/health* # theme/ingress-gw: # theme/internal-cleanup: theme/internals: - - lib/**/* - - types/**/* + - changed-files: + - any-glob-to-any-file: + - lib/**/* + - types/**/* # theme/kubernetes: # theme/mesh-gw: # theme/operator-usability: @@ -48,19 +78,31 @@ theme/internals: # theme/service-metadata: # theme/streaming: theme/telemetry: - - logging/**/* + - changed-files: + - any-glob-to-any-file: + - logging/**/* # theme/terminating-gw: theme/testing: - - ./*test*/**/* + - changed-files: + - any-glob-to-any-file: + - ./*test*/**/* theme/tls: - - tlsutil/**/* + - changed-files: + - any-glob-to-any-file: + - tlsutil/**/* theme/ui: - - ui/**/* + - changed-files: + - any-glob-to-any-file: + - ui/**/* # theme/windows: # thinking: # type/bug: type/ci: - - .github/workflows/* + - changed-files: + - any-glob-to-any-file: + - .github/workflows/* # type/crash: type/docs: - - website/**/* + - changed-files: + - any-glob-to-any-file: + - website/**/* diff --git a/.github/scripts/filter_changed_files_go_test.sh b/.github/scripts/filter_changed_files_go_test.sh deleted file mode 100755 index bdd0e3f77f..0000000000 --- a/.github/scripts/filter_changed_files_go_test.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: BUSL-1.1 - -set -euo pipefail - -# Get the list of changed files -# Using `git merge-base` ensures that we're always comparing against the correct branch point. -#For example, given the commits: -# -# A---B---C---D---W---X---Y---Z # origin/main -# \---E---F # feature/branch -# -# ... `git merge-base origin/$SKIP_CHECK_BRANCH HEAD` would return commit `D` -# `...HEAD` specifies from the common ancestor to the latest commit on the current branch (HEAD).. -files_to_check=$(git diff --name-only "$(git merge-base origin/$SKIP_CHECK_BRANCH HEAD~)"...HEAD) - -# Define the directories to check -skipped_directories=("docs/" "ui/" "website/" "grafana/") - -# Loop through the changed files and find directories/files outside the skipped ones -for file_to_check in "${files_to_check[@]}"; do - file_is_skipped=false - for dir in "${skipped_directories[@]}"; do - if [[ "$file_to_check" == "$dir"* ]] || [[ "$file_to_check" == *.md && "$dir" == *"/" ]]; then - file_is_skipped=true - break - fi - done - if [ "$file_is_skipped" != "true" ]; then - echo -e $file_to_check - SKIP_CI=false - echo "Changes detected in non-documentation files - skip-ci: $SKIP_CI" - echo "skip-ci=$SKIP_CI" >> "$GITHUB_OUTPUT" - exit 0 ## if file is outside of the skipped_directory exit script - fi -done - -echo -e "$files_to_check" -SKIP_CI=true -echo "Changes detected in only documentation files - skip-ci: $SKIP_CI" -echo "skip-ci=$SKIP_CI" >> "$GITHUB_OUTPUT" \ No newline at end of file diff --git a/.github/scripts/goldenfile_checker.sh b/.github/scripts/goldenfile_checker.sh new file mode 100755 index 0000000000..0bdcfa35d0 --- /dev/null +++ b/.github/scripts/goldenfile_checker.sh @@ -0,0 +1,21 @@ +#!/bin/bash +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +set -euo pipefail + +# check if there is a diff in the xds testdata directory after running `make envoy-regen` +echo "regenerating xds files" +make envoy-regen + +echo "calculating changed files" +changed_xds_files=$(git --no-pager diff --name-only HEAD "$(git merge-base HEAD "origin/$GITHUB_BRANCH_REF")" | egrep "agent/xds/testdata/.*" || true) +# If we do not find a file in .changelog/, we fail the check +if [ -z "$changed_xds_files" ]; then + # pass status check if no changes were found for xds files + echo "Found no changes to xds golden files" + exit 0 +else + echo "Found diffs with xds golden files run 'make envoy-regen' to update them and check that output is expected" + exit 0 +fi diff --git a/.github/scripts/verify_artifact.sh b/.github/scripts/verify_artifact.sh index 3aa9e0848d..cb94726571 100755 --- a/.github/scripts/verify_artifact.sh +++ b/.github/scripts/verify_artifact.sh @@ -102,7 +102,8 @@ function verify_rpm { ${docker_image} \ /scripts/verify_rpm.sh \ "/workdir/${artifact_path}" \ - "${expect_version}" + "${expect_version}" \ + "${docker_image}" } # Arguments: diff --git a/.github/scripts/verify_rpm.sh b/.github/scripts/verify_rpm.sh index 96cd658eef..844f6a86bd 100755 --- a/.github/scripts/verify_rpm.sh +++ b/.github/scripts/verify_rpm.sh @@ -10,6 +10,10 @@ set -euo pipefail # report why it failed. This is meant to be run as part of the build workflow to verify the built # .rpm meets some basic criteria for validity. +# Notably, CentOS 7 is EOL, so we need to point to the vault for updates. It's not clear what alternative +# we may use in the future that supports linux/386 as the platform was dropped in CentOS 8+9. The docker_image +# is passed in as the third argument so that the script can determine if it needs to point to the vault for updates. + # set this so we can locate and execute the verify_bin.sh script for verifying version output SCRIPT_DIR="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )" @@ -20,6 +24,7 @@ function usage { function main { local rpm_path="${1:-}" local expect_version="${2:-}" + local docker_image="${3:-}" local got_version if [[ -z "${rpm_path}" ]]; then @@ -34,6 +39,12 @@ function main { exit 1 fi + if [[ -z "${docker_image}" ]]; then + echo "ERROR: docker image argument is required" + usage + exit 1 + fi + # expand globs for path names, if this fails, the script will exit rpm_path=$(echo ${rpm_path}) @@ -43,6 +54,12 @@ function main { exit 1 fi + # CentOS 7 is EOL, so we need to point to the vault for updates + if [[ "$docker_image" == *centos:7 ]]; then + sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* + sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* + fi + yum -y clean all yum -y update yum -y install which openssl diff --git a/.github/workflows/backport-assistant.yml b/.github/workflows/backport-assistant.yml index 78125b6dab..d004f220bf 100644 --- a/.github/workflows/backport-assistant.yml +++ b/.github/workflows/backport-assistant.yml @@ -19,7 +19,7 @@ jobs: backport: if: github.event.pull_request.merged runs-on: ubuntu-latest - container: hashicorpdev/backport-assistant:0.3.4 + container: hashicorpdev/backport-assistant:0.4.4 steps: - name: Run Backport Assistant for release branches run: | @@ -27,11 +27,24 @@ jobs: env: BACKPORT_LABEL_REGEXP: "backport/(?P\\d+\\.\\d+)" BACKPORT_TARGET_TEMPLATE: "release/{{.target}}.x" - GITHUB_TOKEN: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ secrets.ELEVATED_GITHUB_TOKEN_WORKFLOW }} + ENABLE_VERSION_MANIFESTS: true + backport-ent: + if: github.event.pull_request.merged && contains(join(github.event.pull_request.labels.*.name), 'backport/ent') + runs-on: ubuntu-latest + steps: + - name: Trigger backport for Enterprise + uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 + with: + token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + repository: hashicorp/consul-enterprise + event-type: ent-backport + client-payload: ${{ toJson(github.event) }} handle-failure: needs: - backport - if: always() && needs.backport.result == 'failure' + - backport-ent + if: always() && (needs.backport.result == 'failure' || needs.backport-ent.result == 'failure') runs-on: ubuntu-latest steps: - name: Comment on PR @@ -41,3 +54,4 @@ jobs: -X POST \ -d "{ \"body\": \"${github_message}\"}" \ "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${{ github.event.pull_request.number }}/comments" + diff --git a/.github/workflows/bot-auto-approve.yaml b/.github/workflows/bot-auto-approve.yaml index 911fc27f46..5851dbb1cb 100644 --- a/.github/workflows/bot-auto-approve.yaml +++ b/.github/workflows/bot-auto-approve.yaml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest if: github.actor == 'hc-github-team-consul-core' steps: - - uses: hmarr/auto-approve-action@44888193675f29a83e04faf4002fa8c0b537b1e4 # v3.2.1 + - uses: hmarr/auto-approve-action@f0939ea97e9205ef24d872e76833fa908a770363 # v4.0.0 with: review-message: "Auto approved Consul Bot automated PR" github-token: ${{ secrets.MERGE_APPROVE_TOKEN }} diff --git a/.github/workflows/broken-link-check.yml b/.github/workflows/broken-link-check.yml index d3dddccae7..e9b82a22e3 100644 --- a/.github/workflows/broken-link-check.yml +++ b/.github/workflows/broken-link-check.yml @@ -12,11 +12,11 @@ jobs: linkChecker: runs-on: ubuntu-latest steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Run lychee link checker id: lychee - uses: lycheeverse/lychee-action@ec3ed119d4f44ad2673a7232460dc7dff59d2421 # v1.8.0 + uses: lycheeverse/lychee-action@2b973e86fc7b1f6b36a93795fe2c9c6ae1118621 # v1.10.0 with: args: ./website/content/docs/ --base https://developer.hashicorp.com/ --exclude-all-private --exclude '\.(svg|gif|jpg|png)' --exclude 'manage\.auth0\.com' --accept 403 --max-concurrency=24 --no-progress --verbose # Fail GitHub action when broken links are found? @@ -26,7 +26,7 @@ jobs: - name: Create GitHub Issue From lychee output file if: env.lychee_exit_code != 0 - uses: peter-evans/create-issue-from-file@433e51abf769039ee20ba1293a088ca19d573b7f # v4.0.1 + uses: peter-evans/create-issue-from-file@24452a72d85239eacf1468b0f1982a9f3fec4c94 # v5.0.0 with: title: Link Checker Report content-filepath: ./lychee/out.md diff --git a/.github/workflows/build-artifacts.yml b/.github/workflows/build-artifacts.yml index 3c4fb7e669..3c8bb35fc9 100644 --- a/.github/workflows/build-artifacts.yml +++ b/.github/workflows/build-artifacts.yml @@ -25,7 +25,7 @@ jobs: compute-large: ${{ steps.setup-outputs.outputs.compute-large }} compute-xl: ${{ steps.setup-outputs.outputs.compute-xl }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - id: setup-outputs name: Setup outputs run: ./.github/scripts/get_runner_classes.sh @@ -52,7 +52,7 @@ jobs: - name: Fetch Secrets if: ${{ endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -61,14 +61,14 @@ jobs: kv/data/github/${{ github.repository }}/dockerhub username | DOCKERHUB_USERNAME; kv/data/github/${{ github.repository }}/dockerhub token | DOCKERHUB_TOKEN; - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: ENT specific step as we need to set elevated GitHub permissions. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} @@ -83,17 +83,17 @@ jobs: echo "GITHUB_BUILD_URL=${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" >> $GITHUB_ENV - name: Set up Docker Buildx - uses: docker/setup-buildx-action@2a1a44ac4aa01993040736bd95bb470da1a38365 # v2.9.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 # NOTE: conditional specific logic as we store secrets in Vault in ENT and use GHA secrets in CE. - name: Login to Docker Hub - uses: docker/login-action@465a07811f14bebb1938fbed4728c6a1ff8901fc # v2.2.0 + uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20 # v3.1.0 with: username: ${{ endsWith(github.repository, '-enterprise') && steps.secrets.outputs.DOCKERHUB_USERNAME || secrets.DOCKERHUB_USERNAME }} password: ${{ endsWith(github.repository, '-enterprise') && steps.secrets.outputs.DOCKERHUB_TOKEN || secrets.DOCKERHUB_TOKEN }} - name: Docker build and push - uses: docker/build-push-action@2eb1c1961a95fc15694676618e422e8ba1d63825 # v4.1.1 + uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0 # v5.3.0 with: context: ./bin file: ./build-support/docker/Consul-Dev.dockerfile diff --git a/.github/workflows/build-distros.yml b/.github/workflows/build-distros.yml index 4930d77e36..3b29a4e47c 100644 --- a/.github/workflows/build-distros.yml +++ b/.github/workflows/build-distros.yml @@ -31,7 +31,7 @@ jobs: compute-large: ${{ steps.setup-outputs.outputs.compute-large }} compute-xl: ${{ steps.setup-outputs.outputs.compute-xl }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - id: setup-outputs name: Setup outputs run: ./.github/scripts/get_runner_classes.sh @@ -60,14 +60,14 @@ jobs: XC_OS: "freebsd linux windows" runs-on: ${{ fromJSON(needs.setup.outputs.compute-xl) }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - name: Build @@ -85,14 +85,14 @@ jobs: XC_OS: "darwin freebsd linux solaris windows" runs-on: ${{ fromJSON(needs.setup.outputs.compute-xl) }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - name: Build @@ -111,7 +111,7 @@ jobs: CGO_ENABLED: 1 GOOS: linux steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git @@ -119,7 +119,7 @@ jobs: run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: | @@ -138,13 +138,13 @@ jobs: - check-go-mod runs-on: ${{ fromJSON(needs.setup.outputs.compute-xl) }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - name: Build diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0047d809b2..78481f2a20 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,12 +30,12 @@ jobs: pre-version: ${{ steps.set-product-version.outputs.prerelease-product-version }} shared-ldflags: ${{ steps.shared-ldflags.outputs.shared-ldflags }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # action-set-product-version implicitly sets fields like 'product-version' using version/VERSION # https://github.com/hashicorp/actions-set-product-version - name: set product version id: set-product-version - uses: hashicorp/actions-set-product-version@v1 + uses: hashicorp/actions-set-product-version@v2 - name: get product version id: get-product-version run: | @@ -70,7 +70,7 @@ jobs: filepath: ${{ steps.generate-metadata-file.outputs.filepath }} steps: - name: 'Checkout directory' - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Generate metadata file id: generate-metadata-file uses: hashicorp/actions-generate-metadata@v1 @@ -78,7 +78,7 @@ jobs: version: ${{ needs.set-product-version.outputs.product-version }} product: ${{ env.PKG_NAME }} - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: metadata.json path: ${{ steps.generate-metadata-file.outputs.filepath }} @@ -95,6 +95,8 @@ jobs: - {goos: "linux", goarch: "amd64"} - {goos: "linux", goarch: "arm"} - {goos: "linux", goarch: "arm64"} + - {goos: "darwin", goarch: "amd64"} + - {goos: "darwin", goarch: "arm64"} - {goos: "freebsd", goarch: "386"} - {goos: "freebsd", goarch: "amd64"} - {goos: "windows", goarch: "386"} @@ -104,10 +106,10 @@ jobs: name: Go ${{ needs.get-go-version.outputs.go-version }} ${{ matrix.goos }} ${{ matrix.goarch }} build steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Setup with node and yarn - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '18' cache: 'yarn' @@ -132,17 +134,27 @@ jobs: PRERELEASE_VERSION: ${{ needs.set-product-version.outputs.pre-version }} CGO_ENABLED: "0" GOLDFLAGS: "${{needs.set-product-version.outputs.shared-ldflags}}" - uses: hashicorp/actions-go-build@v0.1.7 + uses: hashicorp/actions-go-build@make-clean-flag-optional with: product_name: ${{ env.PKG_NAME }} product_version: ${{ needs.set-product-version.outputs.product-version }} go_version: ${{ needs.get-go-version.outputs.go-version }} os: ${{ matrix.goos }} arch: ${{ matrix.goarch }} - reproducible: report + reproducible: nope + clean: false instructions: |- + cp LICENSE $TARGET_DIR/LICENSE.txt go build -ldflags="$GOLDFLAGS" -o "$BIN_PATH" -trimpath -buildvcs=false + - name: Copy license file + if: ${{ !endsWith(github.repository, '-enterprise') }} + env: + LICENSE_DIR: ".release/linux/package/usr/share/doc/${{ env.PKG_NAME }}" + run: | + mkdir -p "$LICENSE_DIR" + cp LICENSE "$LICENSE_DIR/LICENSE.txt" + - name: Package if: ${{ matrix.goos == 'linux' }} uses: hashicorp/actions-packaging-linux@v1 @@ -153,7 +165,7 @@ jobs: version: ${{ needs.set-product-version.outputs.product-version }} maintainer: "HashiCorp" homepage: "https://github.com/hashicorp/consul" - license: "MPL-2.0" + license: "BSL-1.1" binary: "dist/${{ env.PKG_NAME }}" deb_depends: "openssl" rpm_depends: "openssl" @@ -169,13 +181,13 @@ jobs: echo "RPM_PACKAGE=$(basename out/*.rpm)" >> $GITHUB_ENV echo "DEB_PACKAGE=$(basename out/*.deb)" >> $GITHUB_ENV - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: ${{ matrix.goos == 'linux' }} with: name: ${{ env.RPM_PACKAGE }} path: out/${{ env.RPM_PACKAGE }} - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: ${{ matrix.goos == 'linux' }} with: name: ${{ env.DEB_PACKAGE }} @@ -195,10 +207,10 @@ jobs: name: Go ${{ needs.get-go-version.outputs.go-version }} ${{ matrix.goos }} ${{ matrix.goarch }} build steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Setup with node and yarn - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '18' cache: 'yarn' @@ -223,67 +235,19 @@ jobs: PRERELEASE_VERSION: ${{ needs.set-product-version.outputs.pre-version }} CGO_ENABLED: "0" GOLDFLAGS: "${{needs.set-product-version.outputs.shared-ldflags}}" - uses: hashicorp/actions-go-build@v0.1.7 + uses: hashicorp/actions-go-build@make-clean-flag-optional with: product_name: ${{ env.PKG_NAME }} product_version: ${{ needs.set-product-version.outputs.product-version }} go_version: ${{ needs.get-go-version.outputs.go-version }} os: ${{ matrix.goos }} arch: ${{ matrix.goarch }} - reproducible: report + reproducible: nope + clean: false instructions: |- + cp LICENSE $TARGET_DIR/LICENSE.txt go build -ldflags="$GOLDFLAGS" -o "$BIN_PATH" -trimpath -buildvcs=false - build-darwin: - needs: - - set-product-version - - get-go-version - runs-on: macos-latest - strategy: - matrix: - goos: [ darwin ] - goarch: [ "amd64", "arm64" ] - fail-fast: true - - name: Go ${{ needs.get-go-version.outputs.go-version }} ${{ matrix.goos }} ${{ matrix.goarch }} build - steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - - - name: Setup with node and yarn - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 - with: - node-version: '18' - cache: 'yarn' - cache-dependency-path: 'ui/yarn.lock' - - - name: Build UI - run: | - CONSUL_VERSION=${{ needs.set-product-version.outputs.product-version }} - CONSUL_BINARY_TYPE=${CONSUL_BINARY_TYPE} - CONSUL_COPYRIGHT_YEAR=$(git show -s --format=%cd --date=format:%Y HEAD) - echo "consul_version is ${CONSUL_VERSION}" - echo "consul binary type is ${CONSUL_BINARY_TYPE}" - echo "consul copyright year is ${CONSUL_COPYRIGHT_YEAR}" - cd ui && make && cd .. - rm -rf agent/uiserver/dist - mv ui/packages/consul-ui/dist agent/uiserver/ - - name: Go Build - env: - PRODUCT_VERSION: ${{ needs.set-product-version.outputs.product-version }} - PRERELEASE_VERSION: ${{ needs.set-product-version.outputs.pre-version }} - CGO_ENABLED: "0" - GOLDFLAGS: "${{needs.set-product-version.outputs.shared-ldflags}}" - uses: hashicorp/actions-go-build@v0.1.7 - with: - product_name: ${{ env.PKG_NAME }} - product_version: ${{ needs.set-product-version.outputs.product-version }} - go_version: ${{ needs.get-go-version.outputs.go-version }} - os: ${{ matrix.goos }} - arch: ${{ matrix.goarch }} - reproducible: report - instructions: |- - go build -ldflags="$GOLDFLAGS" -tags netcgo -o "$BIN_PATH" -trimpath -buildvcs=false - build-docker: name: Docker ${{ matrix.arch }} build needs: @@ -302,7 +266,7 @@ jobs: version: ${{needs.set-product-version.outputs.product-version}} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # Strip everything but MAJOR.MINOR from the version string and add a `-dev` suffix # This naming convention will be used ONLY for per-commit dev images @@ -314,7 +278,7 @@ jobs: echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" >> $GITHUB_ENV - name: Docker Build (Action) - uses: hashicorp/actions-docker-build@v1 + uses: hashicorp/actions-docker-build@v2 with: version: ${{env.version}} target: default @@ -340,7 +304,7 @@ jobs: version: ${{needs.set-product-version.outputs.product-version}} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # Strip everything but MAJOR.MINOR from the version string and add a `-dev` suffix # This naming convention will be used ONLY for per-commit dev images @@ -351,7 +315,7 @@ jobs: echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" echo "minor_dev_tag=$(echo ${{ env.version }}| sed -E 's/([0-9]+\.[0-9]+)\.[0-9]+(-[0-9a-zA-Z\+\.]+)?$/\1\2/')" >> $GITHUB_ENV - - uses: hashicorp/actions-docker-build@v1 + - uses: hashicorp/actions-docker-build@v2 with: version: ${{env.version}} target: ubi @@ -386,17 +350,17 @@ jobs: name: Verify ${{ matrix.arch }} linux binary steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 if: ${{ endsWith(github.repository, '-enterprise') || matrix.arch != 's390x' }} - name: Download ${{ matrix.arch }} zip if: ${{ endsWith(github.repository, '-enterprise') || matrix.arch != 's390x' }} - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: ${{ env.zip_name }} - name: Set up QEMU - uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 if: ${{ matrix.arch == 'arm' || matrix.arch == 'arm64' }} with: # this should be a comma-separated string as opposed to an array @@ -406,32 +370,6 @@ jobs: if: ${{ endsWith(github.repository, '-enterprise') || matrix.arch != 's390x' }} run: .github/scripts/verify_artifact.sh ${{ env.zip_name }} v${{ env.version }} - verify-darwin: - needs: - - set-product-version - - build-darwin - runs-on: macos-latest - strategy: - fail-fast: true - env: - version: ${{needs.set-product-version.outputs.product-version}} - zip_name: consul_${{ needs.set-product-version.outputs.product-version }}_darwin_amd64.zip - - name: Verify amd64 darwin binary - steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - - - name: Download amd64 darwin zip - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 - with: - name: ${{ env.zip_name }} - - - name: Unzip amd64 darwin zip - run: unzip ${{ env.zip_name }} - - - name: Run verification for amd64 darwin binary - run: .github/scripts/verify_bin.sh ./consul v${{ env.version }} - verify-linux-packages-deb: needs: - build @@ -450,7 +388,7 @@ jobs: name: Verify ${{ matrix.arch }} debian package steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Set package version run: | @@ -461,12 +399,12 @@ jobs: echo "pkg_name=consul_${{ env.pkg_version }}-1_${{ matrix.arch }}.deb" >> $GITHUB_ENV - name: Download workflow artifacts - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: ${{ env.pkg_name }} - name: Set up QEMU - uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 with: platforms: all @@ -491,7 +429,7 @@ jobs: name: Verify ${{ matrix.arch }} rpm steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Set package version run: | @@ -502,12 +440,12 @@ jobs: echo "pkg_name=consul-${{ env.pkg_version }}-1.${{ matrix.arch }}.rpm" >> $GITHUB_ENV - name: Download workflow artifacts - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: ${{ env.pkg_name }} - name: Set up QEMU - uses: docker/setup-qemu-action@2b82ce82d56a2a04d2637cd93a637ae1b359c0a7 # v2.2.0 + uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0 with: platforms: all diff --git a/.github/workflows/ce-merge-trigger.yml b/.github/workflows/ce-merge-trigger.yml index 30a6b5fd90..9e088e1fd0 100644 --- a/.github/workflows/ce-merge-trigger.yml +++ b/.github/workflows/ce-merge-trigger.yml @@ -9,6 +9,11 @@ on: branches: - main - release/** + - '!release/1.18**' + - '!release/1.17**' + - '!release/1.16**' + - '!release/1.15**' + jobs: trigger-ce-merge: diff --git a/.github/workflows/changelog-checker.yml b/.github/workflows/changelog-checker.yml index 62b906eda3..597e4751b4 100644 --- a/.github/workflows/changelog-checker.yml +++ b/.github/workflows/changelog-checker.yml @@ -22,7 +22,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 # by default the checkout action doesn't checkout all branches diff --git a/.github/workflows/embedded-asset-checker.yml b/.github/workflows/embedded-asset-checker.yml index 38879945e2..c42ae65ba1 100644 --- a/.github/workflows/embedded-asset-checker.yml +++ b/.github/workflows/embedded-asset-checker.yml @@ -20,7 +20,7 @@ jobs: if: "! ( contains(github.event.pull_request.labels.*.name, 'pr/update-ui-assets') || github.event.pull_request.user.login == 'hc-github-team-consul-core' )" runs-on: ubuntu-latest steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 # by default the checkout action doesn't checkout all branches diff --git a/.github/workflows/frontend.yml b/.github/workflows/frontend.yml index 3fbab0d9cf..93f8ee0bd0 100644 --- a/.github/workflows/frontend.yml +++ b/.github/workflows/frontend.yml @@ -21,7 +21,7 @@ jobs: compute-large: ${{ steps.setup-outputs.outputs.compute-large }} compute-xl: ${{ steps.setup-outputs.outputs.compute-xl }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - id: setup-outputs name: Setup outputs run: ./.github/scripts/get_runner_classes.sh @@ -33,9 +33,9 @@ jobs: run: working-directory: ui steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '18' @@ -53,9 +53,9 @@ jobs: needs: setup runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '18' @@ -83,9 +83,9 @@ jobs: CONSUL_NSPACES_ENABLED: 0 # NOTE: this should be 1 in ENT. JOBS: 2 # limit parallelism for broccoli-babel-transpiler steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '18' @@ -93,7 +93,7 @@ jobs: run: corepack enable - name: Install Chrome - uses: browser-actions/setup-chrome@c485fa3bab6be59dce18dbc18ef6ab7cbc8ff5f1 # v1.2.0 + uses: browser-actions/setup-chrome@82b9ce628cc5595478a9ebadc480958a36457dc2 # v1.6.0 - name: Install dependencies working-directory: ui @@ -123,9 +123,9 @@ jobs: CONSUL_NSPACES_ENABLED: 1 # NOTE: this should be 1 in ENT. JOBS: 2 # limit parallelism for broccoli-babel-transpiler steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: '18' @@ -133,7 +133,7 @@ jobs: run: corepack enable - name: Install Chrome - uses: browser-actions/setup-chrome@c485fa3bab6be59dce18dbc18ef6ab7cbc8ff5f1 # v1.2.0 + uses: browser-actions/setup-chrome@82b9ce628cc5595478a9ebadc480958a36457dc2 # v1.6.0 - name: Install dependencies working-directory: ui diff --git a/.github/workflows/go-tests.yml b/.github/workflows/go-tests.yml index 920a2ed5c5..c30e6e8194 100644 --- a/.github/workflows/go-tests.yml +++ b/.github/workflows/go-tests.yml @@ -22,7 +22,6 @@ permissions: env: TEST_RESULTS: /tmp/test-results GOPRIVATE: github.com/hashicorp # Required for enterprise deps - SKIP_CHECK_BRANCH: ${{ github.head_ref || github.ref_name }} # concurrency concurrency: @@ -31,17 +30,7 @@ concurrency: jobs: conditional-skip: - runs-on: ubuntu-latest - name: Get files changed and conditionally skip CI - outputs: - skip-ci: ${{ steps.read-files.outputs.skip-ci }} - steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - with: - fetch-depth: 0 - - name: Get changed files - id: read-files - run: ./.github/scripts/filter_changed_files_go_test.sh + uses: ./.github/workflows/reusable-conditional-skip.yml setup: needs: [conditional-skip] @@ -54,7 +43,7 @@ jobs: compute-large: ${{ steps.setup-outputs.outputs.compute-large }} compute-xl: ${{ steps.setup-outputs.outputs.compute-xl }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - id: setup-outputs name: Setup outputs run: ./.github/scripts/get_runner_classes.sh @@ -80,12 +69,12 @@ jobs: - get-go-version runs-on: ${{ fromJSON(needs.setup.outputs.compute-medium) }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: make proto-tools @@ -106,12 +95,12 @@ jobs: - get-go-version runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: make --always-make codegen @@ -127,12 +116,12 @@ jobs: - get-go-version runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: go install github.com/reillywatson/enumcover/cmd/enumcover@master && enumcover ./... @@ -143,11 +132,11 @@ jobs: - get-go-version runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: make lint-container-test-deps @@ -158,12 +147,12 @@ jobs: - get-go-version runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: make lint-consul-retry @@ -598,7 +587,7 @@ jobs: # FAILED_TESTS must also be checked to avoid running this step on cancellation due to the summary check above if: ${{ failure() && env.FAILED_TESTS == 'true' && (github.ref_name == 'main' || startsWith(github.ref_name, 'release/')) }} id: slack - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: # Escape entire message string to ensure valid JSON. If invalid, the notification will fail silently in CI. payload: | diff --git a/.github/workflows/goldenfile-checker.yml b/.github/workflows/goldenfile-checker.yml new file mode 100644 index 0000000000..6f3d1ca681 --- /dev/null +++ b/.github/workflows/goldenfile-checker.yml @@ -0,0 +1,43 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +# This workflow checks that are no changes necessary to golden files for xds +# tests ensuring they are up to date + +name: Golden File Checker + +on: + pull_request: + types: [opened, synchronize, labeled] + # Runs on PRs to main and all release branches + branches: + - main + - release/* + +jobs: + get-go-version: + uses: ./.github/workflows/reusable-get-go-version.yml + # checks that there is no diff between the existing golden files + goldenfile-check: + runs-on: ubuntu-latest + needs: + - get-go-version + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 # by default the checkout action doesn't checkout all branches + # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. + - name: Setup Git + if: ${{ endsWith(github.repository, '-enterprise') }} + run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" + - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 + with: + go-version: ${{ needs.get-go-version.outputs.go-version }} + - name: Download Modules + run: go mod download + - name: Check for golden file xds tests in diff + run: ./.github/scripts/goldenfile_checker.sh + env: + GITHUB_BRANCH_REF: ${{ github.event.pull_request.head.ref }} + CONSUL_LICENSE: ${{ secrets.CONSUL_LICENSE }} diff --git a/.github/workflows/issue-comment-created.yml b/.github/workflows/issue-comment-created.yml index 42483d92b1..950dc65d60 100644 --- a/.github/workflows/issue-comment-created.yml +++ b/.github/workflows/issue-comment-created.yml @@ -11,7 +11,7 @@ jobs: triage: runs-on: ubuntu-latest steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1.3.0 with: labels: | diff --git a/.github/workflows/jira-issues.yaml b/.github/workflows/jira-issues.yaml index c136dfd69a..6899781791 100644 --- a/.github/workflows/jira-issues.yaml +++ b/.github/workflows/jira-issues.yaml @@ -16,7 +16,7 @@ jobs: name: Jira Community Issue sync steps: - name: Login - uses: atlassian/gajira-login@ca13f8850ea309cf44a6e4e0c49d9aa48ac3ca4c # v3 + uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -49,7 +49,6 @@ jobs: # customfield_10089 is "Issue Link", customfield_10371 is "Source" (use JIRA API to retrieve) extraFields: '{ "customfield_10089": "${{ github.event.issue.html_url || github.event.pull_request.html_url }}", "customfield_10371": { "value": "GitHub" }, - "customfield_10535": [{ "value": "Service Mesh" }], "components": [{ "name": "${{ github.event.repository.name }}" }], "labels": ${{ steps.set-ticket-labels.outputs.LABELS }} }' env: @@ -91,14 +90,14 @@ jobs: - name: Close ticket if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "Closed" - name: Reopen ticket if: github.event.action == 'reopened' && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "To Do" diff --git a/.github/workflows/jira-pr.yaml b/.github/workflows/jira-pr.yaml index a40bb0ae0f..3a1aa5d6f1 100644 --- a/.github/workflows/jira-pr.yaml +++ b/.github/workflows/jira-pr.yaml @@ -14,7 +14,7 @@ jobs: name: Jira sync steps: - name: Login - uses: atlassian/gajira-login@ca13f8850ea309cf44a6e4e0c49d9aa48ac3ca4c # v3 + uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1 env: JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} @@ -67,8 +67,7 @@ jobs: description: "${{ github.event.issue.body || github.event.pull_request.body }}\n\n_Created in GitHub by ${{ github.actor }}._" # customfield_10089 is "Issue Link", customfield_10371 is "Source" (use JIRA API to retrieve) extraFields: '{ "customfield_10089": "${{ github.event.pull_request.html_url }}", - "customfield_10371": { "value": "GitHub" }, - "customfield_10535": [{ "value": "Service Mesh" }], + "customfield_10371": { "value": "GitHub" }, "components": [{ "name": "${{ github.event.repository.name }}" }], "labels": ${{ steps.set-ticket-labels.outputs.LABELS }} }' env: @@ -105,14 +104,14 @@ jobs: - name: Close ticket if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "Closed" - name: Reopen ticket if: github.event.action == 'reopened' && steps.search.outputs.issue - uses: atlassian/gajira-transition@4749176faf14633954d72af7a44d7f2af01cc92b # v3 + uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 with: issue: ${{ steps.search.outputs.issue }} transition: "To Do" diff --git a/.github/workflows/nightly-test-1.15.x.yaml b/.github/workflows/nightly-test-1.15.x.yaml index a98eb73070..f8dec0f82e 100644 --- a/.github/workflows/nightly-test-1.15.x.yaml +++ b/.github/workflows/nightly-test-1.15.x.yaml @@ -14,15 +14,22 @@ env: GOPRIVATE: github.com/hashicorp # Required for enterprise deps jobs: + check-ent: + runs-on: ubuntu-latest + if: ${{ endsWith(github.repository, '-enterprise') }} + steps: + - run: echo "Building Enterprise" + frontend-test-workspace-node: runs-on: ubuntu-latest + needs: [check-ent] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 14 cache: 'yarn' @@ -45,16 +52,17 @@ jobs: frontend-build-ce: runs-on: ubuntu-latest + needs: [check-ent] env: JOBS: 2 CONSUL_NSPACES_ENABLED: 0 steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 14 cache: 'yarn' @@ -71,7 +79,7 @@ jobs: run: make build-ci - name: Upload CE Frontend - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: frontend-ce-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -88,12 +96,12 @@ jobs: EMBER_TEST_REPORT: test-results/report-ce.xml #outputs test report for CI test summary EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 14 cache: 'yarn' @@ -105,7 +113,7 @@ jobs: run: make deps - name: Download CE Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ce-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -117,16 +125,17 @@ jobs: frontend-build-ent: runs-on: ubuntu-latest + needs: [check-ent] env: JOBS: 2 CONSUL_NSPACES_ENABLED: 1 steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 14 cache: 'yarn' @@ -143,7 +152,7 @@ jobs: run: make build-ci - name: Upload ENT Frontend - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -160,12 +169,12 @@ jobs: EMBER_TEST_REPORT: test-results/report-ce.xml #outputs test report for CI test summary EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 14 cache: 'yarn' @@ -177,7 +186,7 @@ jobs: run: make deps - name: Download ENT Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -191,12 +200,12 @@ jobs: runs-on: ubuntu-latest needs: [frontend-build-ent] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 14 cache: 'yarn' @@ -208,7 +217,7 @@ jobs: run: make deps - name: Download ENT Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -224,7 +233,7 @@ jobs: steps: - name: Slack Notification id: slack - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: | { diff --git a/.github/workflows/nightly-test-1.17.x.yaml b/.github/workflows/nightly-test-1.18.x.yaml similarity index 75% rename from .github/workflows/nightly-test-1.17.x.yaml rename to .github/workflows/nightly-test-1.18.x.yaml index 9a063001e4..ca627b0139 100644 --- a/.github/workflows/nightly-test-1.17.x.yaml +++ b/.github/workflows/nightly-test-1.18.x.yaml @@ -1,7 +1,7 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -name: Nightly Frontend Test 1.17.x +name: Nightly Frontend Test 1.18.x on: schedule: - cron: '0 4 * * *' @@ -9,20 +9,27 @@ on: env: EMBER_PARTITION_TOTAL: 4 # Has to be changed in tandem with the matrix.partition - BRANCH: "release/1.17.x" - BRANCH_NAME: "release-1.17.x" # Used for naming artifacts + BRANCH: "release/1.18.x" + BRANCH_NAME: "release-1.18.x" # Used for naming artifacts GOPRIVATE: github.com/hashicorp # Required for enterprise deps jobs: + check-ent: + runs-on: ubuntu-latest + if: ${{ endsWith(github.repository, '-enterprise') }} + steps: + - run: echo "Building Enterprise" + frontend-test-workspace-node: runs-on: ubuntu-latest + needs: [check-ent] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: 'yarn' @@ -45,16 +52,17 @@ jobs: frontend-build-ce: runs-on: ubuntu-latest + needs: [check-ent] env: JOBS: 2 CONSUL_NSPACES_ENABLED: 0 steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: 'yarn' @@ -71,7 +79,7 @@ jobs: run: make build-ci - name: Upload CE Frontend - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: frontend-ce-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -88,12 +96,12 @@ jobs: EMBER_TEST_REPORT: test-results/report-ce.xml #outputs test report for CI test summary EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: 'yarn' @@ -105,7 +113,7 @@ jobs: run: make deps - name: Download CE Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ce-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -117,16 +125,17 @@ jobs: frontend-build-ent: runs-on: ubuntu-latest + needs: [check-ent] env: JOBS: 2 CONSUL_NSPACES_ENABLED: 1 steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: 'yarn' @@ -143,7 +152,7 @@ jobs: run: make build-ci - name: Upload ENT Frontend - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -160,12 +169,12 @@ jobs: EMBER_TEST_REPORT: test-results/report-ce.xml #outputs test report for CI test summary EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: 'yarn' @@ -177,7 +186,7 @@ jobs: run: make deps - name: Download ENT Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -191,12 +200,12 @@ jobs: runs-on: ubuntu-latest needs: [frontend-build-ent] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: 'yarn' @@ -208,7 +217,7 @@ jobs: run: make deps - name: Download ENT Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -224,7 +233,7 @@ jobs: steps: - name: Slack Notification id: slack - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: | { diff --git a/.github/workflows/nightly-test-1.14.x.yaml b/.github/workflows/nightly-test-1.19.x.yaml similarity index 73% rename from .github/workflows/nightly-test-1.14.x.yaml rename to .github/workflows/nightly-test-1.19.x.yaml index 11fb011d13..20c80dcb23 100644 --- a/.github/workflows/nightly-test-1.14.x.yaml +++ b/.github/workflows/nightly-test-1.19.x.yaml @@ -1,7 +1,7 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -name: Nightly Frontend Test 1.14.x +name: Nightly Frontend Test 1.19.x on: schedule: - cron: '0 4 * * *' @@ -9,22 +9,29 @@ on: env: EMBER_PARTITION_TOTAL: 4 # Has to be changed in tandem with the matrix.partition - BRANCH: "release/1.14.x" - BRANCH_NAME: "release-1.14.x" # Used for naming artifacts + BRANCH: "release/1.19.x" + BRANCH_NAME: "release-1.19.x" # Used for naming artifacts GOPRIVATE: github.com/hashicorp # Required for enterprise deps jobs: + check-ent: + runs-on: ubuntu-latest + if: ${{ endsWith(github.repository, '-enterprise') }} + steps: + - run: echo "Building Enterprise" + frontend-test-workspace-node: runs-on: ubuntu-latest + needs: [ check-ent ] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: 14 + node-version: 18 cache: 'yarn' cache-dependency-path: ./ui/yarn.lock @@ -45,18 +52,19 @@ jobs: frontend-build-ce: runs-on: ubuntu-latest + needs: [ check-ent ] env: JOBS: 2 CONSUL_NSPACES_ENABLED: 0 steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: 14 + node-version: 18 cache: 'yarn' cache-dependency-path: ./ui/yarn.lock @@ -71,7 +79,7 @@ jobs: run: make build-ci - name: Upload CE Frontend - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: frontend-ce-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -88,14 +96,14 @@ jobs: EMBER_TEST_REPORT: test-results/report-ce.xml #outputs test report for CI test summary EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: 14 + node-version: 18 cache: 'yarn' cache-dependency-path: ./ui/yarn.lock @@ -105,7 +113,7 @@ jobs: run: make deps - name: Download CE Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ce-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -117,18 +125,19 @@ jobs: frontend-build-ent: runs-on: ubuntu-latest + needs: [ check-ent ] env: JOBS: 2 CONSUL_NSPACES_ENABLED: 1 steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: 14 + node-version: 18 cache: 'yarn' cache-dependency-path: ./ui/yarn.lock @@ -143,7 +152,7 @@ jobs: run: make build-ci - name: Upload ENT Frontend - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -160,14 +169,14 @@ jobs: EMBER_TEST_REPORT: test-results/report-ce.xml #outputs test report for CI test summary EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: 14 + node-version: 18 cache: 'yarn' cache-dependency-path: ./ui/yarn.lock @@ -177,7 +186,7 @@ jobs: run: make deps - name: Download ENT Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -191,14 +200,14 @@ jobs: runs-on: ubuntu-latest needs: [frontend-build-ent] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: 14 + node-version: 18 cache: 'yarn' cache-dependency-path: ./ui/yarn.lock @@ -208,7 +217,7 @@ jobs: run: make deps - name: Download ENT Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -224,7 +233,7 @@ jobs: steps: - name: Slack Notification id: slack - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: | { diff --git a/.github/workflows/nightly-test-1.16.x.yaml b/.github/workflows/nightly-test-1.20.x.yaml similarity index 73% rename from .github/workflows/nightly-test-1.16.x.yaml rename to .github/workflows/nightly-test-1.20.x.yaml index b441eca5d0..37f035def2 100644 --- a/.github/workflows/nightly-test-1.16.x.yaml +++ b/.github/workflows/nightly-test-1.20.x.yaml @@ -1,7 +1,7 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -name: Nightly Frontend Test 1.16.x +name: Nightly Frontend Test 1.20.x on: schedule: - cron: '0 4 * * *' @@ -9,22 +9,29 @@ on: env: EMBER_PARTITION_TOTAL: 4 # Has to be changed in tandem with the matrix.partition - BRANCH: "release/1.16.x" - BRANCH_NAME: "release-1.16.x" # Used for naming artifacts + BRANCH: "release/1.20.x" + BRANCH_NAME: "release-1.20.x" # Used for naming artifacts GOPRIVATE: github.com/hashicorp # Required for enterprise deps jobs: + check-ent: + runs-on: ubuntu-latest + if: ${{ endsWith(github.repository, '-enterprise') }} + steps: + - run: echo "Building Enterprise" + frontend-test-workspace-node: runs-on: ubuntu-latest + needs: [ check-ent ] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: 14 + node-version: 18 cache: 'yarn' cache-dependency-path: ./ui/yarn.lock @@ -45,18 +52,19 @@ jobs: frontend-build-ce: runs-on: ubuntu-latest + needs: [ check-ent ] env: JOBS: 2 CONSUL_NSPACES_ENABLED: 0 steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: 14 + node-version: 18 cache: 'yarn' cache-dependency-path: ./ui/yarn.lock @@ -71,7 +79,7 @@ jobs: run: make build-ci - name: Upload CE Frontend - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: frontend-ce-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -88,14 +96,14 @@ jobs: EMBER_TEST_REPORT: test-results/report-ce.xml #outputs test report for CI test summary EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: 14 + node-version: 18 cache: 'yarn' cache-dependency-path: ./ui/yarn.lock @@ -105,7 +113,7 @@ jobs: run: make deps - name: Download CE Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ce-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -117,18 +125,19 @@ jobs: frontend-build-ent: runs-on: ubuntu-latest + needs: [ check-ent ] env: JOBS: 2 CONSUL_NSPACES_ENABLED: 1 steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: 14 + node-version: 18 cache: 'yarn' cache-dependency-path: ./ui/yarn.lock @@ -143,7 +152,7 @@ jobs: run: make build-ci - name: Upload ENT Frontend - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -160,14 +169,14 @@ jobs: EMBER_TEST_REPORT: test-results/report-ce.xml #outputs test report for CI test summary EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: 14 + node-version: 18 cache: 'yarn' cache-dependency-path: ./ui/yarn.lock @@ -177,7 +186,7 @@ jobs: run: make deps - name: Download ENT Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -191,14 +200,14 @@ jobs: runs-on: ubuntu-latest needs: [frontend-build-ent] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: - node-version: 14 + node-version: 18 cache: 'yarn' cache-dependency-path: ./ui/yarn.lock @@ -208,7 +217,7 @@ jobs: run: make deps - name: Download ENT Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -224,7 +233,7 @@ jobs: steps: - name: Slack Notification id: slack - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: | { diff --git a/.github/workflows/nightly-test-integ-peering_commontopo.yml b/.github/workflows/nightly-test-integ-peering_commontopo.yml index afedb50a9f..5c7f5fa23b 100644 --- a/.github/workflows/nightly-test-integ-peering_commontopo.yml +++ b/.github/workflows/nightly-test-integ-peering_commontopo.yml @@ -6,7 +6,7 @@ name: Nightly test integrations - peering_common_topo on: schedule: # Run nightly at 12AM UTC/8PM EST/5PM PST - - cron: '* 0 * * *' + - cron: '0 0 * * *' workflow_dispatch: {} env: @@ -31,7 +31,7 @@ jobs: enterprise: ${{ steps.runners.outputs.enterprise }} steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ inputs.branch }} - id: runners @@ -39,12 +39,20 @@ jobs: get-go-version: uses: ./.github/workflows/reusable-get-go-version.yml + with: + ref: ${{ inputs.branch }} + + get-envoy-versions: + uses: ./.github/workflows/reusable-get-envoy-versions.yml + with: + ref: ${{ inputs.branch }} tests: runs-on: ${{ fromJSON(needs.setup.outputs.compute-xl ) }} needs: - setup - get-go-version + - get-envoy-versions permissions: id-token: write # NOTE: this permission is explicitly required for Vault auth. contents: read @@ -62,14 +70,14 @@ jobs: name: '${{matrix.test-case}}' env: - ENVOY_VERSION: "1.24.6" + ENVOY_VERSION: ${{ needs.get-envoy-versions.outputs.max-envoy-version }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: go env @@ -117,7 +125,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -157,7 +165,7 @@ jobs: - name: Notify Slack if: ${{ failure() }} id: slack - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: | { diff --git a/.github/workflows/nightly-test-integrations-1.15.x.yml b/.github/workflows/nightly-test-integrations-1.15.x.yml index 8c9c9eabdd..abb24520ef 100644 --- a/.github/workflows/nightly-test-integrations-1.15.x.yml +++ b/.github/workflows/nightly-test-integrations-1.15.x.yml @@ -6,7 +6,7 @@ name: Nightly test-integrations 1.15.x on: schedule: # Run nightly at 1AM UTC/9PM EST/6PM PST - - cron: '* 1 * * *' + - cron: '0 1 * * *' workflow_dispatch: {} env: @@ -23,8 +23,15 @@ env: BRANCH_NAME: "release-1.15.x" # Used for naming artifacts jobs: + check-ent: + runs-on: ubuntu-latest + if: ${{ endsWith(github.repository, '-enterprise') }} + steps: + - run: echo "Building Enterprise" + setup: runs-on: ubuntu-latest + needs: [check-ent] name: Setup outputs: compute-small: ${{ steps.runners.outputs.compute-small }} @@ -34,14 +41,23 @@ jobs: enterprise: ${{ steps.runners.outputs.enterprise }} steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} - id: runners run: .github/scripts/get_runner_classes.sh get-go-version: + needs: [check-ent] uses: ./.github/workflows/reusable-get-go-version.yml + with: + ref: release/1.15.x + + get-envoy-versions: + needs: [check-ent] + uses: ./.github/workflows/reusable-get-envoy-versions.yml + with: + ref: release/1.15.x dev-build: needs: @@ -65,42 +81,40 @@ jobs: envoy-matrix: ${{ steps.set-matrix.outputs.envoy-matrix }} steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} - name: Generate Envoy Job Matrix id: set-matrix env: - # this is further going to multiplied in envoy-integration tests by the - # other dimensions in the matrix. Currently TOTAL_RUNNERS would be - # 14 based on these values: - # envoy-version: ["1.22.11", "1.23.12", "1.24.12", "1.25.11", "1.26.8", "1.27.4", "1.28.2"] - # xds-target: ["server", "client"] - TOTAL_RUNNERS: 7 + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | - NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) - if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then - echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$((NUM_DIRS-1)) + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) fi - # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. - NUM_RUNNERS=$((NUM_RUNNERS-1)) + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) { echo -n "envoy-matrix=" find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ | xargs -0 -n 1 basename \ - | jq --raw-input --argjson runnercount "$NUM_RUNNERS" "$JQ_SLICER" \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ | jq --compact-output 'map(join("|"))' } >> "$GITHUB_OUTPUT" - + envoy-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} needs: - setup - get-go-version + - get-envoy-versions - generate-envoy-job-matrices - dev-build permissions: @@ -109,7 +123,7 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.22.11", "1.23.12", "1.24.12", "1.25.11", "1.26.8", "1.27.4", "1.28.2"] + envoy-version: ${{ fromJSON(needs.get-envoy-versions.outputs.envoy-versions-json) }} xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: @@ -118,25 +132,29 @@ jobs: AWS_LAMBDA_REGION: us-west-2 steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - name: fetch binary - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' path: ./bin - name: restore mode+x run: chmod +x ./bin/consul + - name: Set up Docker Buildx - uses: docker/setup-buildx-action@2a1a44ac4aa01993040736bd95bb470da1a38365 # v2.9.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + - name: Docker build run: docker build -t consul:local -f ./build-support/docker/Consul-Dev.dockerfile ./bin + - name: Envoy Integration Tests + id: envoy-integration-tests env: GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml GOTESTSUM_FORMAT: standard-verbose @@ -157,6 +175,23 @@ jobs: --packages=./test/integration/connect/envoy \ -- -timeout=30m -tags integration -run="TestEnvoy/(${{ matrix.test-cases }})" + # See https://github.com/orgs/community/discussions/8945#discussioncomment-9897011 + # and overall topic discussion for why this is necessary. + - name: Generate artifact ID + id: generate-artifact-id + if: ${{ failure() && steps.envoy-integration-tests.conclusion == 'failure' }} + run: | + ARTIFACT_ID=$(uuidgen) + echo "Artifact ID: $ARTIFACT_ID (search this in job summary for download link)" + echo "artifact_id=$ARTIFACT_ID" >> "$GITHUB_ENV" + + - name: Upload failure logs + if: ${{ failure() && steps.envoy-integration-tests.conclusion == 'failure' }} + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + name: envoy-${{ matrix.envoy-version }}-logs-${{ env.artifact_id }} + path: test/integration/connect/envoy/workdir/logs/ + # NOTE: ENT specific step as we store secrets in Vault. - name: Authenticate to Vault if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} @@ -167,7 +202,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -188,7 +223,7 @@ jobs: DATADOG_API_KEY: "${{ endsWith(github.repository, '-enterprise') && env.DATADOG_API_KEY || secrets.DATADOG_API_KEY }}" DD_ENV: ci run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" $TEST_RESULTS_DIR/results.xml - + upgrade-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} needs: @@ -204,24 +239,34 @@ jobs: consul-version: ["1.14", "1.15"] env: CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} - ENVOY_VERSION: "1.24.6" + # ENVOY_VERSION should be the latest version supported by _all_ Consul versions in the + # matrix.consul-version, since we are testing upgrade from an older Consul version. + # In practice, this should be the highest Envoy version supported by the lowest non-LTS + # Consul version in the matrix (LTS versions receive additional Envoy version support). + # + # This value should be kept current in new nightly test workflows, and updated any time + # a new major Envoy release is added to the set supported by Consul versions in + # matrix.consul-version (i.e. whenever the highest common Envoy version across active + # Consul versions changes). The minor Envoy version does not necessarily need to be + # kept current for the purpose of these tests, but the major (1.N) version should be. + ENVOY_VERSION: "1.24.12" steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: go env # Get go binary from workspace - name: fetch binary - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' path: . @@ -275,7 +320,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -305,7 +350,7 @@ jobs: - envoy-integration-test - upgrade-integration-test runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} - if: ${{ always() }} + if: ${{ always() && endsWith(github.repository, '-enterprise') }} steps: - name: evaluate upstream job results run: | @@ -317,7 +362,7 @@ jobs: - name: Notify Slack if: ${{ failure() }} id: slack - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: | { diff --git a/.github/workflows/nightly-test-integrations-1.18.x.yml b/.github/workflows/nightly-test-integrations-1.18.x.yml new file mode 100644 index 0000000000..2d358cda69 --- /dev/null +++ b/.github/workflows/nightly-test-integrations-1.18.x.yml @@ -0,0 +1,482 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + +name: Nightly test-integrations 1.18.x + +on: + schedule: + # Run nightly at 1AM UTC/9PM EST/6PM PST + - cron: '0 1 * * *' + workflow_dispatch: {} + +env: + TEST_RESULTS_DIR: /tmp/test-results + TEST_RESULTS_ARTIFACT_NAME: test-results + CONSUL_LICENSE: ${{ secrets.CONSUL_LICENSE }} + GOTAGS: ${{ endsWith(github.repository, '-enterprise') && 'consulent' || '' }} + GOTESTSUM_VERSION: "1.11.0" + CONSUL_BINARY_UPLOAD_NAME: consul-bin + # strip the hashicorp/ off the front of github.repository for consul + CONSUL_LATEST_IMAGE_NAME: ${{ endsWith(github.repository, '-enterprise') && github.repository || 'hashicorp/consul' }} + GOPRIVATE: github.com/hashicorp # Required for enterprise deps + BRANCH: "release/1.18.x" + BRANCH_NAME: "release-1.18.x" # Used for naming artifacts + +jobs: + check-ent: + runs-on: ubuntu-latest + if: ${{ endsWith(github.repository, '-enterprise') }} + steps: + - run: echo "Building Enterprise" + + setup: + runs-on: ubuntu-latest + needs: [check-ent] + name: Setup + outputs: + compute-small: ${{ steps.runners.outputs.compute-small }} + compute-medium: ${{ steps.runners.outputs.compute-medium }} + compute-large: ${{ steps.runners.outputs.compute-large }} + compute-xl: ${{ steps.runners.outputs.compute-xl }} + enterprise: ${{ steps.runners.outputs.enterprise }} + steps: + - name: Checkout code + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + ref: ${{ env.BRANCH }} + - id: runners + run: .github/scripts/get_runner_classes.sh + + get-go-version: + needs: [check-ent] + uses: ./.github/workflows/reusable-get-go-version.yml + with: + ref: release/1.18.x + + get-envoy-versions: + needs: [check-ent] + uses: ./.github/workflows/reusable-get-envoy-versions.yml + with: + ref: release/1.18.x + + dev-build: + needs: + - setup + - get-go-version + uses: ./.github/workflows/reusable-dev-build.yml + with: + runs-on: ${{ needs.setup.outputs.compute-large }} + repository-name: ${{ github.repository }} + uploaded-binary-name: 'consul-bin' + branch-name: "release/1.18.x" + go-version: ${{ needs.get-go-version.outputs.go-version }} + secrets: + elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} + + generate-envoy-job-matrices: + needs: [setup] + runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} + name: Generate Envoy Job Matrices + outputs: + envoy-matrix: ${{ steps.set-matrix.outputs.envoy-matrix }} + steps: + - name: Checkout code + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + ref: ${{ env.BRANCH }} + - name: Generate Envoy Job Matrix + id: set-matrix + env: + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 + JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' + run: | + NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) + + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) + fi + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) + { + echo -n "envoy-matrix=" + find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ + | xargs -0 -n 1 basename \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ + | jq --compact-output 'map(join("|"))' + } >> "$GITHUB_OUTPUT" + + envoy-integration-test: + runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} + needs: + - setup + - get-go-version + - get-envoy-versions + - generate-envoy-job-matrices + - dev-build + permissions: + id-token: write # NOTE: this permission is explicitly required for Vault auth. + contents: read + strategy: + fail-fast: false + matrix: + envoy-version: ${{ fromJSON(needs.get-envoy-versions.outputs.envoy-versions-json) }} + xds-target: ["server", "client"] + test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} + env: + ENVOY_VERSION: ${{ matrix.envoy-version }} + XDS_TARGET: ${{ matrix.xds-target }} + AWS_LAMBDA_REGION: us-west-2 + steps: + - name: Checkout code + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + ref: ${{ env.BRANCH }} + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version: ${{ needs.get-go-version.outputs.go-version }} + + - name: fetch binary + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + with: + name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' + path: ./bin + - name: restore mode+x + run: chmod +x ./bin/consul + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 + + - name: Docker build + run: docker build -t consul:local -f ./build-support/docker/Consul-Dev.dockerfile ./bin + + - name: Envoy Integration Tests + id: envoy-integration-tests + env: + GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml + GOTESTSUM_FORMAT: standard-verbose + COMPOSE_INTERACTIVE_NO_CLI: 1 + LAMBDA_TESTS_ENABLED: "true" + # tput complains if this isn't set to something. + TERM: ansi + run: | + # shellcheck disable=SC2001 + echo "Running $(sed 's,|, ,g' <<< "${{ matrix.test-cases }}" |wc -w) subtests" + # shellcheck disable=SC2001 + sed 's,|,\n,g' <<< "${{ matrix.test-cases }}" + go run gotest.tools/gotestsum@v${{env.GOTESTSUM_VERSION}} \ + --debug \ + --rerun-fails \ + --rerun-fails-report=/tmp/gotestsum-rerun-fails \ + --jsonfile /tmp/jsonfile/go-test.log \ + --packages=./test/integration/connect/envoy \ + -- -timeout=30m -tags integration -run="TestEnvoy/(${{ matrix.test-cases }})" + + # See https://github.com/orgs/community/discussions/8945#discussioncomment-9897011 + # and overall topic discussion for why this is necessary. + - name: Generate artifact ID + id: generate-artifact-id + if: ${{ failure() && steps.envoy-integration-tests.conclusion == 'failure' }} + run: | + ARTIFACT_ID=$(uuidgen) + echo "Artifact ID: $ARTIFACT_ID (search this in job summary for download link)" + echo "artifact_id=$ARTIFACT_ID" >> "$GITHUB_ENV" + + - name: Upload failure logs + if: ${{ failure() && steps.envoy-integration-tests.conclusion == 'failure' }} + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + name: envoy-${{ matrix.envoy-version }}-logs-${{ env.artifact_id }} + path: test/integration/connect/envoy/workdir/logs/ + + # NOTE: ENT specific step as we store secrets in Vault. + - name: Authenticate to Vault + if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} + id: vault-auth + run: vault-auth + + # NOTE: ENT specific step as we store secrets in Vault. + - name: Fetch Secrets + if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} + id: secrets + uses: hashicorp/vault-action@v3 + with: + url: ${{ steps.vault-auth.outputs.addr }} + caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} + token: ${{ steps.vault-auth.outputs.token }} + secrets: | + kv/data/github/${{ github.repository }}/datadog apikey | DATADOG_API_KEY; + + - name: prepare datadog-ci + if: ${{ !cancelled() && !endsWith(github.repository, '-enterprise') }} + run: | + curl -L --fail "https://github.com/DataDog/datadog-ci/releases/latest/download/datadog-ci_linux-x64" --output "/usr/local/bin/datadog-ci" + chmod +x /usr/local/bin/datadog-ci + + - name: upload coverage + # do not run on forks + if: ${{ !cancelled() && github.event.pull_request.head.repo.full_name == github.repository }} + env: + DATADOG_API_KEY: "${{ endsWith(github.repository, '-enterprise') && env.DATADOG_API_KEY || secrets.DATADOG_API_KEY }}" + DD_ENV: ci + run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" $TEST_RESULTS_DIR/results.xml + + upgrade-integration-test: + runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} + needs: + - setup + - get-go-version + - dev-build + permissions: + id-token: write # NOTE: this permission is explicitly required for Vault auth. + contents: read + strategy: + fail-fast: false + matrix: + consul-version: ["1.15", "1.16", "1.17", "1.18"] + env: + CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} + # ENVOY_VERSION should be the latest version supported by _all_ Consul versions in the + # matrix.consul-version, since we are testing upgrade from an older Consul version. + # In practice, this should be the highest Envoy version supported by the lowest non-LTS + # Consul version in the matrix (LTS versions receive additional Envoy version support). + # + # This value should be kept current in new nightly test workflows, and updated any time + # a new major Envoy release is added to the set supported by Consul versions in + # matrix.consul-version (i.e. whenever the highest common Envoy version across active + # Consul versions changes). The minor Envoy version does not necessarily need to be + # kept current for the purpose of these tests, but the major (1.N) version should be. + ENVOY_VERSION: 1.27.6 + steps: + - name: Checkout code + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + ref: ${{ env.BRANCH }} + # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. + - name: Setup Git + if: ${{ endsWith(github.repository, '-enterprise') }} + run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version: ${{ needs.get-go-version.outputs.go-version }} + - run: go env + + # Get go binary from workspace + - name: fetch binary + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 + with: + name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' + path: . + - name: restore mode+x + run: chmod +x consul + - name: Build consul:local image + run: docker build -t ${{ env.CONSUL_LATEST_IMAGE_NAME }}:local -f ./build-support/docker/Consul-Dev.dockerfile . + - name: Build consul-envoy:latest-version image + id: buildConsulEnvoyLatestImage + run: | + if ${{ endsWith(github.repository, '-enterprise') }} == 'true' + then + docker build -t consul-envoy:latest-version --build-arg CONSUL_IMAGE=docker.mirror.hashicorp.services/${{ env.CONSUL_LATEST_IMAGE_NAME }}:${{ env.CONSUL_LATEST_VERSION }}-ent --build-arg ENVOY_VERSION=${{ env.ENVOY_VERSION }} -f ./test/integration/consul-container/assets/Dockerfile-consul-envoy ./test/integration/consul-container/assets + else + docker build -t consul-envoy:latest-version --build-arg CONSUL_IMAGE=docker.mirror.hashicorp.services/${{ env.CONSUL_LATEST_IMAGE_NAME }}:${{ env.CONSUL_LATEST_VERSION }} --build-arg ENVOY_VERSION=${{ env.ENVOY_VERSION }} -f ./test/integration/consul-container/assets/Dockerfile-consul-envoy ./test/integration/consul-container/assets + fi + - name: Build consul-envoy:target-version image + id: buildConsulEnvoyTargetImage + continue-on-error: true + run: docker build -t consul-envoy:target-version --build-arg CONSUL_IMAGE=${{ env.CONSUL_LATEST_IMAGE_NAME }}:local --build-arg ENVOY_VERSION=${{ env.ENVOY_VERSION }} -f ./test/integration/consul-container/assets/Dockerfile-consul-envoy ./test/integration/consul-container/assets + - name: Retry Build consul-envoy:target-version image + if: steps.buildConsulEnvoyTargetImage.outcome == 'failure' + run: docker build -t consul-envoy:target-version --build-arg CONSUL_IMAGE=${{ env.CONSUL_LATEST_IMAGE_NAME }}:local --build-arg ENVOY_VERSION=${{ env.ENVOY_VERSION }} -f ./test/integration/consul-container/assets/Dockerfile-consul-envoy ./test/integration/consul-container/assets + - name: Build sds image + run: docker build -t consul-sds-server ./test/integration/connect/envoy/test-sds-server/ + - name: Configure GH workaround for ipv6 loopback + if: ${{ !endsWith(github.repository, '-enterprise') }} + run: | + cat /etc/hosts && echo "-----------" + sudo sed -i 's/::1 *localhost ip6-localhost ip6-loopback/::1 ip6-localhost ip6-loopback/g' /etc/hosts + cat /etc/hosts + - name: Upgrade Integration Tests + run: | + mkdir -p "${{ env.TEST_RESULTS_DIR }}" + cd ./test/integration/consul-container/test/upgrade + docker run --rm ${{ env.CONSUL_LATEST_IMAGE_NAME }}:local consul version + go run gotest.tools/gotestsum@v${{env.GOTESTSUM_VERSION}} \ + --raw-command \ + --format=github-actions \ + --rerun-fails \ + --packages="./..." \ + -- \ + go test \ + -p=4 \ + -tags "${{ env.GOTAGS }}" \ + -timeout=30m \ + -json \ + ./... \ + --follow-log=false \ + --target-image ${{ env.CONSUL_LATEST_IMAGE_NAME }} \ + --target-version local \ + --latest-image docker.mirror.hashicorp.services/${{ env.CONSUL_LATEST_IMAGE_NAME }} \ + --latest-version "${{ env.CONSUL_LATEST_VERSION }}" + ls -lrt + env: + # this is needed because of incompatibility between RYUK container and GHA + GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml + GOTESTSUM_FORMAT: standard-verbose + COMPOSE_INTERACTIVE_NO_CLI: 1 + # tput complains if this isn't set to something. + TERM: ansi + # NOTE: ENT specific step as we store secrets in Vault. + - name: Authenticate to Vault + if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} + id: vault-auth + run: vault-auth + + # NOTE: ENT specific step as we store secrets in Vault. + - name: Fetch Secrets + if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} + id: secrets + uses: hashicorp/vault-action@v3 + with: + url: ${{ steps.vault-auth.outputs.addr }} + caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} + token: ${{ steps.vault-auth.outputs.token }} + secrets: | + kv/data/github/${{ github.repository }}/datadog apikey | DATADOG_API_KEY; + + - name: prepare datadog-ci + if: ${{ !cancelled() && !endsWith(github.repository, '-enterprise') }} + run: | + curl -L --fail "https://github.com/DataDog/datadog-ci/releases/latest/download/datadog-ci_linux-x64" --output "/usr/local/bin/datadog-ci" + chmod +x /usr/local/bin/datadog-ci + + - name: upload coverage + # do not run on forks + if: ${{ !cancelled() && github.event.pull_request.head.repo.full_name == github.repository }} + env: + DATADOG_API_KEY: "${{ endsWith(github.repository, '-enterprise') && env.DATADOG_API_KEY || secrets.DATADOG_API_KEY }}" + DD_ENV: ci + run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" $TEST_RESULTS_DIR/results.xml + + upgrade-integration-test-deployer: + runs-on: ${{ fromJSON(needs.setup.outputs.compute-large ) }} + needs: + - setup + - get-go-version + - dev-build + permissions: + id-token: write # NOTE: this permission is explicitly required for Vault auth. + contents: read + strategy: + fail-fast: false + matrix: + consul-version: [ "1.15", "1.16", "1.17"] + env: + CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} + steps: + - name: Checkout code + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + ref: ${{ env.BRANCH }} + # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. + - name: Setup Git + if: ${{ endsWith(github.repository, '-enterprise') }} + run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version: ${{ needs.get-go-version.outputs.go-version }} + - run: go env + - name: Build image + run: make test-deployer-setup + - name: Upgrade Integration Tests + run: | + mkdir -p "${{ env.TEST_RESULTS_DIR }}" + export NOLOGBUFFER=1 + cd ./test-integ/upgrade + docker run --rm ${{ env.CONSUL_LATEST_IMAGE_NAME }}:local consul version + go run gotest.tools/gotestsum@v${{env.GOTESTSUM_VERSION}} \ + --raw-command \ + --format=standard-verbose \ + --debug \ + --packages="./..." \ + -- \ + go test \ + -tags "${{ env.GOTAGS }}" \ + -timeout=60m \ + -parallel=2 \ + -json \ + ./... \ + --target-image ${{ env.CONSUL_LATEST_IMAGE_NAME }} \ + --target-version local \ + --latest-image docker.mirror.hashicorp.services/${{ env.CONSUL_LATEST_IMAGE_NAME }} \ + --latest-version "${{ env.CONSUL_LATEST_VERSION }}" + env: + # this is needed because of incompatibility between RYUK container and GHA + GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml + GOTESTSUM_FORMAT: standard-verbose + COMPOSE_INTERACTIVE_NO_CLI: 1 + # tput complains if this isn't set to something. + TERM: ansi + # NOTE: ENT specific step as we store secrets in Vault. + - name: Authenticate to Vault + if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} + id: vault-auth + run: vault-auth + + # NOTE: ENT specific step as we store secrets in Vault. + - name: Fetch Secrets + if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} + id: secrets + uses: hashicorp/vault-action@v3 + with: + url: ${{ steps.vault-auth.outputs.addr }} + caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} + token: ${{ steps.vault-auth.outputs.token }} + secrets: | + kv/data/github/${{ github.repository }}/datadog apikey | DATADOG_API_KEY; + + - name: prepare datadog-ci + if: ${{ !cancelled() && !endsWith(github.repository, '-enterprise') }} + run: | + curl -L --fail "https://github.com/DataDog/datadog-ci/releases/latest/download/datadog-ci_linux-x64" --output "/usr/local/bin/datadog-ci" + chmod +x /usr/local/bin/datadog-ci + + - name: upload coverage + # do not run on forks + if: ${{ !cancelled() && github.event.pull_request.head.repo.full_name == github.repository }} + env: + DATADOG_API_KEY: "${{ endsWith(github.repository, '-enterprise') && env.DATADOG_API_KEY || secrets.DATADOG_API_KEY }}" + DD_ENV: ci + run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" $TEST_RESULTS_DIR/results.xml + + test-integrations-success: + needs: + - setup + - dev-build + - generate-envoy-job-matrices + - envoy-integration-test + - upgrade-integration-test + - upgrade-integration-test-deployer + runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} + if: ${{ always() && endsWith(github.repository, '-enterprise') }} + steps: + - name: evaluate upstream job results + run: | + # exit 1 if failure or cancelled result for any upstream job + if printf '${{ toJSON(needs) }}' | grep -E -i '\"result\": \"(failure|cancelled)\"'; then + printf "Tests failed or workflow cancelled:\n\n${{ toJSON(needs) }}" + exit 1 + fi + - name: Notify Slack + if: ${{ failure() }} + id: slack + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 + with: + payload: | + { + "message": "One or more nightly integration tests have failed on branch ${{ env.BRANCH }} for Consul. ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}" + } + env: + SLACK_WEBHOOK_URL: ${{ secrets.CONSUL_NIGHTLY_INTEG_TEST_SLACK_WEBHOOK }} diff --git a/.github/workflows/nightly-test-integrations-1.16.x.yml b/.github/workflows/nightly-test-integrations-1.19.x.yml similarity index 63% rename from .github/workflows/nightly-test-integrations-1.16.x.yml rename to .github/workflows/nightly-test-integrations-1.19.x.yml index 8795baf9f7..327907d184 100644 --- a/.github/workflows/nightly-test-integrations-1.16.x.yml +++ b/.github/workflows/nightly-test-integrations-1.19.x.yml @@ -1,12 +1,12 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -name: Nightly test-integrations 1.16.x +name: Nightly test-integrations 1.19.x on: schedule: # Run nightly at 1AM UTC/9PM EST/6PM PST - - cron: '* 1 * * *' + - cron: '0 1 * * *' workflow_dispatch: {} env: @@ -19,8 +19,8 @@ env: # strip the hashicorp/ off the front of github.repository for consul CONSUL_LATEST_IMAGE_NAME: ${{ endsWith(github.repository, '-enterprise') && github.repository || 'hashicorp/consul' }} GOPRIVATE: github.com/hashicorp # Required for enterprise deps - BRANCH: "release/1.16.x" - BRANCH_NAME: "release-1.16.x" # Used for naming artifacts + BRANCH: "release/1.19.x" + BRANCH_NAME: "release-1.19.x" # Used for naming artifacts jobs: setup: @@ -34,7 +34,7 @@ jobs: enterprise: ${{ steps.runners.outputs.enterprise }} steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} - id: runners @@ -42,6 +42,13 @@ jobs: get-go-version: uses: ./.github/workflows/reusable-get-go-version.yml + with: + ref: release/1.19.x + + get-envoy-versions: + uses: ./.github/workflows/reusable-get-envoy-versions.yml + with: + ref: release/1.19.x dev-build: needs: @@ -52,7 +59,7 @@ jobs: runs-on: ${{ needs.setup.outputs.compute-large }} repository-name: ${{ github.repository }} uploaded-binary-name: 'consul-bin' - branch-name: "release/1.16.x" + branch-name: "release/1.19.x" go-version: ${{ needs.get-go-version.outputs.go-version }} secrets: elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} @@ -65,42 +72,40 @@ jobs: envoy-matrix: ${{ steps.set-matrix.outputs.envoy-matrix }} steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} - name: Generate Envoy Job Matrix id: set-matrix env: - # this is further going to multiplied in envoy-integration tests by the - # other dimensions in the matrix. Currently TOTAL_RUNNERS would be - # multiplied by 8 based on these values: - # envoy-version: ["1.23.12", "1.24.12", "1.25.11", "1.26.8"] - # xds-target: ["server", "client"] - TOTAL_RUNNERS: 8 + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | - NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) - if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then - echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$((NUM_DIRS-1)) + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) fi - # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. - NUM_RUNNERS=$((NUM_RUNNERS-1)) + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) { echo -n "envoy-matrix=" find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ | xargs -0 -n 1 basename \ - | jq --raw-input --argjson runnercount "$NUM_RUNNERS" "$JQ_SLICER" \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ | jq --compact-output 'map(join("|"))' } >> "$GITHUB_OUTPUT" - + envoy-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} needs: - setup - get-go-version + - get-envoy-versions - generate-envoy-job-matrices - dev-build permissions: @@ -109,7 +114,7 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.23.12", "1.24.12", "1.25.11", "1.26.8"] + envoy-version: ${{ fromJSON(needs.get-envoy-versions.outputs.envoy-versions-json) }} xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: @@ -118,15 +123,15 @@ jobs: AWS_LAMBDA_REGION: us-west-2 steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - name: fetch binary - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' path: ./bin @@ -134,12 +139,13 @@ jobs: run: chmod +x ./bin/consul - name: Set up Docker Buildx - uses: docker/setup-buildx-action@2a1a44ac4aa01993040736bd95bb470da1a38365 # v2.9.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Docker build run: docker build -t consul:local -f ./build-support/docker/Consul-Dev.dockerfile ./bin - name: Envoy Integration Tests + id: envoy-integration-tests env: GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml GOTESTSUM_FORMAT: standard-verbose @@ -160,6 +166,23 @@ jobs: --packages=./test/integration/connect/envoy \ -- -timeout=30m -tags integration -run="TestEnvoy/(${{ matrix.test-cases }})" + # See https://github.com/orgs/community/discussions/8945#discussioncomment-9897011 + # and overall topic discussion for why this is necessary. + - name: Generate artifact ID + id: generate-artifact-id + if: ${{ failure() && steps.envoy-integration-tests.conclusion == 'failure' }} + run: | + ARTIFACT_ID=$(uuidgen) + echo "Artifact ID: $ARTIFACT_ID (search this in job summary for download link)" + echo "artifact_id=$ARTIFACT_ID" >> "$GITHUB_ENV" + + - name: Upload failure logs + if: ${{ failure() && steps.envoy-integration-tests.conclusion == 'failure' }} + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + name: envoy-${{ matrix.envoy-version }}-logs-${{ env.artifact_id }} + path: test/integration/connect/envoy/workdir/logs/ + # NOTE: ENT specific step as we store secrets in Vault. - name: Authenticate to Vault if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} @@ -170,7 +193,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -197,6 +220,7 @@ jobs: needs: - setup - get-go-version + - get-envoy-versions - dev-build permissions: id-token: write # NOTE: this permission is explicitly required for Vault auth. @@ -204,27 +228,37 @@ jobs: strategy: fail-fast: false matrix: - consul-version: ["1.14", "1.15", "1.16"] + consul-version: ["1.15", "1.17", "1.18", "1.19"] env: CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} - ENVOY_VERSION: "1.24.6" + # ENVOY_VERSION should be the latest version supported by _all_ Consul versions in the + # matrix.consul-version, since we are testing upgrade from an older Consul version. + # In practice, this should be the highest Envoy version supported by the lowest non-LTS + # Consul version in the matrix (LTS versions receive additional Envoy version support). + # + # This value should be kept current in new nightly test workflows, and updated any time + # a new major Envoy release is added to the set supported by Consul versions in + # matrix.consul-version (i.e. whenever the highest common Envoy version across active + # Consul versions changes). The minor Envoy version does not necessarily need to be + # kept current for the purpose of these tests, but the major (1.N) version should be. + ENVOY_VERSION: 1.27.6 steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: go env # Get go binary from workspace - name: fetch binary - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' path: . @@ -296,7 +330,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -318,6 +352,96 @@ jobs: DD_ENV: ci run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" $TEST_RESULTS_DIR/results.xml + upgrade-integration-test-deployer: + runs-on: ${{ fromJSON(needs.setup.outputs.compute-large ) }} + needs: + - setup + - get-go-version + - dev-build + permissions: + id-token: write # NOTE: this permission is explicitly required for Vault auth. + contents: read + strategy: + fail-fast: false + matrix: + consul-version: ["1.15", "1.17", "1.18"] + env: + CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} + steps: + - name: Checkout code + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + ref: ${{ env.BRANCH }} + # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. + - name: Setup Git + if: ${{ endsWith(github.repository, '-enterprise') }} + run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + with: + go-version: ${{ needs.get-go-version.outputs.go-version }} + - run: go env + - name: Build image + run: make test-deployer-setup + - name: Upgrade Integration Tests + run: | + mkdir -p "${{ env.TEST_RESULTS_DIR }}" + export NOLOGBUFFER=1 + cd ./test-integ/upgrade + docker run --rm ${{ env.CONSUL_LATEST_IMAGE_NAME }}:local consul version + go run gotest.tools/gotestsum@v${{env.GOTESTSUM_VERSION}} \ + --raw-command \ + --format=standard-verbose \ + --debug \ + --packages="./..." \ + -- \ + go test \ + -tags "${{ env.GOTAGS }}" \ + -timeout=60m \ + -parallel=2 \ + -json \ + ./... \ + --target-image ${{ env.CONSUL_LATEST_IMAGE_NAME }} \ + --target-version local \ + --latest-image docker.mirror.hashicorp.services/${{ env.CONSUL_LATEST_IMAGE_NAME }} \ + --latest-version "${{ env.CONSUL_LATEST_VERSION }}" + env: + # this is needed because of incompatibility between RYUK container and GHA + GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml + GOTESTSUM_FORMAT: standard-verbose + COMPOSE_INTERACTIVE_NO_CLI: 1 + # tput complains if this isn't set to something. + TERM: ansi + # NOTE: ENT specific step as we store secrets in Vault. + - name: Authenticate to Vault + if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} + id: vault-auth + run: vault-auth + + # NOTE: ENT specific step as we store secrets in Vault. + - name: Fetch Secrets + if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} + id: secrets + uses: hashicorp/vault-action@v3 + with: + url: ${{ steps.vault-auth.outputs.addr }} + caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} + token: ${{ steps.vault-auth.outputs.token }} + secrets: | + kv/data/github/${{ github.repository }}/datadog apikey | DATADOG_API_KEY; + + - name: prepare datadog-ci + if: ${{ !cancelled() && !endsWith(github.repository, '-enterprise') }} + run: | + curl -L --fail "https://github.com/DataDog/datadog-ci/releases/latest/download/datadog-ci_linux-x64" --output "/usr/local/bin/datadog-ci" + chmod +x /usr/local/bin/datadog-ci + + - name: upload coverage + # do not run on forks + if: ${{ !cancelled() && github.event.pull_request.head.repo.full_name == github.repository }} + env: + DATADOG_API_KEY: "${{ endsWith(github.repository, '-enterprise') && env.DATADOG_API_KEY || secrets.DATADOG_API_KEY }}" + DD_ENV: ci + run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" $TEST_RESULTS_DIR/results.xml test-integrations-success: needs: @@ -326,6 +450,7 @@ jobs: - generate-envoy-job-matrices - envoy-integration-test - upgrade-integration-test + - upgrade-integration-test-deployer runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} if: ${{ always() }} steps: @@ -339,7 +464,7 @@ jobs: - name: Notify Slack if: ${{ failure() }} id: slack - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: | { diff --git a/.github/workflows/nightly-test-integrations-1.17.x.yml b/.github/workflows/nightly-test-integrations-1.20.x.yml similarity index 80% rename from .github/workflows/nightly-test-integrations-1.17.x.yml rename to .github/workflows/nightly-test-integrations-1.20.x.yml index c936d887d6..2687ea6992 100644 --- a/.github/workflows/nightly-test-integrations-1.17.x.yml +++ b/.github/workflows/nightly-test-integrations-1.20.x.yml @@ -1,12 +1,12 @@ # Copyright (c) HashiCorp, Inc. # SPDX-License-Identifier: MPL-2.0 -name: Nightly test-integrations 1.17.x +name: Nightly test-integrations 1.20.x on: schedule: # Run nightly at 1AM UTC/9PM EST/6PM PST - - cron: '* 1 * * *' + - cron: '0 1 * * *' workflow_dispatch: {} env: @@ -19,8 +19,8 @@ env: # strip the hashicorp/ off the front of github.repository for consul CONSUL_LATEST_IMAGE_NAME: ${{ endsWith(github.repository, '-enterprise') && github.repository || 'hashicorp/consul' }} GOPRIVATE: github.com/hashicorp # Required for enterprise deps - BRANCH: "release/1.17.x" - BRANCH_NAME: "release-1.17.x" # Used for naming artifacts + BRANCH: "release/1.20.x" + BRANCH_NAME: "release-1.20.x" # Used for naming artifacts jobs: setup: @@ -34,7 +34,7 @@ jobs: enterprise: ${{ steps.runners.outputs.enterprise }} steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} - id: runners @@ -42,6 +42,13 @@ jobs: get-go-version: uses: ./.github/workflows/reusable-get-go-version.yml + with: + ref: release/1.20.x + + get-envoy-versions: + uses: ./.github/workflows/reusable-get-envoy-versions.yml + with: + ref: release/1.20.x dev-build: needs: @@ -52,7 +59,7 @@ jobs: runs-on: ${{ needs.setup.outputs.compute-large }} repository-name: ${{ github.repository }} uploaded-binary-name: 'consul-bin' - branch-name: "release/1.17.x" + branch-name: "release/1.20.x" go-version: ${{ needs.get-go-version.outputs.go-version }} secrets: elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} @@ -65,42 +72,40 @@ jobs: envoy-matrix: ${{ steps.set-matrix.outputs.envoy-matrix }} steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} - name: Generate Envoy Job Matrix id: set-matrix env: - # this is further going to multiplied in envoy-integration tests by the - # other dimensions in the matrix. Currently TOTAL_RUNNERS would be - # multiplied by 8 based on these values: - # envoy-version: ["1.24.12", "1.25.11", "1.26.8", "1.27.4"] - # xds-target: ["server", "client"] - TOTAL_RUNNERS: 4 + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | - NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) - if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then - echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$((NUM_DIRS-1)) + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) fi - # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. - NUM_RUNNERS=$((NUM_RUNNERS-1)) + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) { echo -n "envoy-matrix=" find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ | xargs -0 -n 1 basename \ - | jq --raw-input --argjson runnercount "$NUM_RUNNERS" "$JQ_SLICER" \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ | jq --compact-output 'map(join("|"))' } >> "$GITHUB_OUTPUT" - + envoy-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} needs: - setup - get-go-version + - get-envoy-versions - generate-envoy-job-matrices - dev-build permissions: @@ -109,7 +114,7 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.24.12", "1.25.11", "1.26.8", "1.27.4"] + envoy-version: ${{ fromJSON(needs.get-envoy-versions.outputs.envoy-versions-json) }} xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: @@ -118,15 +123,15 @@ jobs: AWS_LAMBDA_REGION: us-west-2 steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - name: fetch binary - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' path: ./bin @@ -134,12 +139,13 @@ jobs: run: chmod +x ./bin/consul - name: Set up Docker Buildx - uses: docker/setup-buildx-action@2a1a44ac4aa01993040736bd95bb470da1a38365 # v2.9.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Docker build run: docker build -t consul:local -f ./build-support/docker/Consul-Dev.dockerfile ./bin - name: Envoy Integration Tests + id: envoy-integration-tests env: GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml GOTESTSUM_FORMAT: standard-verbose @@ -160,6 +166,23 @@ jobs: --packages=./test/integration/connect/envoy \ -- -timeout=30m -tags integration -run="TestEnvoy/(${{ matrix.test-cases }})" + # See https://github.com/orgs/community/discussions/8945#discussioncomment-9897011 + # and overall topic discussion for why this is necessary. + - name: Generate artifact ID + id: generate-artifact-id + if: ${{ failure() && steps.envoy-integration-tests.conclusion == 'failure' }} + run: | + ARTIFACT_ID=$(uuidgen) + echo "Artifact ID: $ARTIFACT_ID (search this in job summary for download link)" + echo "artifact_id=$ARTIFACT_ID" >> "$GITHUB_ENV" + + - name: Upload failure logs + if: ${{ failure() && steps.envoy-integration-tests.conclusion == 'failure' }} + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + name: envoy-${{ matrix.envoy-version }}-logs-${{ env.artifact_id }} + path: test/integration/connect/envoy/workdir/logs/ + # NOTE: ENT specific step as we store secrets in Vault. - name: Authenticate to Vault if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} @@ -170,7 +193,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -197,6 +220,7 @@ jobs: needs: - setup - get-go-version + - get-envoy-versions - dev-build permissions: id-token: write # NOTE: this permission is explicitly required for Vault auth. @@ -204,27 +228,37 @@ jobs: strategy: fail-fast: false matrix: - consul-version: ["1.15", "1.16", "1.17"] + consul-version: ["1.15", "1.18", "1.19"] env: CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} - ENVOY_VERSION: "1.24.6" + # ENVOY_VERSION should be the latest version supported by _all_ Consul versions in the + # matrix.consul-version, since we are testing upgrade from an older Consul version. + # In practice, this should be the highest Envoy version supported by the lowest non-LTS + # Consul version in the matrix (LTS versions receive additional Envoy version support). + # + # This value should be kept current in new nightly test workflows, and updated any time + # a new major Envoy release is added to the set supported by Consul versions in + # matrix.consul-version (i.e. whenever the highest common Envoy version across active + # Consul versions changes). The minor Envoy version does not necessarily need to be + # kept current for the purpose of these tests, but the major (1.N) version should be. + ENVOY_VERSION: 1.28.7 steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: go env # Get go binary from workspace - name: fetch binary - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' path: . @@ -296,7 +330,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -330,19 +364,19 @@ jobs: strategy: fail-fast: false matrix: - consul-version: [ "1.15", "1.16", "1.17"] + consul-version: ["1.15", "1.18", "1.19"] env: CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: go env @@ -387,7 +421,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -430,7 +464,7 @@ jobs: - name: Notify Slack if: ${{ failure() }} id: slack - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: | { diff --git a/.github/workflows/nightly-test-integrations.yml b/.github/workflows/nightly-test-integrations.yml index 160388e9d2..cfaa253030 100644 --- a/.github/workflows/nightly-test-integrations.yml +++ b/.github/workflows/nightly-test-integrations.yml @@ -6,7 +6,7 @@ name: Nightly test-integrations on: schedule: # Run nightly at 12AM UTC/8PM EST/5PM PST - - cron: '* 0 * * *' + - cron: '0 0 * * *' workflow_dispatch: {} env: @@ -32,7 +32,7 @@ jobs: enterprise: ${{ steps.runners.outputs.enterprise }} steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ inputs.branch }} - id: runners @@ -41,6 +41,9 @@ jobs: get-go-version: uses: ./.github/workflows/reusable-get-go-version.yml + get-envoy-versions: + uses: ./.github/workflows/reusable-get-envoy-versions.yml + dev-build: needs: - setup @@ -55,41 +58,40 @@ jobs: elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} generate-envoy-job-matrices: - needs: [setup] + needs: + - setup + - get-envoy-versions runs-on: ${{ fromJSON(needs.setup.outputs.compute-small) }} name: Generate Envoy Job Matrices outputs: envoy-matrix: ${{ steps.set-matrix.outputs.envoy-matrix }} steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ inputs.branch }} - name: Generate Envoy Job Matrix id: set-matrix env: - # this is further going to multiplied in envoy-integration tests by the - # other dimensions in the matrix. Currently TOTAL_RUNNERS would be - # multiplied by 8 based on these values: - # envoy-version: ["1.25.11", "1.26.8", "1.27.4", "1.28.2"] - # xds-target: ["server", "client"] - TOTAL_RUNNERS: 8 + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | - NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) - if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then - echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$((NUM_DIRS-1)) + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) fi - # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. - NUM_RUNNERS=$((NUM_RUNNERS-1)) + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) { echo -n "envoy-matrix=" find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ | xargs -0 -n 1 basename \ - | jq --raw-input --argjson runnercount "$NUM_RUNNERS" "$JQ_SLICER" \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ | jq --compact-output 'map(join("|"))' } >> "$GITHUB_OUTPUT" @@ -98,6 +100,7 @@ jobs: needs: - setup - get-go-version + - get-envoy-versions - generate-envoy-job-matrices - dev-build permissions: @@ -106,7 +109,7 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.25.11", "1.26.8", "1.27.4", "1.28.2"] + envoy-version: ${{ fromJSON(needs.get-envoy-versions.outputs.envoy-versions-json) }} xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: @@ -115,15 +118,15 @@ jobs: AWS_LAMBDA_REGION: us-west-2 steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ inputs.branch }} - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - name: fetch binary - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' path: ./bin @@ -131,7 +134,7 @@ jobs: run: chmod +x ./bin/consul - name: Set up Docker Buildx - uses: docker/setup-buildx-action@2a1a44ac4aa01993040736bd95bb470da1a38365 # v2.9.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Docker build run: docker build -t consul:local -f ./build-support/docker/Consul-Dev.dockerfile ./bin @@ -167,7 +170,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -188,7 +191,7 @@ jobs: DATADOG_API_KEY: "${{ endsWith(github.repository, '-enterprise') && env.DATADOG_API_KEY || secrets.DATADOG_API_KEY }}" DD_ENV: ci run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" $TEST_RESULTS_DIR/results.xml - + upgrade-integration-test: runs-on: ${{ fromJSON(needs.setup.outputs.compute-large ) }} needs: @@ -201,30 +204,37 @@ jobs: strategy: fail-fast: false matrix: - consul-version: [ "1.16", "1.17"] + consul-version: ["1.17", "1.18", "1.19"] env: CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} - # ENVOY_VERSION should be the latest version upported by all - # consul versions in the matrix.consul-version, since we are testing upgrade from - # an older consul version, e.g., 1.26.6 is supported by both 1.16 and 1.17. - ENVOY_VERSION: "1.26.6" + # ENVOY_VERSION should be the latest version supported by _all_ Consul versions in the + # matrix.consul-version, since we are testing upgrade from an older Consul version. + # In practice, this should be the highest Envoy version supported by the lowest non-LTS + # Consul version in the matrix (LTS versions receive additional Envoy version support). + # + # This value should be kept current in new nightly test workflows, and updated any time + # a new major Envoy release is added to the set supported by Consul versions in + # matrix.consul-version (i.e. whenever the highest common Envoy version across active + # Consul versions changes). The minor Envoy version does not necessarily need to be + # kept current for the purpose of these tests, but the major (1.N) version should be. + ENVOY_VERSION: 1.27.6 steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ inputs.branch }} # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: go env # Get go binary from workspace - name: fetch binary - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' path: . @@ -296,7 +306,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -329,19 +339,19 @@ jobs: strategy: fail-fast: false matrix: - consul-version: [ "1.16", "1.17"] + consul-version: [ "1.17", "1.18"] env: CONSUL_LATEST_VERSION: ${{ matrix.consul-version }} steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ inputs.branch }} # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: go env @@ -385,7 +395,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -428,7 +438,7 @@ jobs: - name: Notify Slack if: ${{ failure() }} id: slack - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: | { diff --git a/.github/workflows/nightly-test-main.yaml b/.github/workflows/nightly-test-main.yaml index a089121cc8..a3ce2edbb5 100644 --- a/.github/workflows/nightly-test-main.yaml +++ b/.github/workflows/nightly-test-main.yaml @@ -17,12 +17,12 @@ jobs: frontend-test-workspace-node: runs-on: ubuntu-latest steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: 'yarn' @@ -49,12 +49,12 @@ jobs: JOBS: 2 CONSUL_NSPACES_ENABLED: 0 steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: 'yarn' @@ -71,7 +71,7 @@ jobs: run: make build-ci - name: Upload CE Frontend - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: frontend-ce-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -88,12 +88,12 @@ jobs: EMBER_TEST_REPORT: test-results/report-ce.xml #outputs test report for CI test summary EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: 'yarn' @@ -105,7 +105,7 @@ jobs: run: make deps - name: Download CE Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ce-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -121,12 +121,12 @@ jobs: JOBS: 2 CONSUL_NSPACES_ENABLED: 1 steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: 'yarn' @@ -143,7 +143,7 @@ jobs: run: make build-ci - name: Upload ENT Frontend - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -160,12 +160,12 @@ jobs: EMBER_TEST_REPORT: test-results/report-ce.xml #outputs test report for CI test summary EMBER_TEST_PARALLEL: true #enables test parallelization with ember-exam steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: 'yarn' @@ -177,7 +177,7 @@ jobs: run: make deps - name: Download ENT Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -191,12 +191,12 @@ jobs: runs-on: ubuntu-latest needs: [frontend-build-ent] steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ env.BRANCH }} # Not necessary to use yarn, but enables caching - - uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3.7.0 + - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4.0.2 with: node-version: 18 cache: 'yarn' @@ -208,7 +208,7 @@ jobs: run: make deps - name: Download ENT Frontend - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: frontend-ent-${{ env.BRANCH_NAME }} path: ./ui/packages/consul-ui/dist @@ -224,7 +224,7 @@ jobs: steps: - name: Slack Notification id: slack - uses: slackapi/slack-github-action@e28cf165c92ffef168d23c5c9000cffc8a25e117 # v1.24.0 + uses: slackapi/slack-github-action@70cd7be8e40a46e8b0eced40b0de447bdb42f68e # v1.26.0 with: payload: | { diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 0d6b71c9f0..409ba8c8cf 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -1,6 +1,6 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: MPL-2.0 + name: "Pull Request Labeler" on: pull_request_target: @@ -10,7 +10,9 @@ jobs: triage: runs-on: ubuntu-latest steps: - - uses: actions/labeler@0967ca812e7fdc8f5f71402a1b486d5bd061fe20 # v4.2.0 + - name: 'Checkout repo' + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/labeler@8558fd74291d67161a8a78ce36a881fa63b766a9 # v5.0.0 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" configuration-path: .github/pr-labeler.yml diff --git a/.github/workflows/pr-metrics-test-checker.yml b/.github/workflows/pr-metrics-test-checker.yml index d0bdac04f7..f3f15719dc 100644 --- a/.github/workflows/pr-metrics-test-checker.yml +++ b/.github/workflows/pr-metrics-test-checker.yml @@ -14,7 +14,7 @@ jobs: if: "! ( contains(github.event.pull_request.labels.*.name, 'pr/no-metrics-test') || github.event.pull_request.user.login == 'hc-github-team-consul-core' )" runs-on: ubuntu-latest steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 name: "checkout repo" with: ref: ${{ github.event.pull_request.head.sha }} diff --git a/.github/workflows/reusable-check-go-mod.yml b/.github/workflows/reusable-check-go-mod.yml index a646aa0712..7afb296b5a 100644 --- a/.github/workflows/reusable-check-go-mod.yml +++ b/.github/workflows/reusable-check-go-mod.yml @@ -21,12 +21,12 @@ jobs: runs-on: ${{ fromJSON(inputs.runs-on) }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(inputs.repository-name, '-enterprise') }} run: git config --global url."https://${{ secrets.elevated-github-token }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ inputs.go-version }} # Run on all go.mod (include submodules). diff --git a/.github/workflows/reusable-conditional-skip.yml b/.github/workflows/reusable-conditional-skip.yml new file mode 100644 index 0000000000..36a5eaba60 --- /dev/null +++ b/.github/workflows/reusable-conditional-skip.yml @@ -0,0 +1,69 @@ +name: conditional-skip + +on: + workflow_call: + outputs: + skip-ci: + description: "Whether we should skip build and test jobs" + value: ${{ jobs.check-skip.outputs.skip-ci }} + +jobs: + check-skip: + runs-on: ubuntu-latest + name: Check whether to skip build and tests + outputs: + skip-ci: ${{ steps.maybe-skip-ci.outputs.skip-ci }} + steps: + # We only allow use of conditional skip in two scenarios: + # 1. PRs + # 2. Pushes (merges) to protected branches (`main`, `release/**`) + # + # The second scenario is the only place we can be sure that checking just the + # latest change on the branch is sufficient. In PRs, we need to check _all_ commits. + # The ability to do this is ultimately determined by the triggers of the calling + # workflow, since `base_ref` (the target branch of a PR) is only available in + # `pull_request` events, not `push`. + - name: Error if conditional check is not allowed + if: ${{ !github.base_ref && !github.ref_protected }} + run: | + echo "Conditional skip requires a PR event with 'base_ref' or 'push' to a protected branch." + echo "github.base_ref: ${{ github.base_ref }}" + echo "github.ref_protected: ${{ github.ref_protected }}" + echo "github.ref_name: ${{ github.ref_name }}" + echo "Check the triggers of the calling workflow to ensure that these requirements are met." + exit 1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + fetch-depth: 0 + - name: Check for skippable file changes + id: changed-files + uses: tj-actions/changed-files@e9772d140489982e0e3704fea5ee93d536f1e275 # v45.0.1 + with: + # This is a multi-line YAML string with one match pattern per line. + # Do not use quotes around values, as it's not supported. + # See https://github.com/tj-actions/changed-files/blob/main/README.md#inputs-%EF%B8%8F + # for usage, options, and more details on match syntax. + files: | + .github/workflows/reusable-conditional-skip.yml + **.md + docs/** + ui/** + website/** + grafana/** + .changelog/** + - name: Print changed files + env: + SKIPPABLE_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} + NON_SKIPPABLE_FILES: ${{ steps.changed-files.outputs.other_changed_files }} + run: | + echo "Skippable changed files:" + for file in ${SKIPPABLE_CHANGED_FILES}; do echo " $file"; done + echo + echo "Non-skippable files:" + for file in ${NON_SKIPPABLE_FILES}; do echo " $file"; done + - name: Skip tests and build if only skippable files changed + id: maybe-skip-ci + if: ${{ steps.changed-files.outputs.only_changed == 'true' }} + run: | + echo "Skipping tests and build because only skippable files changed" + echo "skip-ci=true" >> $GITHUB_OUTPUT \ No newline at end of file diff --git a/.github/workflows/reusable-dev-build-windows.yml b/.github/workflows/reusable-dev-build-windows.yml index 1417d1dbae..430331608f 100644 --- a/.github/workflows/reusable-dev-build-windows.yml +++ b/.github/workflows/reusable-dev-build-windows.yml @@ -28,12 +28,12 @@ jobs: build: runs-on: 'windows-2019' steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(inputs.repository-name, '-enterprise') }} run: git config --global url."https://${{ secrets.elevated-github-token }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ inputs.go-version }} - name: Build @@ -41,7 +41,7 @@ jobs: GOARCH: ${{ inputs.goarch }} run: go build . # save dev build to pass to downstream jobs - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: ${{inputs.uploaded-binary-name}} path: consul.exe diff --git a/.github/workflows/reusable-dev-build.yml b/.github/workflows/reusable-dev-build.yml index 511ea7925c..ce0e33471f 100644 --- a/.github/workflows/reusable-dev-build.yml +++ b/.github/workflows/reusable-dev-build.yml @@ -34,18 +34,18 @@ jobs: steps: # NOTE: This is used for nightly job of building release branch. - name: Checkout branch ${{ inputs.branch-name }} - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ inputs.branch-name }} if: inputs.branch-name != '' - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 if: inputs.branch-name == '' # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(inputs.repository-name, '-enterprise') }} run: git config --global url."https://${{ secrets.elevated-github-token }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ inputs.go-version }} - name: Build @@ -53,7 +53,7 @@ jobs: GOARCH: ${{ inputs.goarch }} run: make dev # save dev build to pass to downstream jobs - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 with: name: ${{inputs.uploaded-binary-name}} path: ./bin/consul diff --git a/.github/workflows/reusable-get-envoy-versions.yml b/.github/workflows/reusable-get-envoy-versions.yml new file mode 100644 index 0000000000..1d40587cd7 --- /dev/null +++ b/.github/workflows/reusable-get-envoy-versions.yml @@ -0,0 +1,71 @@ +name: get-envoy-versions + +# Reads the canonical ENVOY_VERSIONS file for either the current branch or a specified version of Consul, +# and returns both the max and all supported Envoy versions. + +on: + workflow_call: + inputs: + ref: + description: | + The Consul ref/branch (e.g. release/1.18.x) for which to determine supported Envoy versions. + If not provided, the default actions/checkout value (current ref) is used. + type: string + outputs: + max-envoy-version: + description: The max supported Envoy version for the specified Consul version + value: ${{ jobs.get-envoy-versions.outputs.max-envoy-version }} + envoy-versions: + description: | + All supported Envoy versions for the specified Consul version (formatted as multiline string with one version + per line, in descending order) + value: ${{ jobs.get-envoy-versions.outputs.envoy-versions }} + envoy-versions-json: + description: | + All supported Envoy versions for the specified Consul version (formatted as JSON array) + value: ${{ jobs.get-envoy-versions.outputs.envoy-versions-json }} + +jobs: + get-envoy-versions: + name: "Determine supported Envoy versions" + runs-on: ubuntu-latest + outputs: + max-envoy-version: ${{ steps.get-envoy-versions.outputs.max-envoy-version }} + envoy-versions: ${{ steps.get-envoy-versions.outputs.envoy-versions }} + envoy-versions-json: ${{ steps.get-envoy-versions.outputs.envoy-versions-json }} + steps: + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + # If not set, will default to current branch. + ref: ${{ inputs.ref }} + - name: Determine Envoy versions + id: get-envoy-versions + # Note that this script assumes that the ENVOY_VERSIONS file is in the envoyextensions/xdscommon directory. + # If in the future this file moves between branches, we could introduce a workflow input for the path that + # defaults to the new value, and manually configure the old value as needed. + run: | + MAX_ENVOY_VERSION=$(cat envoyextensions/xdscommon/ENVOY_VERSIONS | grep '^[[:digit:]]' | sort -nr | head -n 1) + ENVOY_VERSIONS=$(cat envoyextensions/xdscommon/ENVOY_VERSIONS | grep '^[[:digit:]]' | sort -nr) + ENVOY_VERSIONS_JSON=$(echo -n '[' && echo "${ENVOY_VERSIONS}" | awk '{printf "\"%s\",", $0}' | sed 's/,$//' && echo -n ']') + + # Loop through each line of ENVOY_VERSIONS and compare it to the regex + while IFS= read -r version; do + if ! [[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo 'Invalid version in ENVOY_VERSIONS: '$version' does not match the pattern ^[0-9]+\.[0-9]+\.[0-9]+$' + exit 1 + fi + done <<< "$ENVOY_VERSIONS" + if ! [[ $MAX_ENVOY_VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo 'Invalid MAX_ENVOY_VERSION: '$MAX_ENVOY_VERSION' does not match the pattern ^[0-9]+\.[0-9]+\.[0-9]+$' + exit 1 + fi + + echo "Supported Envoy versions:" + echo "${ENVOY_VERSIONS}" + echo "envoy-versions<> $GITHUB_OUTPUT + echo "${ENVOY_VERSIONS}" >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + echo "Supported Envoy versions JSON: ${ENVOY_VERSIONS_JSON}" + echo "envoy-versions-json=${ENVOY_VERSIONS_JSON}" >> $GITHUB_OUTPUT + echo "Max supported Envoy version: ${MAX_ENVOY_VERSION}" + echo "max-envoy-version=${MAX_ENVOY_VERSION}" >> $GITHUB_OUTPUT diff --git a/.github/workflows/reusable-get-go-version.yml b/.github/workflows/reusable-get-go-version.yml index ea2d6f5c8f..91e870c73c 100644 --- a/.github/workflows/reusable-get-go-version.yml +++ b/.github/workflows/reusable-get-go-version.yml @@ -2,6 +2,12 @@ name: get-go-version on: workflow_call: + inputs: + ref: + description: | + The Consul ref/branch (e.g. release/1.18.x) for which to determine the Go version. + If not provided, the default actions/checkout value (current ref) is used. + type: string outputs: go-version: description: "The Go version detected by this workflow" @@ -18,7 +24,10 @@ jobs: go-version: ${{ steps.get-go-version.outputs.go-version }} go-version-previous: ${{ steps.get-go-version.outputs.go-version-previous }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + with: + # If not set, will default to current branch. + ref: ${{ inputs.ref }} - name: Determine Go version id: get-go-version # We use .go-version as our source of truth for current Go diff --git a/.github/workflows/reusable-lint.yml b/.github/workflows/reusable-lint.yml index b834d56491..836a0bd3f6 100644 --- a/.github/workflows/reusable-lint.yml +++ b/.github/workflows/reusable-lint.yml @@ -42,19 +42,19 @@ jobs: fail-fast: true name: lint ${{ matrix.directory }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(inputs.repository-name, '-enterprise') }} run: git config --global url."https://${{ secrets.elevated-github-token }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ inputs.go-version }} - run: go env - name: Set golangci-lint version run: echo "GOLANGCI_LINT_VERSION=$(make --no-print-directory print-GOLANGCI_LINT_VERSION)" >> $GITHUB_ENV - name: lint-${{ matrix.directory }} - uses: golangci/golangci-lint-action@639cd343e1d3b897ff35927a75193d57cfcba299 # v3.6.0 + uses: golangci/golangci-lint-action@82d40c283aeb1f2b6595839195e95c2d6a49081b # v5.0.0 with: working-directory: ${{ matrix.directory }} version: ${{ env.GOLANGCI_LINT_VERSION }} diff --git a/.github/workflows/reusable-unit-split.yml b/.github/workflows/reusable-unit-split.yml index ab16db368b..ba0e88bbf1 100644 --- a/.github/workflows/reusable-unit-split.yml +++ b/.github/workflows/reusable-unit-split.yml @@ -63,8 +63,8 @@ jobs: outputs: package-matrix: ${{ steps.set-matrix.outputs.matrix }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ inputs.go-version }} - id: set-matrix @@ -86,12 +86,20 @@ jobs: ulimit -Sa echo "Hard limits" ulimit -Ha - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + # upload-artifact requires a unique ID per run. These steps will be repeated with the matrix run, and other unit tests + # will also overlap with the names here. We use a random string rather than trying to do trickery + # with the package matrix. + - id: generate-matrix-id + run: | + MATRIX_RUN_ID=$(head /dev/urandom | tr -dc A-Z | head -c8) + echo "The matrix run ID is $MATRIX_RUN_ID" + echo "matrix-run-id=$MATRIX_RUN_ID" >> "$GITHUB_OUTPUT" + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(inputs.repository-name, '-enterprise') }} run: git config --global url."https://${{ secrets.elevated-github-token }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ inputs.go-version }} - run: mkdir -p ${{env.TEST_RESULTS}} @@ -99,7 +107,7 @@ jobs: working-directory: ${{inputs.directory}} run: go mod download - name: Download consul - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: ${{inputs.uploaded-binary-name}} path: ${{inputs.directory}} @@ -143,7 +151,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -164,15 +172,15 @@ jobs: DD_ENV: ci run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" ${{env.TEST_RESULTS}}/gotestsum-report.xml - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: ${{ !cancelled() }} with: - name: test-results + name: ${{ steps.generate-matrix-id.outputs.matrix-run-id }}-test-results path: ${{env.TEST_RESULTS}} - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: ${{ !cancelled() }} with: - name: jsonfile + name: ${{ steps.generate-matrix-id.outputs.matrix-run-id }}-jsonfile path: /tmp/jsonfile - name: "Re-run fails report" if: ${{ !cancelled() }} diff --git a/.github/workflows/reusable-unit.yml b/.github/workflows/reusable-unit.yml index 072999d79e..db06329401 100644 --- a/.github/workflows/reusable-unit.yml +++ b/.github/workflows/reusable-unit.yml @@ -56,12 +56,12 @@ jobs: go-test: runs-on: ${{ fromJSON(inputs.runs-on) }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(inputs.repository-name, '-enterprise') }} run: git config --global url."https://${{ secrets.elevated-github-token }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ inputs.go-version }} - run: mkdir -p ${{env.TEST_RESULTS}} @@ -69,7 +69,7 @@ jobs: working-directory: ${{inputs.directory}} run: go mod download - name: Download consul - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: ${{inputs.uploaded-binary-name}} path: ${{inputs.directory}} @@ -110,7 +110,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -130,16 +130,22 @@ jobs: env: DD_ENV: ci run: datadog-ci junit upload --service "$GITHUB_REPOSITORY" ${{env.TEST_RESULTS}}/gotestsum-report.xml - - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + # upload-artifact requires a unique ID per run. These steps will overlap with other users of the reusable workflow. + # We use a random string rather than trying to pass in some identifying information. + - id: generate-run-id + run: | + RUN_ID=$(head /dev/urandom | tr -dc A-Z | head -c8) + echo "The run ID is $RUN_ID" + echo "run-id=$RUN_ID" >> "$GITHUB_OUTPUT" + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: ${{ !cancelled() }} with: - name: test-results + name: ${{ steps.generate-run-id.outputs.run-id }}-test-results path: ${{env.TEST_RESULTS}} - - uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 + - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 if: ${{ !cancelled() }} with: - name: jsonfile + name: ${{ steps.generate-run-id.outputs.run-id }}-jsonfile path: /tmp/jsonfile - name: "Re-run fails report" if: ${{ !cancelled() }} diff --git a/.github/workflows/security-scan.yml b/.github/workflows/security-scan.yml index 60c91baf90..63e00847fc 100644 --- a/.github/workflows/security-scan.yml +++ b/.github/workflows/security-scan.yml @@ -1,3 +1,5 @@ +# This job runs a non-blocking informational security scan on the repository. +# For release-blocking security scans, see .release/security-scan.hcl. name: Security Scan on: @@ -9,6 +11,12 @@ on: branches: - main - release/** + # paths-ignore only works for non-required checks. + # Jobs that are required for merge must use reusable-conditional-skip.yml. + paths-ignore: + - 'docs/**' + - 'grafana/**' + - '.changelog/**' # cancel existing runs of the same workflow on the same ref concurrency: @@ -16,23 +24,8 @@ concurrency: cancel-in-progress: true jobs: - conditional-skip: - runs-on: ubuntu-latest - name: Get files changed and conditionally skip CI - outputs: - skip-ci: ${{ steps.read-files.outputs.skip-ci }} - steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - with: - fetch-depth: 0 - - name: Get changed files - id: read-files - run: ./.github/scripts/filter_changed_files_go_test.sh - setup: - needs: [conditional-skip] name: Setup - if: needs.conditional-skip.outputs.skip-ci != 'true' runs-on: ubuntu-latest outputs: compute-small: ${{ steps.setup-outputs.outputs.compute-small }} @@ -40,7 +33,7 @@ jobs: compute-large: ${{ steps.setup-outputs.outputs.compute-large }} compute-xl: ${{ steps.setup-outputs.outputs.compute-xl }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - id: setup-outputs name: Setup outputs run: ./.github/scripts/get_runner_classes.sh @@ -59,18 +52,18 @@ jobs: && (github.actor != 'dependabot[bot]') && (github.actor != 'hc-github-team-consul-core') }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - name: Set up Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@0a12ed9d6a96ab950c8f026ed9f722fe0da7ef32 # v5.0.2 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - name: Clone Security Scanner repo - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: repository: hashicorp/security-scanner - token: ${{ secrets.HASHIBOT_PRODSEC_GITHUB_TOKEN }} + token: ${{ secrets.PRODSEC_SCANNER_READ_ONLY }} path: security-scanner ref: main @@ -87,6 +80,6 @@ jobs: cat results.sarif | jq - name: Upload SARIF file - uses: github/codeql-action/upload-sarif@46a6823b81f2d7c67ddf123851eea88365bc8a67 # codeql-bundle-v2.13.5 + uses: github/codeql-action/upload-sarif@8fd294e26a0e458834582b0fe4988d79966c7c0a # codeql-bundle-v2.18.4 with: - sarif_file: results.sarif \ No newline at end of file + sarif_file: results.sarif diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index ff07a961a4..a24da99989 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -12,7 +12,7 @@ jobs: permissions: pull-requests: write steps: - - uses: actions/stale@1160a2240286f5da8ec72b1c0816ce2481aabf84 # v8.0.0 + - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 with: days-before-stale: -1 days-before-close: -1 diff --git a/.github/workflows/test-integrations-windows.yml b/.github/workflows/test-integrations-windows.yml deleted file mode 100644 index 3dbb55400c..0000000000 --- a/.github/workflows/test-integrations-windows.yml +++ /dev/null @@ -1,1219 +0,0 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - -name: test-integrations-windows - -on: - workflow_dispatch: - -env: - TEST_RESULTS_DIR: /tmp/test-results - TEST_RESULTS_ARTIFACT_NAME: test-results - CONSUL_LICENSE: ${{ secrets.CONSUL_LICENSE }} - GOTAGS: ${{ endsWith(github.repository, '-enterprise') && 'consulent' || '' }} - GOTESTSUM_VERSION: "1.11.0" - CONSUL_BINARY_UPLOAD_NAME: consul.exe - # strip the hashicorp/ off the front of github.repository for consul - CONSUL_LATEST_IMAGE_NAME: ${{ endsWith(github.repository, '-enterprise') && github.repository || 'consul' }} - GOPRIVATE: github.com/hashicorp # Required for enterprise deps - -jobs: - setup: - runs-on: ubuntu-latest - name: Setup - outputs: - compute-small: ${{ steps.runners.outputs.compute-small }} - compute-medium: ${{ steps.runners.outputs.compute-medium }} - compute-large: ${{ steps.runners.outputs.compute-large }} - compute-xl: ${{ steps.runners.outputs.compute-xl }} - enterprise: ${{ steps.runners.outputs.enterprise }} - steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - - id: runners - run: .github/scripts/get_runner_classes_windows.sh - - get-go-version: - uses: ./.github/workflows/reusable-get-go-version.yml - - dev-build: - needs: - - setup - - get-go-version - uses: ./.github/workflows/reusable-dev-build-windows.yml - with: - runs-on: ${{ needs.setup.outputs.compute-large }} - repository-name: ${{ github.repository }} - uploaded-binary-name: 'consul.exe' - go-version: ${{ needs.get-go-version.outputs.go-version }} - secrets: - elevated-github-token: ${{ secrets.ELEVATED_GITHUB_TOKEN }} - - # NOTE: Jobs needs to be added here manually. Jobs when run together on windows fails intermittently. - # So they are run independently of each other. - envoy-integration-test: - needs: - - setup - - get-go-version - - dev-build - runs-on: ${{ fromJSON(needs.setup.outputs.compute-large) }} - permissions: - id-token: write # NOTE: this permission is explicitly required for Vault auth. - contents: read - strategy: - fail-fast: false - matrix: - envoy-version: [ "1.28.2" ] - xds-target: [ "server", "client" ] - env: - ENVOY_VERSION: ${{ matrix.envoy-version }} - XDS_TARGET: ${{ matrix.xds-target }} - AWS_LAMBDA_REGION: us-west-2 - steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 - with: - go-version: ${{ needs.get-go-version.outputs.go-version }} - - - name: Fetch binary - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 - with: - name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' - path: ${{ github.workspace }} - - - name: Create dist folder and copy binary - run: | - mkdir dist - cp ${{ github.workspace }}\consul.exe dist\ - - - name: Restore mode+x - run: icacls ${{ github.workspace }}\consul.exe /grant:rx Everyone:RX - - - name: Setup TcpDump Docker Image - shell: bash - run: | - cd test/integration/connect/envoy - curl -sSL "https://asheshvidyut-bucket.s3.ap-southeast-2.amazonaws.com/tcpdump.exe" -o tcpdump.exe - docker build -t envoy-tcpdump -f Dockerfile-tcpdump-windows . - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@2a1a44ac4aa01993040736bd95bb470da1a38365 # v2.9.0 - - - name: Docker build consul - run: docker build -t windows/consul -f Dockerfile-windows . - - - name: Docker build consul local - shell: bash - run: cd build-support/windows && ./build-consul-local-images.sh - - - name: Docker build consul dev - shell: bash - run: cd build-support/windows && ./build-consul-dev-image.sh - - # https://hashicorp.atlassian.net/browse/NET-4973 - # ^ Ticket to figure out why grouping test case is failing on Windows Machine - -# - name: Envoy Integration Tests for windows case-api-gateway-http-hostnames -# shell: bash -# if: always() -# env: -# GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml -# GOTESTSUM_FORMAT: standard-verbose -# COMPOSE_INTERACTIVE_NO_CLI: 1 -# LAMBDA_TESTS_ENABLED: "true" -# # tput complains if this isn't set to something. -# TERM: ansi -# run: | -# # shellcheck disable=SC2001 -# echo "Running Integration Test case-api-gateway-http-hostnames" -# # shellcheck disable=SC2001 -# go test -v -timeout=45m -tags integration \ -# ./test/integration/connect/envoy -run="TestEnvoy/case-api-gateway-http-hostnames" -win=true - - - name: Envoy Integration Tests for windows case-api-gateway-http-simple - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-api-gateway-http-simple" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-api-gateway-http-simple" -win=true - - - name: Envoy Integration Tests for windows case-api-gateway-http-splitter-targets - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-api-gateway-http-splitter-targets" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-api-gateway-http-splitter-targets" -win=true - - - name: Envoy Integration Tests for windows case-api-gateway-http-tls-overlapping-hosts - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-api-gateway-http-tls-overlapping-hosts" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-api-gateway-http-tls-overlapping-hosts" -win=true - - - name: Envoy Integration Tests for windows case-api-gateway-tcp-conflicted - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-api-gateway-tcp-conflicted" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-api-gateway-tcp-conflicted" -win=true - - - name: Envoy Integration Tests for windows case-api-gateway-tcp-simple - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-api-gateway-tcp-simple" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-api-gateway-tcp-simple" -win=true - - - name: Envoy Integration Tests for windows case-api-gateway-tcp-tls-overlapping-hosts - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-api-gateway-tcp-tls-overlapping-hosts" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-api-gateway-tcp-tls-overlapping-hosts" -win=true - - - name: Envoy Integration Tests for windows case-badauthz - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-badauthz" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-badauthz" -win=true - - - name: Envoy Integration Tests for windows case-basic - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-basic" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-basic" -win=true - - - name: Envoy Integration Tests for windows case-centralconf - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-centralconf" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-centralconf" -win=true - - - name: Envoy Integration Tests for windows case-cfg-resolver-cluster-peering-failover - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-resolver-cluster-peering-failover" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-resolver-cluster-peering-failover" -win=true - - - name: Envoy Integration Tests for windows case-cfg-resolver-dc-failover-gateways-none - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-resolver-dc-failover-gateways-none" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-resolver-dc-failover-gateways-none" -win=true - - - name: Envoy Integration Tests for windows case-cfg-resolver-dc-failover-gateways-remote - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-resolver-dc-failover-gateways-remote" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-resolver-dc-failover-gateways-remote" -win=true - - - name: Envoy Integration Tests for windows case-cfg-resolver-defaultsubset - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-resolver-defaultsubset" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-resolver-defaultsubset" -win=true - - - name: Envoy Integration Tests for windows case-cfg-resolver-features - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-resolver-features" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-resolver-features" -win=true - - - name: Envoy Integration Tests for windows case-cfg-resolver-subset-onlypassing - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-resolver-subset-onlypassing" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-resolver-subset-onlypassing" -win=true - - - name: Envoy Integration Tests for windows case-cfg-resolver-subset-redirect - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-resolver-subset-redirect" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-resolver-subset-redirect" -win=true - - - name: Envoy Integration Tests for windows case-cfg-resolver-svc-failover - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-resolver-svc-failover" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-resolver-svc-failover" -win=true - - - name: Envoy Integration Tests for windows case-cfg-resolver-svc-redirect-http - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-resolver-svc-redirect-http" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-resolver-svc-redirect-http" -win=true - - - name: Envoy Integration Tests for windows case-cfg-resolver-svc-redirect-tcp - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-resolver-svc-redirect-tcp" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-resolver-svc-redirect-tcp" -win=true - - - name: Envoy Integration Tests for windows case-cfg-router-features - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-router-features" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-router-features" -win=true - - - name: Envoy Integration Tests for windows case-cfg-splitter-cluster-peering - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-splitter-cluster-peering" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-splitter-cluster-peering" -win=true - - - name: Envoy Integration Tests for windows case-cfg-splitter-features - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-splitter-features" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-splitter-features" -win=true - - - name: Envoy Integration Tests for windows case-cfg-splitter-peering-ingress-gateways - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cfg-splitter-peering-ingress-gateways" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cfg-splitter-peering-ingress-gateways" -win=true - - # This test runs fine on windows machine but fails on CI - # Task to be picked later on - https://hashicorp.atlassian.net/browse/NET-4972 - # - name: Envoy Integration Tests for windows case-consul-exec - # if: always() - # shell: bash - # env: - # GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - # GOTESTSUM_FORMAT: standard-verbose - # COMPOSE_INTERACTIVE_NO_CLI: 1 - # LAMBDA_TESTS_ENABLED: "true" - # # tput complains if this isn't set to something. - # TERM: ansi - # run: | - # #shellcheck disable=SC2001 - # echo "Running Integration Test case-consul-exec" - # # shellcheck disable=SC2001 - # go test -v -timeout=45m -tags integration \ - # ./test/integration/connect/envoy -run="TestEnvoy/case-consul-exec" -win=true - - - name: Envoy Integration Tests for windows case-cross-peer-control-plane-mgw - if: always() - shell: bash - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cross-peer-control-plane-mgw" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cross-peer-control-plane-mgw" -win=true - - - name: Envoy Integration Tests for windows case-cross-peers - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cross-peers" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cross-peers" -win=true - - - name: Envoy Integration Tests for windows case-cross-peers-http - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cross-peers-http" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cross-peers-http" -win=true - - - name: Envoy Integration Tests for windows case-cross-peers-http-router - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cross-peers-http-router" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cross-peers-http-router" -win=true - - - name: Envoy Integration Tests for windows case-cross-peers-resolver-redirect-tcp - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-cross-peers-resolver-redirect-tcp" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-cross-peers-resolver-redirect-tcp" -win=true - - - name: Envoy Integration Tests for windows case-dogstatsd-udp - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-dogstatsd-udp" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-dogstatsd-udp" -win=true - - - name: Envoy Integration Tests for windows case-envoyext-ratelimit - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-envoyext-ratelimit" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-envoyext-ratelimit" -win=true - - - name: Envoy Integration Tests for windows case-expose-checks - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-expose-checks" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-expose-checks" -win=true - - - name: Envoy Integration Tests for windows case-gateway-without-services - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-gateway-without-services" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-gateway-without-services" -win=true - - - name: Envoy Integration Tests for windows case-gateways-local - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-gateways-local" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-gateways-local" -win=true - - - name: Envoy Integration Tests for windows case-gateways-remote - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-gateways-remote" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-gateways-remote" -win=true - - - name: Envoy Integration Tests for windows case-grpc - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-grpc" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-grpc" -win=true - - - name: Envoy Integration Tests for windows case-http - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-http" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-http" -win=true - - - name: Envoy Integration Tests for windows case-http-badauthz - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-http-badauthz" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-http-badauthz" -win=true - - - name: Envoy Integration Tests for windows case-ingress-gateway-grpc - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-ingress-gateway-grpc" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-ingress-gateway-grpc" -win=true - - - name: Envoy Integration Tests for windows case-ingress-gateway-http - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-ingress-gateway-http" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-ingress-gateway-http" -win=true - - - name: Envoy Integration Tests for windows case-ingress-gateway-multiple-services - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-ingress-gateway-multiple-services" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-ingress-gateway-multiple-services" -win=true - - - name: Envoy Integration Tests for windows case-ingress-gateway-peering-failover - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-ingress-gateway-peering-failover" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-ingress-gateway-peering-failover" -win=true - - - name: Envoy Integration Tests for windows case-ingress-gateway-simple - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-ingress-gateway-simple" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-ingress-gateway-simple" -win=true - - - name: Envoy Integration Tests for windows case-ingress-mesh-gateways-resolver - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-ingress-mesh-gateways-resolver" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-ingress-mesh-gateways-resolver" -win=true - - - name: Envoy Integration Tests for windows case-l7-intentions - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-l7-intentions" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-l7-intentions" -win=true - - - name: Envoy Integration Tests for windows case-multidc-rsa-ca - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-multidc-rsa-ca" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-multidc-rsa-ca" -win=true - - - name: Envoy Integration Tests for windows case-prometheus - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-prometheus" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-prometheus" -win=true - - - name: Envoy Integration Tests for windows case-property-override - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-property-override" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-property-override" -win=true - - - name: Envoy Integration Tests for windows case-stats-proxy - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-stats-proxy" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-stats-proxy" -win=true - - - name: Envoy Integration Tests for windows case-statsd-udp - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-statsd-udp" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-statsd-udp" -win=true - - - name: Envoy Integration Tests for windows case-terminating-gateway-hostnames - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-terminating-gateway-hostnames" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-terminating-gateway-hostnames" -win=true - - - name: Envoy Integration Tests for windows case-terminating-gateway-simple - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-terminating-gateway-simple" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-terminating-gateway-simple" -win=true - - - name: Envoy Integration Tests for windows case-terminating-gateway-without-services - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-terminating-gateway-without-services" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-terminating-gateway-without-services" -win=true - - - name: Envoy Integration Tests for windows case-upstream-config - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-upstream-config" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-upstream-config" -win=true - - - name: Envoy Integration Tests for windows case-wanfed-gw - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-wanfed-gw" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-wanfed-gw" -win=true - - - name: Envoy Integration Tests for windows case-ingress-gateway-sds - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-ingress-gateway-sds" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-ingress-gateway-sds" -win=true - - - name: Envoy Integration Tests for windows case-lua - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-lua" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-lua" -win=true - - - name: Envoy Integration Tests for windows case-terminating-gateway-subsets - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-terminating-gateway-subsets" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-terminating-gateway-subsets" -win=true - - # Skipping this because - https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/wasm_filter - # - name: Envoy Integration Tests for windows case-wasm - # shell: bash - # env: - # GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - # GOTESTSUM_FORMAT: standard-verbose - # COMPOSE_INTERACTIVE_NO_CLI: 1 - # LAMBDA_TESTS_ENABLED: "true" - # # tput complains if this isn't set to something. - # TERM: ansi - # run: | - # # shellcheck disable=SC2001 - # echo "Running Integration Test case-wasm" - # # shellcheck disable=SC2001 - # go test -v -timeout=45m -tags integration \ - # ./test/integration/connect/envoy -run="TestEnvoy/case-wasm" -win=true - - # Skipping because of - cacert is not available in curl windows - # https://www.phillipsj.net/posts/windows-curl-and-self-signed-certs/ - # - name: Envoy Integration Tests for windows case-ingress-gateway-tls - # shell: bash - # if: always() - # env: - # GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - # GOTESTSUM_FORMAT: standard-verbose - # COMPOSE_INTERACTIVE_NO_CLI: 1 - # LAMBDA_TESTS_ENABLED: "true" - # # tput complains if this isn't set to something. - # TERM: ansi - # run: | - # # shellcheck disable=SC2001 - # echo "Running Integration Test case-ingress-gateway-tls" - # # shellcheck disable=SC2001 - # go test -v -timeout=45m -tags integration \ - # ./test/integration/connect/envoy -run="TestEnvoy/case-ingress-gateway-tls" -win=true - - - name: Envoy Integration Tests for windows case-mesh-to-lambda - shell: bash - if: always() - env: - GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml - GOTESTSUM_FORMAT: standard-verbose - COMPOSE_INTERACTIVE_NO_CLI: 1 - LAMBDA_TESTS_ENABLED: "true" - # tput complains if this isn't set to something. - TERM: ansi - run: | - # shellcheck disable=SC2001 - echo "Running Integration Test case-mesh-to-lambda" - # shellcheck disable=SC2001 - go test -v -timeout=45m -tags integration \ - ./test/integration/connect/envoy -run="TestEnvoy/case-mesh-to-lambda" -win=true - - - # NOTE: ENT specific step as we store secrets in Vault. - - name: Authenticate to Vault - if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} - id: vault-auth - run: vault-auth - - # NOTE: ENT specific step as we store secrets in Vault. - - name: Fetch Secrets - if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} - id: secrets - uses: hashicorp/vault-action@v2.5.0 - with: - url: ${{ steps.vault-auth.outputs.addr }} - caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} - token: ${{ steps.vault-auth.outputs.token }} - secrets: | - kv/data/github/${{ github.repository }}/datadog apikey | DATADOG_API_KEY; - - - name: Prepare datadog-ci - shell: bash - if: ${{ !cancelled() && !endsWith(github.repository, '-enterprise') }} - run: | - curl -L --fail "https://github.com/DataDog/datadog-ci/releases/download/v2.17.2/datadog-ci_win-x64.exe" --output "C:/datadog-ci" - icacls C:/datadog-ci /grant:rx Everyone:RX - - - name: Upload coverage - # do not run on forks - if: ${{ !cancelled() && github.event.pull_request.head.repo.full_name == github.repository }} - env: - DATADOG_API_KEY: "${{ endsWith(github.repository, '-enterprise') && env.DATADOG_API_KEY || secrets.DATADOG_API_KEY }}" - DD_ENV: ci - run: C:/datadog-ci junit upload --service "$GITHUB_REPOSITORY" $TEST_RESULTS_DIR/results.xml - - test-integrations-success: - needs: - - envoy-integration-test - runs-on: 'ubuntu-latest' - if: ${{ always() }} - steps: - - name: evaluate upstream job results - run: | - # exit 1 if failure or cancelled result for any upstream job - if printf '${{ toJSON(needs) }}' | grep -E -i '\"result\": \"(failure|cancelled)\"'; then - printf "Tests failed or workflow cancelled:\n\n${{ toJSON(needs) }}" - exit 1 - fi diff --git a/.github/workflows/test-integrations.yml b/.github/workflows/test-integrations.yml index 019b3ece71..ba4eecdf42 100644 --- a/.github/workflows/test-integrations.yml +++ b/.github/workflows/test-integrations.yml @@ -13,6 +13,11 @@ on: - 'backport/docs/**' - 'backport/ui/**' - 'backport/mktg-**' + push: + branches: + # Push events on the main branch + - main + - release/** env: TEST_RESULTS_DIR: /tmp/test-results @@ -24,25 +29,14 @@ env: # strip the hashicorp/ off the front of github.repository for consul CONSUL_LATEST_IMAGE_NAME: ${{ endsWith(github.repository, '-enterprise') && github.repository || 'hashicorp/consul' }} GOPRIVATE: github.com/hashicorp # Required for enterprise deps - SKIP_CHECK_BRANCH: ${{ github.head_ref || github.ref_name }} concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.ref }} + group: "${{ github.workflow }}-${{ github.head_ref || github.ref }}" cancel-in-progress: true jobs: conditional-skip: - runs-on: ubuntu-latest - name: Get files changed and conditionally skip CI - outputs: - skip-ci: ${{ steps.read-files.outputs.skip-ci }} - steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - with: - fetch-depth: 0 - - name: Get changed files - id: read-files - run: ./.github/scripts/filter_changed_files_go_test.sh + uses: ./.github/workflows/reusable-conditional-skip.yml setup: needs: [conditional-skip] @@ -56,13 +50,16 @@ jobs: compute-xl: ${{ steps.runners.outputs.compute-xl }} enterprise: ${{ steps.runners.outputs.enterprise }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - id: runners run: .github/scripts/get_runner_classes.sh get-go-version: uses: ./.github/workflows/reusable-get-go-version.yml + get-envoy-versions: + uses: ./.github/workflows/reusable-get-envoy-versions.yml + dev-build: needs: - setup @@ -86,22 +83,22 @@ jobs: contents: read strategy: matrix: - nomad-version: ['v1.7.3', 'v1.6.6', 'v1.5.13'] + nomad-version: ['v1.8.3', 'v1.7.7', 'v1.6.10'] steps: - name: Checkout Nomad - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: repository: hashicorp/nomad ref: ${{ matrix.nomad-version }} - name: Install Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: # Do not explicitly set Go version here, as it should depend on what Nomad declares. go-version-file: 'go.mod' - name: Fetch Consul binary - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' path: ./bin @@ -111,7 +108,9 @@ jobs: echo "$(pwd)/bin" >> $GITHUB_PATH - name: Make Nomad dev build - run: make pkg/linux_amd64/nomad + run: | + make pkg/linux_amd64/nomad + echo "$(pwd)/pkg/linux_amd64" >> $GITHUB_PATH - name: Run integration tests run: | @@ -134,7 +133,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -167,18 +166,18 @@ jobs: contents: read strategy: matrix: - vault-version: ["1.15.4", "1.14.8", "1.13.12"] + vault-version: ["1.17.5", "1.16.3", "1.15.6"] env: VAULT_BINARY_VERSION: ${{ matrix.vault-version }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: # We use the current Consul Go version here since Vault is installed as a binary # and tests are run from the Consul repo. @@ -218,7 +217,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -263,32 +262,29 @@ jobs: outputs: envoy-matrix: ${{ steps.set-matrix.outputs.envoy-matrix }} steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - name: Generate Envoy Job Matrix id: set-matrix env: - # this is further going to multiplied in envoy-integration tests by the - # other dimensions in the matrix. Currently TOTAL_RUNNERS would be - # multiplied by 2 based on these values: - # envoy-version: ["1.28.2"] - # xds-target: ["server", "client"] - TOTAL_RUNNERS: 2 + # TEST_SPLITS sets the number of test case splits to use in the matrix. This will be + # further multiplied in envoy-integration tests by the other dimensions in the matrix + # to determine the total number of runners used. + TEST_SPLITS: 4 JQ_SLICER: '[ inputs ] | [_nwise(length / $runnercount | floor)]' run: | - NUM_RUNNERS=$TOTAL_RUNNERS NUM_DIRS=$(find ./test/integration/connect/envoy -mindepth 1 -maxdepth 1 -type d | wc -l) - if [ "$NUM_DIRS" -lt "$NUM_RUNNERS" ]; then - echo "TOTAL_RUNNERS is larger than the number of tests/packages to split." - NUM_RUNNERS=$((NUM_DIRS-1)) + if [ "$NUM_DIRS" -lt "$TEST_SPLITS" ]; then + echo "TEST_SPLITS is larger than the number of tests/packages to split." + TEST_SPLITS=$((NUM_DIRS-1)) fi - # fix issue where test splitting calculation generates 1 more split than TOTAL_RUNNERS. - NUM_RUNNERS=$((NUM_RUNNERS-1)) + # fix issue where test splitting calculation generates 1 more split than TEST_SPLITS. + TEST_SPLITS=$((TEST_SPLITS-1)) { echo -n "envoy-matrix=" find ./test/integration/connect/envoy -maxdepth 1 -type d -print0 \ | xargs -0 -n 1 basename \ - | jq --raw-input --argjson runnercount "$NUM_RUNNERS" "$JQ_SLICER" \ + | jq --raw-input --argjson runnercount "$TEST_SPLITS" "$JQ_SLICER" \ | jq --compact-output 'map(join("|"))' } >> "$GITHUB_OUTPUT" @@ -297,6 +293,7 @@ jobs: needs: - setup - get-go-version + - get-envoy-versions - generate-envoy-job-matrices - dev-build permissions: @@ -305,21 +302,20 @@ jobs: strategy: fail-fast: false matrix: - envoy-version: ["1.28.2"] xds-target: ["server", "client"] test-cases: ${{ fromJSON(needs.generate-envoy-job-matrices.outputs.envoy-matrix) }} env: - ENVOY_VERSION: ${{ matrix.envoy-version }} + ENVOY_VERSION: ${{ needs.get-envoy-versions.outputs.max-envoy-version }} XDS_TARGET: ${{ matrix.xds-target }} AWS_LAMBDA_REGION: us-west-2 steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - name: fetch binary - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' path: ./bin @@ -327,12 +323,13 @@ jobs: run: chmod +x ./bin/consul - name: Set up Docker Buildx - uses: docker/setup-buildx-action@2a1a44ac4aa01993040736bd95bb470da1a38365 # v2.9.0 + uses: docker/setup-buildx-action@d70bba72b1f3fd22344832f00baa16ece964efeb # v3.3.0 - name: Docker build run: docker build -t consul:local -f ./build-support/docker/Consul-Dev.dockerfile ./bin - name: Envoy Integration Tests + id: envoy-integration-tests env: GOTESTSUM_JUNITFILE: ${{ env.TEST_RESULTS_DIR }}/results.xml GOTESTSUM_FORMAT: standard-verbose @@ -353,6 +350,23 @@ jobs: --packages=./test/integration/connect/envoy \ -- -timeout=30m -tags integration -run="TestEnvoy/(${{ matrix.test-cases }})" + # See https://github.com/orgs/community/discussions/8945#discussioncomment-9897011 + # and overall topic discussion for why this is necessary. + - name: Generate artifact ID + id: generate-artifact-id + if: ${{ failure() && steps.envoy-integration-tests.conclusion == 'failure' }} + run: | + ARTIFACT_ID=$(uuidgen) + echo "Artifact ID: $ARTIFACT_ID (search this in job summary for download link)" + echo "artifact_id=$ARTIFACT_ID" >> "$GITHUB_ENV" + + - name: Upload failure logs + if: ${{ failure() && steps.envoy-integration-tests.conclusion == 'failure' }} + uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 + with: + name: envoy-${{ matrix.envoy-version }}-logs-${{ env.artifact_id }} + path: test/integration/connect/envoy/workdir/logs/ + # NOTE: ENT specific step as we store secrets in Vault. - name: Authenticate to Vault if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} @@ -363,7 +377,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -390,20 +404,22 @@ jobs: needs: - setup - get-go-version + - get-envoy-versions - dev-build permissions: id-token: write # NOTE: this permission is explicitly required for Vault auth. contents: read env: - ENVOY_VERSION: "1.28.2" - CONSUL_DATAPLANE_IMAGE: "docker.io/hashicorppreview/consul-dataplane:1.3-dev-ubi" + ENVOY_VERSION: ${{ needs.get-envoy-versions.outputs.max-envoy-version }} + #TODO don't harcode this image name + CONSUL_DATAPLANE_IMAGE: "docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.6-dev-ubi" steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: go env @@ -412,7 +428,7 @@ jobs: docker version docker info - name: fetch binary - uses: actions/download-artifact@9bc31d5ccc31df68ecc42ccf4149144866c47d8a # v3.0.2 + uses: actions/download-artifact@65a9edc5881444af0b9093a5e628f2fe47ea3b2e # v4.1.7 with: name: '${{ env.CONSUL_BINARY_UPLOAD_NAME }}' path: . @@ -476,7 +492,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} @@ -509,15 +525,16 @@ jobs: strategy: fail-fast: false env: - DEPLOYER_CONSUL_DATAPLANE_IMAGE: "docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.3-dev" + # TODO @sarah.alsmiller Don't hardcode this version value + DEPLOYER_CONSUL_DATAPLANE_IMAGE: "docker.mirror.hashicorp.services/hashicorppreview/consul-dataplane:1.6-dev" steps: - name: Checkout code - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 # NOTE: This step is specifically needed for ENT. It allows us to access the required private HashiCorp repos. - name: Setup Git if: ${{ endsWith(github.repository, '-enterprise') }} run: git config --global url."https://${{ secrets.ELEVATED_GITHUB_TOKEN }}:@github.com".insteadOf "https://github.com" - - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ needs.get-go-version.outputs.go-version }} - run: go env @@ -560,7 +577,7 @@ jobs: - name: Fetch Secrets if: ${{ !cancelled() && endsWith(github.repository, '-enterprise') }} id: secrets - uses: hashicorp/vault-action@v2.5.0 + uses: hashicorp/vault-action@v3 with: url: ${{ steps.vault-auth.outputs.addr }} caCertificate: ${{ steps.vault-auth.outputs.ca_certificate }} diff --git a/.github/workflows/verify-envoy-version.yml b/.github/workflows/verify-envoy-version.yml index dafa9db6f2..d27ad195d8 100644 --- a/.github/workflows/verify-envoy-version.yml +++ b/.github/workflows/verify-envoy-version.yml @@ -21,7 +21,7 @@ jobs: verify-envoy-version: runs-on: ubuntu-latest steps: - - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 with: ref: ${{ github.event.pull_request.head.sha }} fetch-depth: 0 # by default the checkout action doesn't checkout all branches diff --git a/.gitignore b/.gitignore index 9649c4a8cb..fcd852606c 100644 --- a/.gitignore +++ b/.gitignore @@ -71,3 +71,6 @@ terraform.rc /go.work /go.work.sum .docker + +# Avoid accidental commits of consul-k8s submodule used by some dev environments +consul-k8s/ diff --git a/.go-version b/.go-version index f124bfa155..5d287e490a 100644 --- a/.go-version +++ b/.go-version @@ -1 +1 @@ -1.21.9 +1.22.7 \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index 3a6f56ec99..e530ce70bd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -75,7 +75,9 @@ linters-settings: simplify: true forbidigo: # Forbid the following identifiers (list of regexp). + # Format includes custom message based on https://github.com/ashanbrown/forbidigo/pull/11 forbid: + - '\bhtml\/template\b(# Use text/template instead)?' - '\bioutil\b(# Use io and os packages instead of ioutil)?' - '\brequire\.New\b(# Use package-level functions with explicit TestingT)?' - '\bassert\.New\b(# Use package-level functions with explicit TestingT)?' diff --git a/.release/security-scan.hcl b/.release/security-scan.hcl index 83c503563b..3edf2cac82 100644 --- a/.release/security-scan.hcl +++ b/.release/security-scan.hcl @@ -38,9 +38,14 @@ container { suppress { # N.b. `vulnerabilites` is the correct spelling for this tool. vulnerabilites = [ - "CVE-2023-46218", # curl@8.4.0-r0 - "CVE-2023-46219", # curl@8.4.0-r0 - "CVE-2023-5678", # openssl@3.1.4-r0 + "CVE-2024-8096", # curl@8.9.1-r2, + ] + paths = [ + "internal/tools/proto-gen-rpc-glue/e2e/consul/*", + "test/integration/connect/envoy/test-sds-server/*", + "test/integration/consul-container/*", + "testing/deployer/*", + "test-integ/*", ] } } @@ -49,6 +54,7 @@ container { binary { go_modules = true osv = true + go_stdlib = true # We can't enable npm for binary targets today because we don't yet embed the relevant file # (yarn.lock) in the Consul binary. This is something we may investigate in the future. @@ -74,7 +80,13 @@ binary { suppress { # N.b. `vulnerabilites` is the correct spelling for this tool. vulnerabilites = [ - "GO-2024-2631", # go-jose/v3@v3.0.3 (false positive) + ] + paths = [ + "internal/tools/proto-gen-rpc-glue/e2e/consul/*", + "test/integration/connect/envoy/test-sds-server/*", + "test/integration/consul-container/*", + "testing/deployer/*", + "test-integ/*", ] } } diff --git a/.release/versions.hcl b/.release/versions.hcl new file mode 100644 index 0000000000..253430f3cc --- /dev/null +++ b/.release/versions.hcl @@ -0,0 +1,19 @@ +# Copyright (c) HashiCorp, Inc. +# SPDX-License-Identifier: BUSL-1.1 + +# This manifest file describes active releases and is consumed by the backport tooling. +# It is only consumed from the default branch, so backporting changes to this file is not necessary. + +schema = 1 +active_versions { + version "1.20" { + ce_active = true + } + version "1.19" {} + version "1.18" { + lts = true + } + version "1.15" { + lts = true + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index de182d13eb..675b8d5bf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,367 @@ +## 1.20.0 (October 14, 2024) + +SECURITY: + +* Explicitly set 'Content-Type' header to mitigate XSS vulnerability. [[GH-21704](https://github.com/hashicorp/consul/issues/21704)] +* Implement HTML sanitization for user-generated content to prevent XSS attacks in the UI. [[GH-21711](https://github.com/hashicorp/consul/issues/21711)] +* UI: Remove codemirror linting due to package dependency [[GH-21726](https://github.com/hashicorp/consul/issues/21726)] +* Upgrade Go to use 1.22.7. This addresses CVE +[CVE-2024-34155](https://nvd.nist.gov/vuln/detail/CVE-2024-34155) [[GH-21705](https://github.com/hashicorp/consul/issues/21705)] +* Upgrade to support aws/aws-sdk-go `v1.55.5 or higher`. This resolves CVEs +[CVE-2020-8911](https://nvd.nist.gov/vuln/detail/cve-2020-8911) and +[CVE-2020-8912](https://nvd.nist.gov/vuln/detail/cve-2020-8912). [[GH-21684](https://github.com/hashicorp/consul/issues/21684)] +* ui: Pin a newer resolution of Braces [[GH-21710](https://github.com/hashicorp/consul/issues/21710)] +* ui: Pin a newer resolution of Codemirror [[GH-21715](https://github.com/hashicorp/consul/issues/21715)] +* ui: Pin a newer resolution of Markdown-it [[GH-21717](https://github.com/hashicorp/consul/issues/21717)] +* ui: Pin a newer resolution of ansi-html [[GH-21735](https://github.com/hashicorp/consul/issues/21735)] + +FEATURES: + +* grafana: added the dashboards service-to-service dashboard, service dashboard, and consul dataplane dashboard [[GH-21806](https://github.com/hashicorp/consul/issues/21806)] +* server: remove v2 tenancy, catalog, and mesh experiments [[GH-21592](https://github.com/hashicorp/consul/issues/21592)] + +IMPROVEMENTS: + +* security: upgrade ubi base image to 9.4 [[GH-21750](https://github.com/hashicorp/consul/issues/21750)] +* connect: Add Envoy 1.31 and 1.30 to support matrix [[GH-21616](https://github.com/hashicorp/consul/issues/21616)] + +BUG FIXES: + +* jwt-provider: change dns lookup family from the default of AUTO which would prefer ipv6 to ALL if LOGICAL_DNS is used or PREFER_IPV4 if STRICT_DNS is used to gracefully handle transitions to ipv6. [[GH-21703](https://github.com/hashicorp/consul/issues/21703)] + +## 1.20.0-rc1 (September 19, 2024) + +SECURITY: + +* Explicitly set 'Content-Type' header to mitigate XSS vulnerability. [[GH-21704](https://github.com/hashicorp/consul/issues/21704)] +* Implement HTML sanitization for user-generated content to prevent XSS attacks in the UI. [[GH-21711](https://github.com/hashicorp/consul/issues/21711)] +* UI: Remove codemirror linting due to package dependency [[GH-21726](https://github.com/hashicorp/consul/issues/21726)] +* Upgrade Go to use 1.22.7. This addresses CVE + [CVE-2024-34155](https://nvd.nist.gov/vuln/detail/CVE-2024-34155) [[GH-21705](https://github.com/hashicorp/consul/issues/21705)] +* Upgrade to support aws/aws-sdk-go `v1.55.5 or higher`. This resolves CVEs + [CVE-2020-8911](https://nvd.nist.gov/vuln/detail/cve-2020-8911) and + [CVE-2020-8912](https://nvd.nist.gov/vuln/detail/cve-2020-8912). [[GH-21684](https://github.com/hashicorp/consul/issues/21684)] +* ui: Pin a newer resolution of Braces [[GH-21710](https://github.com/hashicorp/consul/issues/21710)] +* ui: Pin a newer resolution of Codemirror [[GH-21715](https://github.com/hashicorp/consul/issues/21715)] +* ui: Pin a newer resolution of Markdown-it [[GH-21717](https://github.com/hashicorp/consul/issues/21717)] +* ui: Pin a newer resolution of ansi-html [[GH-21735](https://github.com/hashicorp/consul/issues/21735)] + +FEATURES: + +* server: remove v2 tenancy, catalog, and mesh experiments [[GH-21592](https://github.com/hashicorp/consul/issues/21592)] + +IMPROVEMENTS: + +* security: upgrade ubi base image to 9.4 [[GH-21750](https://github.com/hashicorp/consul/issues/21750)] +* connect: Add Envoy 1.31 and 1.30 to support matrix [[GH-21616](https://github.com/hashicorp/consul/issues/21616)] + +BUG FIXES: + +* jwt-provider: change dns lookup family from the default of AUTO which would prefer ipv6 to ALL if LOGICAL_DNS is used or PREFER_IPV4 if STRICT_DNS is used to gracefully handle transitions to ipv6. [[GH-21703](https://github.com/hashicorp/consul/issues/21703)] + +## 1.19.2 (August 26, 2024) + +SECURITY: + +* ui: Upgrade modules with d3-color as a dependency to address denial of service issue in d3-color < 3.1.0 [[GH-21588](https://github.com/hashicorp/consul/issues/21588)] + +IMPROVEMENTS: + +* Use Envoy's default for a route's validate_clusters option, which is false. This fixes a case where non-existent clusters could cause a route to no longer route to any of its backends, including existing ones. [[GH-21587](https://github.com/hashicorp/consul/issues/21587)] + +BUG FIXES: + +* api-gateway: **(Enterprise only)** ensure clusters are properly created for JWT providers with a remote URI for the JWKS endpoint [[GH-21604](https://github.com/hashicorp/consul/issues/21604)] + +## 1.18.4 Enterprise (August 26, 2024) + +Enterprise LTS: Consul Enterprise 1.18 is a Long-Term Support (LTS) release. + +SECURITY: +* ui: Upgrade modules with d3-color as a dependency to address denial of service issue in d3-color < 3.1.0 + +IMPROVEMENTS: + +* Use Envoy's default for a route's validate_clusters option, which is false. This fixes a case where non-existent clusters could cause a route to no longer route to any of its backends, including existing ones. [[GH-21587](https://github.com/hashicorp/consul/issues/21587)] + +## 1.17.7 Enterprise (August 26, 2024) + +SECURITY: +* ui: Upgrade modules with d3-color as a dependency to address denial of service issue in d3-color < 3.1.0 + +IMPROVEMENTS: + +* Use Envoy's default for a route's validate_clusters option, which is false. This fixes a case where non-existent clusters could cause a route to no longer route to any of its backends, including existing ones. [[GH-21587](https://github.com/hashicorp/consul/issues/21587)] + +## 1.15.14 Enterprise (August 26, 2024) + +Enterprise LTS: Consul Enterprise 1.15 is a Long-Term Support (LTS) release. + +SECURITY: + +* ui: Upgrade modules with d3-color as a dependency to address denial of service issue in d3-color < 3.1.0 [[GH-21588](https://github.com/hashicorp/consul/issues/21588)] + +IMPROVEMENTS: + +* Use Envoy's default for a route's validate_clusters option, which is false. This fixes a case where non-existent clusters could cause a route to no longer route to any of its backends, including existing ones. [[GH-21587](https://github.com/hashicorp/consul/issues/21587)] + +## 1.19.1 (July 11, 2024) + +SECURITY: + +* Upgrade envoy module dependencies to version 1.27.7, 1.28.5 and 1.29.7 or higher to resolve [CVE-2024-39305](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-39305) [[GH-21524](https://github.com/hashicorp/consul/issues/21524)] +* Upgrade go version to 1.22.5 to address [CVE-2024-24791](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-24791) [[GH-21507](https://github.com/hashicorp/consul/issues/21507)] +* Upgrade go-retryablehttp to address [CVE-2024-6104](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-6104) [[GH-21384](https://github.com/hashicorp/consul/issues/21384)] +* agent: removed reflected cross-site scripting vulnerability [[GH-21342](https://github.com/hashicorp/consul/issues/21342)] +* ui: Pin and namespace sub-module dependencies related to the Consul UI [[GH-21378](https://github.com/hashicorp/consul/issues/21378)] + +IMPROVEMENTS: + +* mesh: update supported envoy version 1.29.5 in addition to 1.28.4, 1.27.6. [[GH-21277](https://github.com/hashicorp/consul/issues/21277)] + +BUG FIXES: + +* core: Fix multiple incorrect type conversion for potential overflows [[GH-21251](https://github.com/hashicorp/consul/issues/21251)] +* core: Fix panic runtime error on AliasCheck [[GH-21339](https://github.com/hashicorp/consul/issues/21339)] +* dns: Fix a regression where DNS SRV questions were returning duplicate hostnames instead of encoded IPs. + This affected Nomad integrations with Consul. [[GH-21361](https://github.com/hashicorp/consul/issues/21361)] +* dns: Fix a regression where DNS tags using the standard lookup syntax, `tag.name.service.consul`, were being disregarded. [[GH-21361](https://github.com/hashicorp/consul/issues/21361)] +* dns: Fixes a spam log message "Failed to parse TTL for prepared query..." + that was always being logged on each prepared query evaluation. [[GH-21381](https://github.com/hashicorp/consul/issues/21381)] +* terminating-gateway: **(Enterprise Only)** Fixed issue where enterprise metadata applied to linked services was the terminating-gateways enterprise metadata and not the linked services enterprise metadata. [[GH-21382](https://github.com/hashicorp/consul/issues/21382)] +* txn: Fix a bug where mismatched Consul server versions could result in undetected data loss for when using newer Transaction verbs. [[GH-21519](https://github.com/hashicorp/consul/issues/21519)] + +## 1.18.3 Enterprise (July 11, 2024) + +**Enterprise LTS**: Consul Enterprise 1.18 is a Long-Term Support (LTS) release. + +SECURITY: + +* Upgrade envoy module dependencies to version 1.27.7, 1.28.5 and 1.29.7 or higher to resolve [CVE-2024-39305](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-39305) [[GH-21524](https://github.com/hashicorp/consul/issues/21524)] +* Upgrade go version to 1.22.5 to address [CVE-2024-24791](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-24791) [[GH-21507](https://github.com/hashicorp/consul/issues/21507)] +* Upgrade go-retryablehttp to address [CVE-2024-6104](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-6104) [[GH-21384](https://github.com/hashicorp/consul/issues/21384)] +* agent: removed reflected cross-site scripting vulnerability [[GH-21342](https://github.com/hashicorp/consul/issues/21342)] +* ui: Pin and namespace sub-module dependencies related to the Consul UI [[GH-21378](https://github.com/hashicorp/consul/issues/21378)] + +IMPROVEMENTS: + +* mesh: update supported envoy version 1.29.4 +* mesh: update supported envoy version 1.29.5 in addition to 1.28.4, 1.27.6. [[GH-21277](https://github.com/hashicorp/consul/issues/21277)] +* upgrade go version to v1.22.3. [[GH-21113](https://github.com/hashicorp/consul/issues/21113)] +* upgrade go version to v1.22.4. [[GH-21265](https://github.com/hashicorp/consul/issues/21265)] + +BUG FIXES: + +* core: Fix multiple incorrect type conversion for potential overflows [[GH-21251](https://github.com/hashicorp/consul/issues/21251)] +* core: Fix panic runtime error on AliasCheck [[GH-21339](https://github.com/hashicorp/consul/issues/21339)] +* dns: Fixes a spam log message "Failed to parse TTL for prepared query..." + that was always being logged on each prepared query evaluation. [[GH-21381](https://github.com/hashicorp/consul/issues/21381)] +* terminating-gateway: **(Enterprise Only)** Fixed issue where enterprise metadata applied to linked services was the terminating-gateways enterprise metadata and not the linked services enterprise metadata. [[GH-21382](https://github.com/hashicorp/consul/issues/21382)] +* txn: Fix a bug where mismatched Consul server versions could result in undetected data loss for when using newer Transaction verbs. [[GH-21519](https://github.com/hashicorp/consul/issues/21519)] +* v2dns: Fix a regression where DNS SRV questions were returning duplicate hostnames instead of encoded IPs. + This affected Nomad integrations with Consul. [[GH-21361](https://github.com/hashicorp/consul/issues/21361)] +* v2dns: Fix a regression where DNS tags using the standard lookup syntax, `tag.name.service.consul`, were being disregarded. [[GH-21361](https://github.com/hashicorp/consul/issues/21361)] + +## 1.17.6 Enterprise (July 11, 2024) + +SECURITY: + +* Upgrade envoy module dependencies to version 1.27.7, 1.28.5 and 1.29.7 or higher to resolve [CVE-2024-39305](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-39305) [[GH-21524](https://github.com/hashicorp/consul/issues/21524)] +* Upgrade go version to 1.22.5 to address [CVE-2024-24791](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-24791) [[GH-21507](https://github.com/hashicorp/consul/issues/21507)] +* Upgrade go-retryablehttp to address [CVE-2024-6104](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-6104) [[GH-21384](https://github.com/hashicorp/consul/issues/21384)] +* agent: removed reflected cross-site scripting vulnerability [[GH-21342](https://github.com/hashicorp/consul/issues/21342)] +* ui: Pin and namespace sub-module dependencies related to the Consul UI [[GH-21378](https://github.com/hashicorp/consul/issues/21378)] + +IMPROVEMENTS: + +* upgrade go version to v1.22.3. [[GH-21113](https://github.com/hashicorp/consul/issues/21113)] +* upgrade go version to v1.22.4. [[GH-21265](https://github.com/hashicorp/consul/issues/21265)] + +BUG FIXES: + +* core: Fix panic runtime error on AliasCheck [[GH-21339](https://github.com/hashicorp/consul/issues/21339)] +* terminating-gateway: **(Enterprise Only)** Fixed issue where enterprise metadata applied to linked services was the terminating-gateways enterprise metadata and not the linked services enterprise metadata. [[GH-21382](https://github.com/hashicorp/consul/issues/21382)] +* txn: Fix a bug where mismatched Consul server versions could result in undetected data loss for when using newer Transaction verbs. [[GH-21519](https://github.com/hashicorp/consul/issues/21519)] + +## 1.15.13 Enterprise (July 11, 2024) + +**Enterprise LTS**: Consul Enterprise 1.15 is a Long-Term Support (LTS) release. + +SECURITY: + +* Upgrade envoy module dependencies to version 1.27.7, 1.28.5 and 1.29.7 or higher to resolve [CVE-2024-39305](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-39305) [[GH-21524](https://github.com/hashicorp/consul/issues/21524)] +* Upgrade go version to 1.22.5 to address [CVE-2024-24791](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-24791) [[GH-21507](https://github.com/hashicorp/consul/issues/21507)] +* Upgrade go-retryablehttp to address [CVE-2024-6104](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2024-6104) [[GH-21384](https://github.com/hashicorp/consul/issues/21384)] +* agent: removed reflected cross-site scripting vulnerability [[GH-21342](https://github.com/hashicorp/consul/issues/21342)] +* ui: Pin and namespace sub-module dependencies related to the Consul UI [[GH-21378](https://github.com/hashicorp/consul/issues/21378)] + +IMPROVEMENTS: + +* mesh: update supported envoy version 1.29.4 +* upgrade go version to v1.22.3. [[GH-21113](https://github.com/hashicorp/consul/issues/21113)] +* upgrade go version to v1.22.4. [[GH-21265](https://github.com/hashicorp/consul/issues/21265)] + +BUG FIXES: + +* core: Fix panic runtime error on AliasCheck [[GH-21339](https://github.com/hashicorp/consul/issues/21339)] +* terminating-gateway: **(Enterprise Only)** Fixed issue where enterprise metadata applied to linked services was the terminating-gateways enterprise metadata and not the linked services enterprise metadata. [[GH-21382](https://github.com/hashicorp/consul/issues/21382)] +* txn: Fix a bug where mismatched Consul server versions could result in undetected data loss for when using newer Transaction verbs. [[GH-21519](https://github.com/hashicorp/consul/issues/21519)] + +## 1.19.0 (June 12, 2024) + +BREAKING CHANGES: + +* telemetry: State store usage metrics with a double `consul` element in the metric name have been removed. Please use the same metric without the second `consul` instead. As an example instead of `consul.consul.state.config_entries` use `consul.state.config_entries` [[GH-20674](https://github.com/hashicorp/consul/issues/20674)] + +SECURITY: + +* Upgrade to support Envoy `1.27.5 and 1.28.3`. This resolves CVE +[CVE-2024-32475](https://nvd.nist.gov/vuln/detail/CVE-2024-32475) (`auto_sni`). [[GH-21017](https://github.com/hashicorp/consul/issues/21017)] +* Upgrade to support k8s.io/apimachinery `v0.18.7 or higher`. This resolves CVE +[CVE-2020-8559](https://nvd.nist.gov/vuln/detail/CVE-2020-8559). [[GH-21017](https://github.com/hashicorp/consul/issues/21017)] + +FEATURES: + +* dns: queries now default to a refactored DNS server that is v1 and v2 Catalog compatible. +Use `v1dns` in the `experiments` agent config to disable. +The legacy server will be removed in a future release of Consul. +See the [Consul 1.19.x Release Notes](https://developer.hashicorp.com/consul/docs/release-notes/consul/v1_19_x) for removed DNS features. [[GH-20715](https://github.com/hashicorp/consul/issues/20715)] +* gateways: api-gateway can leverage listener TLS certificates available on the gateway's local filesystem by specifying the public certificate and private key path in the new file-system-certificate configuration entry [[GH-20873](https://github.com/hashicorp/consul/issues/20873)] + +IMPROVEMENTS: + +* dns: new version was not supporting partition or namespace being set to 'default' in CE version. [[GH-21230](https://github.com/hashicorp/consul/issues/21230)] +* mesh: update supported envoy version 1.29.4 in addition to 1.28.3, 1.27.5, 1.26.8. [[GH-21142](https://github.com/hashicorp/consul/issues/21142)] +* upgrade go version to v1.22.4. [[GH-21265](https://github.com/hashicorp/consul/issues/21265)] +* Upgrade `github.com/envoyproxy/go-control-plane` to 0.12.0. [[GH-20973](https://github.com/hashicorp/consul/issues/20973)] +* dns: DNS-over-grpc when using `consul-dataplane` now accepts partition, namespace, token as metadata to default those query parameters. +`consul-dataplane` v1.5+ will send this information automatically. [[GH-20899](https://github.com/hashicorp/consul/issues/20899)] +* snapshot: Add `consul snapshot decode` CLI command to output a JSON object stream of all the snapshots data. [[GH-20824](https://github.com/hashicorp/consul/issues/20824)] +* telemetry: Add `telemetry.disable_per_tenancy_usage_metrics` in agent configuration to disable setting tenancy labels on usage metrics. This significantly decreases CPU utilization in clusters with many admin partitions or namespaces. +* telemetry: Improved the performance usage metrics emission by not outputting redundant metrics. [[GH-20674](https://github.com/hashicorp/consul/issues/20674)] + +DEPRECATIONS: + +* snapshot agent: **(Enterprise only)** Top level single snapshot destinations `local_storage`, `aws_storage`, `azure_blob_storage`, and `google_storage` in snapshot agent configuration files are now deprecated. Use the `backup_destinations` config object instead. + +BUG FIXES: + +* docs: Consul DNS Forwarding configuration for OpenShift update for [Resolve Consul DNS Requests in Kubernetes](https://developer.hashicorp.com/consul/docs/k8s/dns) [[GH-20439](https://github.com/hashicorp/consul/issues/20439)] +* hcp: fix error logs when failing to push metrics [[GH-20514](https://github.com/hashicorp/consul/issues/20514)] +* streaming: Handle ACL errors consistently when blocking query timeout is reached. [[GH-20876](https://github.com/hashicorp/consul/issues/20876)] + +## 1.18.2 (May 14, 2024) + +**Enterprise LTS**: Consul Enterprise 1.18 is a Long-Term Support (LTS) release. + +SECURITY: + +* Bump Dockerfile base image to `alpine:3.19`. [[GH-20897](https://github.com/hashicorp/consul/issues/20897)] +* Update `vault/api` to v1.12.2 to address [CVE-2024-28180](https://nvd.nist.gov/vuln/detail/CVE-2024-28180) + (removes indirect dependency on impacted `go-jose.v2`) [[GH-20910](https://github.com/hashicorp/consul/issues/20910)] +* Upgrade Go to use 1.21.10. This addresses CVEs + [CVE-2024-24787](https://nvd.nist.gov/vuln/detail/CVE-2024-24787) and + [CVE-2024-24788](https://nvd.nist.gov/vuln/detail/CVE-2024-24788) [[GH-21074](https://github.com/hashicorp/consul/issues/21074)] +* Upgrade to support Envoy `1.26.8, 1.27.4, 1.27.5, 1.28.2 and 1.28.3`. This resolves CVEs + [CVE-2024-27919](https://nvd.nist.gov/vuln/detail/CVE-2024-27919) (`http2`). [[GH-20956](https://github.com/hashicorp/consul/issues/20956)] and [CVE-2024-32475](https://nvd.nist.gov/vuln/detail/CVE-2024-32475) (`auto_sni`). [[GH-21030](https://github.com/hashicorp/consul/issues/21030)] +* Upgrade to support k8s.io/apimachinery `v0.18.7 or higher`. This resolves CVE + [CVE-2020-8559](https://nvd.nist.gov/vuln/detail/CVE-2020-8559). [[GH-21034](https://github.com/hashicorp/consul/issues/21034)] +* Upgrade to use Go `1.21.9`. This resolves CVE + [CVE-2023-45288](https://nvd.nist.gov/vuln/detail/CVE-2023-45288) (`http2`). [[GH-20956](https://github.com/hashicorp/consul/issues/20956)] +* Upgrade to use golang.org/x/net `v0.24.0`. This resolves CVE + [CVE-2023-45288](https://nvd.nist.gov/vuln/detail/CVE-2023-45288) (`x/net`). [[GH-20956](https://github.com/hashicorp/consul/issues/20956)] + +IMPROVEMENTS: + +* gateways: service defaults configuration entries can now be used to set default upstream limits for mesh-gateways [[GH-20945](https://github.com/hashicorp/consul/issues/20945)] +* connect: Add ability to disable Auto Host Header Rewrite on Terminating Gateway at the service level [[GH-20802](https://github.com/hashicorp/consul/issues/20802)] + +BUG FIXES: + +* dns: fix a bug with sameness group queries in DNS where responses did not respect [`DefaultForFailover`](/consul/docs/connect/config-entries/sameness-group#defaultforfailover). + DNS requests against sameness groups without this field set will now error as intended. +* error running consul server in 1.18.0: failed to configure SCADA provider user's home directory path: $HOME is not defined [[GH-20926](https://github.com/hashicorp/consul/issues/20926)] +* server: fix Ent snapshot restore on CE when CE downgrade is enabled [[GH-20977](https://github.com/hashicorp/consul/issues/20977)] +* xds: Make TCP external service registered with terminating gateway reachable from peered cluster [[GH-19881](https://github.com/hashicorp/consul/issues/19881)] + +## 1.17.5 Enterprise (May 14, 2024) + +SECURITY: + +* Bump Dockerfile base image to `alpine:3.19`. [[GH-20897](https://github.com/hashicorp/consul/issues/20897)] +* Update `vault/api` to v1.12.2 to address [CVE-2024-28180](https://nvd.nist.gov/vuln/detail/CVE-2024-28180) + (removes indirect dependency on impacted `go-jose.v2`) [[GH-20910](https://github.com/hashicorp/consul/issues/20910)] +* Upgrade Go to use 1.21.10. This addresses CVEs + [CVE-2024-24787](https://nvd.nist.gov/vuln/detail/CVE-2024-24787) and + [CVE-2024-24788](https://nvd.nist.gov/vuln/detail/CVE-2024-24788) [[GH-21074](https://github.com/hashicorp/consul/issues/21074)] +* Upgrade to support Envoy `1.26.8, 1.27.4, 1.27.5, 1.28.2 and 1.28.3`. This resolves CVEs + [CVE-2024-27919](https://nvd.nist.gov/vuln/detail/CVE-2024-27919) (`http2`). [[GH-20956](https://github.com/hashicorp/consul/issues/20956)] and [CVE-2024-32475](https://nvd.nist.gov/vuln/detail/CVE-2024-32475) (`auto_sni`). [[GH-21030](https://github.com/hashicorp/consul/issues/21030)] +* Upgrade to support k8s.io/apimachinery `v0.18.7 or higher`. This resolves CVE + [CVE-2020-8559](https://nvd.nist.gov/vuln/detail/CVE-2020-8559). [[GH-21033](https://github.com/hashicorp/consul/issues/21033)] +* Upgrade to use Go `1.21.9`. This resolves CVE + [CVE-2023-45288](https://nvd.nist.gov/vuln/detail/CVE-2023-45288) (`http2`). [[GH-20956](https://github.com/hashicorp/consul/issues/20956)] +* Upgrade to use golang.org/x/net `v0.24.0`. This resolves CVE + [CVE-2023-45288](https://nvd.nist.gov/vuln/detail/CVE-2023-45288) (`x/net`). [[GH-20956](https://github.com/hashicorp/consul/issues/20956)] +* security: Remove `coredns/coredns` dependency to address [CVE-2024-0874](https://nvd.nist.gov/vuln/detail/CVE-2024-0874) [[GH-9243](https://github.com/hashicorp/consul/issues/9243)] + +BUG FIXES: + +* dns: fix a bug with sameness group queries in DNS where responses did not respect [`DefaultForFailover`](/consul/docs/connect/config-entries/sameness-group#defaultforfailover). + DNS requests against sameness groups without this field set will now error as intended. +* xds: Make TCP external service registered with terminating gateway reachable from peered cluster [[GH-19881](https://github.com/hashicorp/consul/issues/19881)] + +## 1.16.8 Enterprise (May 14, 2024) + +SECURITY: + +* Bump Dockerfile base image to `alpine:3.19`. [[GH-20897](https://github.com/hashicorp/consul/issues/20897)] +* Update `vault/api` to v1.12.2 to address [CVE-2024-28180](https://nvd.nist.gov/vuln/detail/CVE-2024-28180) + (removes indirect dependency on impacted `go-jose.v2`) [[GH-20910](https://github.com/hashicorp/consul/issues/20910)] +* Upgrade Go to use 1.21.10. This addresses CVEs + [CVE-2024-24787](https://nvd.nist.gov/vuln/detail/CVE-2024-24787) and + [CVE-2024-24788](https://nvd.nist.gov/vuln/detail/CVE-2024-24788) [[GH-21074](https://github.com/hashicorp/consul/issues/21074)] +* Upgrade to support Envoy `1.26.8, 1.27.4, 1.27.5, 1.28.2 and 1.28.3`. This resolves CVEs + [CVE-2024-27919](https://nvd.nist.gov/vuln/detail/CVE-2024-27919) (`http2`). [[GH-20956](https://github.com/hashicorp/consul/issues/20956)] and [CVE-2024-32475](https://nvd.nist.gov/vuln/detail/CVE-2024-32475) (`auto_sni`). [[GH-21030](https://github.com/hashicorp/consul/issues/21030)] +* Upgrade to support k8s.io/apimachinery `v0.18.7 or higher`. This resolves CVE + [CVE-2020-8559](https://nvd.nist.gov/vuln/detail/CVE-2020-8559). [[GH-21032](https://github.com/hashicorp/consul/issues/21032)] +* Upgrade to use Go `1.21.9`. This resolves CVE + [CVE-2023-45288](https://nvd.nist.gov/vuln/detail/CVE-2023-45288) (`http2`). [[GH-20956](https://github.com/hashicorp/consul/issues/20956)] +* Upgrade to use golang.org/x/net `v0.24.0`. This resolves CVE + [CVE-2023-45288](https://nvd.nist.gov/vuln/detail/CVE-2023-45288) (`x/net`). [[GH-20956](https://github.com/hashicorp/consul/issues/20956)] +* security: Remove `coredns/coredns` dependency to address [CVE-2024-0874](https://nvd.nist.gov/vuln/detail/CVE-2024-0874) [[GH-9244](https://github.com/hashicorp/consul/issues/9244)] + +BUG FIXES: + +* dns: fix a bug with sameness group queries in DNS where responses did not respect [`DefaultForFailover`](/consul/docs/connect/config-entries/sameness-group#defaultforfailover). + DNS requests against sameness groups without this field set will now error as intended. +* xds: Make TCP external service registered with terminating gateway reachable from peered cluster [[GH-19881](https://github.com/hashicorp/consul/issues/19881)] + +## 1.15.12 Enterprise (May 14, 2024) + +**Enterprise LTS**: Consul Enterprise 1.15 is a Long-Term Support (LTS) release. + +SECURITY: + +* Bump Dockerfile base image to `alpine:3.19`. [[GH-20897](https://github.com/hashicorp/consul/issues/20897)] +* Update `vault/api` to v1.12.2 to address [CVE-2024-28180](https://nvd.nist.gov/vuln/detail/CVE-2024-28180) + (removes indirect dependency on impacted `go-jose.v2`) [[GH-20910](https://github.com/hashicorp/consul/issues/20910)] +* Upgrade Go to use 1.21.10. This addresses CVEs + [CVE-2024-24787](https://nvd.nist.gov/vuln/detail/CVE-2024-24787) and + [CVE-2024-24788](https://nvd.nist.gov/vuln/detail/CVE-2024-24788) [[GH-21074](https://github.com/hashicorp/consul/issues/21074)] +* Upgrade to support Envoy `1.26.8, 1.27.4, 1.27.5, 1.28.2 and 1.28.3`. This resolves CVEs + [CVE-2024-27919](https://nvd.nist.gov/vuln/detail/CVE-2024-27919) (`http2`). [[GH-20956](https://github.com/hashicorp/consul/issues/20956)] and [CVE-2024-32475](https://nvd.nist.gov/vuln/detail/CVE-2024-32475) (`auto_sni`). [[GH-21030](https://github.com/hashicorp/consul/issues/21030)] +* Upgrade to support k8s.io/apimachinery `v0.18.7 or higher`. This resolves CVE + [CVE-2020-8559](https://nvd.nist.gov/vuln/detail/CVE-2020-8559). [[GH-21030](https://github.com/hashicorp/consul/issues/21030)] +* Upgrade to use Go `1.21.9`. This resolves CVE + [CVE-2023-45288](https://nvd.nist.gov/vuln/detail/CVE-2023-45288) (`http2`). [[GH-20956](https://github.com/hashicorp/consul/issues/20956)] +* Upgrade to use golang.org/x/net `v0.24.0`. This resolves CVE + [CVE-2023-45288](https://nvd.nist.gov/vuln/detail/CVE-2023-45288) (`x/net`). [[GH-20956](https://github.com/hashicorp/consul/issues/20956)] +* security: Remove `coredns/coredns` dependency to address [CVE-2024-0874](https://nvd.nist.gov/vuln/detail/CVE-2024-0874) [[GH-9245](https://github.com/hashicorp/consul/issues/9245)] + +BUG FIXES: + +* xds: Make TCP external service registered with terminating gateway reachable from peered cluster [[GH-19881](https://github.com/hashicorp/consul/issues/19881)] + ## 1.18.1 (March 26, 2024) Enterprise LTS: Consul Enterprise 1.18 is a Long-Term Support (LTS) release. diff --git a/Dockerfile b/Dockerfile index e68a2190b2..dc617c5e04 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ # Official docker image that includes binaries from releases.hashicorp.com. This # downloads the release from releases.hashicorp.com and therefore requires that # the release is published before building the Docker image. -FROM docker.mirror.hashicorp.services/alpine:3.19 as official +FROM docker.mirror.hashicorp.services/alpine:3.20 as official # This is the release of Consul to pull in. ARG VERSION @@ -112,7 +112,7 @@ CMD ["agent", "-dev", "-client", "0.0.0.0"] # Production docker image that uses CI built binaries. # Remember, this image cannot be built locally. -FROM docker.mirror.hashicorp.services/alpine:3.19 as default +FROM docker.mirror.hashicorp.services/alpine:3.20 as default ARG PRODUCT_VERSION ARG BIN_NAME @@ -123,7 +123,7 @@ ENV BIN_NAME=$BIN_NAME ENV PRODUCT_VERSION=$PRODUCT_VERSION ARG PRODUCT_REVISION -ARG PRODUCT_NAME=$BIN_NAME +ENV PRODUCT_NAME=$BIN_NAME # TARGETOS and TARGETARCH are set automatically when --platform is provided. ARG TARGETOS TARGETARCH @@ -136,8 +136,10 @@ LABEL org.opencontainers.image.authors="Consul Team " \ org.opencontainers.image.vendor="HashiCorp" \ org.opencontainers.image.title="consul" \ org.opencontainers.image.description="Consul is a datacenter runtime that provides service discovery, configuration, and orchestration." \ + org.opencontainers.image.licenses="BSL-1.1" \ version=${PRODUCT_VERSION} +COPY LICENSE /usr/share/doc/$PRODUCT_NAME/LICENSE.txt # Set up certificates and base tools. # libc6-compat is needed to symlink the shared libraries for ARM builds RUN apk add -v --no-cache \ @@ -201,9 +203,8 @@ CMD ["agent", "-dev", "-client", "0.0.0.0"] # Red Hat UBI-based image # This target is used to build a Consul image for use on OpenShift. -FROM registry.access.redhat.com/ubi9-minimal:9.3 as ubi +FROM registry.access.redhat.com/ubi9-minimal:9.4 as ubi -ARG PRODUCT_NAME ARG PRODUCT_VERSION ARG PRODUCT_REVISION ARG BIN_NAME @@ -212,8 +213,7 @@ ARG BIN_NAME # and the version to download. Example: PRODUCT_NAME=consul PRODUCT_VERSION=1.2.3. ENV BIN_NAME=$BIN_NAME ENV PRODUCT_VERSION=$PRODUCT_VERSION - -ARG PRODUCT_NAME=$BIN_NAME +ENV PRODUCT_NAME=$BIN_NAME # TARGETOS and TARGETARCH are set automatically when --platform is provided. ARG TARGETOS TARGETARCH @@ -226,8 +226,10 @@ LABEL org.opencontainers.image.authors="Consul Team " \ org.opencontainers.image.vendor="HashiCorp" \ org.opencontainers.image.title="consul" \ org.opencontainers.image.description="Consul is a datacenter runtime that provides service discovery, configuration, and orchestration." \ + org.opencontainers.image.licenses="BSL-1.1" \ version=${PRODUCT_VERSION} +COPY LICENSE /usr/share/doc/$PRODUCT_NAME/LICENSE.txt # Copy license for Red Hat certification. COPY LICENSE /licenses/mozilla.txt diff --git a/Makefile b/Makefile index 84900d4c5e..b8b72d5de1 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,7 @@ PROTOC_GEN_GO_GRPC_VERSION='v1.2.0' MOG_VERSION='v0.4.2' PROTOC_GO_INJECT_TAG_VERSION='v1.3.0' PROTOC_GEN_GO_BINARY_VERSION='v0.1.0' -DEEP_COPY_VERSION='bc3f5aa5735d8a54961580a3a24422c308c831c2' +DEEP_COPY_VERSION='e112476c0181d3d69067bac191f9b6bcda2ce812' COPYWRITE_TOOL_VERSION='v0.16.4' LINT_CONSUL_RETRY_VERSION='v1.4.0' # Go imports formatter @@ -71,9 +71,10 @@ CONSUL_IMAGE_VERSION?=latest # When changing the method of Go version detection, also update # version detection in CI workflows (reusable-get-go-version.yml). GOLANG_VERSION?=$(shell head -n 1 .go-version) -ENVOY_VERSION?='1.28.0' -CONSUL_DATAPLANE_IMAGE := $(or $(CONSUL_DATAPLANE_IMAGE),"docker.io/hashicorppreview/consul-dataplane:1.3-dev-ubi") -DEPLOYER_CONSUL_DATAPLANE_IMAGE := $(or $(DEPLOYER_CONSUL_DATAPLANE_IMAGE), "docker.io/hashicorppreview/consul-dataplane:1.3-dev") +# Takes the highest version from the ENVOY_VERSIONS file. +ENVOY_VERSION?=$(shell cat envoyextensions/xdscommon/ENVOY_VERSIONS | grep '^[[:digit:]]' | sort -nr | head -n 1) +CONSUL_DATAPLANE_IMAGE := $(or $(CONSUL_DATAPLANE_IMAGE),"docker.io/hashicorppreview/consul-dataplane:1.6-dev-ubi") +DEPLOYER_CONSUL_DATAPLANE_IMAGE := $(or $(DEPLOYER_CONSUL_DATAPLANE_IMAGE), "docker.io/hashicorppreview/consul-dataplane:1.6-dev") CONSUL_VERSION?=$(shell cat version/VERSION) @@ -293,7 +294,6 @@ lint-container-test-deps: ## Check that the test-container module only imports a @cd test/integration/consul-container && \ $(CURDIR)/build-support/scripts/check-allowed-imports.sh \ github.com/hashicorp/consul \ - "internal/catalog/catalogtest" \ "internal/resource/resourcetest" ##@ Testing @@ -619,6 +619,14 @@ envoy-regen: ## Regenerating envoy golden files @find "command/connect/envoy/testdata" -name '*.golden' -delete @go test -tags '$(GOTAGS)' ./command/connect/envoy -update + +##@ Changelog + +.PHONY: gen-changelog +gen-changelog: ## Generate changelog entry for the current branch based on the currently open PR for that branch + @$(SHELL) $(CURDIR)/build-support/scripts/gen-changelog.sh + + ##@ Help # The help target prints out all targets with their descriptions organized @@ -634,3 +642,4 @@ envoy-regen: ## Regenerating envoy golden files .PHONY: help help: ## Display this help. @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + diff --git a/acl/MockAuthorizer.go b/acl/MockAuthorizer.go index e3a97ceec9..7e41db074f 100644 --- a/acl/MockAuthorizer.go +++ b/acl/MockAuthorizer.go @@ -59,31 +59,6 @@ func (m *MockAuthorizer) EventWrite(segment string, ctx *AuthorizerContext) Enfo return ret.Get(0).(EnforcementDecision) } -// IdentityRead checks for permission to read a given workload identity. -func (m *MockAuthorizer) IdentityRead(segment string, ctx *AuthorizerContext) EnforcementDecision { - ret := m.Called(segment, ctx) - return ret.Get(0).(EnforcementDecision) -} - -// IdentityReadAll checks for permission to read all workload identities. -func (m *MockAuthorizer) IdentityReadAll(ctx *AuthorizerContext) EnforcementDecision { - ret := m.Called(ctx) - return ret.Get(0).(EnforcementDecision) -} - -// IdentityWrite checks for permission to create or update a given -// workload identity. -func (m *MockAuthorizer) IdentityWrite(segment string, ctx *AuthorizerContext) EnforcementDecision { - ret := m.Called(segment, ctx) - return ret.Get(0).(EnforcementDecision) -} - -// IdentityWriteAny checks for write permission on any workload identity. -func (m *MockAuthorizer) IdentityWriteAny(ctx *AuthorizerContext) EnforcementDecision { - ret := m.Called(ctx) - return ret.Get(0).(EnforcementDecision) -} - // IntentionDefaultAllow determines the default authorized behavior // when no intentions match a Connect request. func (m *MockAuthorizer) IntentionDefaultAllow(ctx *AuthorizerContext) EnforcementDecision { diff --git a/acl/acl_ce.go b/acl/acl_ce.go index 7d2b8513b8..0d207ad421 100644 --- a/acl/acl_ce.go +++ b/acl/acl_ce.go @@ -8,12 +8,25 @@ package acl const ( WildcardPartitionName = "" DefaultPartitionName = "" -) + // NonEmptyDefaultPartitionName is the name of the default partition that is + // not empty. An example of this being supplied is when a partition is specified + // in the request for DNS by consul-dataplane. This has been added to support + // DNS v1.5, which needs to be compatible with the original DNS subsystem which + // supports partition being "default" or empty. Otherwise, use DefaultPartitionName. + NonEmptyDefaultPartitionName = "default" -// Reviewer Note: This is a little bit strange; one might want it to be "" like partition name -// However in consul/structs/intention.go we define IntentionDefaultNamespace as 'default' and so -// we use the same here -const DefaultNamespaceName = "default" + // DefaultNamespaceName is used to mimic the behavior in consul/structs/intention.go, + // where we define IntentionDefaultNamespace as 'default' and so we use the same here. + // This is a little bit strange; one might want it to be "" like DefaultPartitionName. + DefaultNamespaceName = "default" + + // EmptyNamespaceName is the name of the default partition that is an empty string. + // An example of this being supplied is when a namespace is specifiedDNS v1. + // EmptyNamespaceName has been added to support DNS v1.5, which needs to be + // compatible with the original DNS subsystem which supports partition being "default" or empty. + // Otherwise, use DefaultNamespaceName. + EmptyNamespaceName = "" +) type EnterpriseConfig struct { // no fields in CE diff --git a/acl/acl_test.go b/acl/acl_test.go index 28542024e9..3f4c882b0e 100644 --- a/acl/acl_test.go +++ b/acl/acl_test.go @@ -40,22 +40,6 @@ func checkAllowEventWrite(t *testing.T, authz Authorizer, prefix string, entCtx require.Equal(t, Allow, authz.EventWrite(prefix, entCtx)) } -func checkAllowIdentityRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { - require.Equal(t, Allow, authz.IdentityRead(prefix, entCtx)) -} - -func checkAllowIdentityReadAll(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { - require.Equal(t, Allow, authz.IdentityReadAll(entCtx)) -} - -func checkAllowIdentityWrite(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { - require.Equal(t, Allow, authz.IdentityWrite(prefix, entCtx)) -} - -func checkAllowIdentityWriteAny(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { - require.Equal(t, Allow, authz.IdentityWriteAny(entCtx)) -} - func checkAllowIntentionDefaultAllow(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { require.Equal(t, Allow, authz.IntentionDefaultAllow(entCtx)) } @@ -196,22 +180,6 @@ func checkDenyEventWrite(t *testing.T, authz Authorizer, prefix string, entCtx * require.Equal(t, Deny, authz.EventWrite(prefix, entCtx)) } -func checkDenyIdentityRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { - require.Equal(t, Deny, authz.IdentityRead(prefix, entCtx)) -} - -func checkDenyIdentityReadAll(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { - require.Equal(t, Deny, authz.IdentityReadAll(entCtx)) -} - -func checkDenyIdentityWrite(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { - require.Equal(t, Deny, authz.IdentityWrite(prefix, entCtx)) -} - -func checkDenyIdentityWriteAny(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { - require.Equal(t, Deny, authz.IdentityWriteAny(entCtx)) -} - func checkDenyIntentionDefaultAllow(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { require.Equal(t, Deny, authz.IntentionDefaultAllow(entCtx)) } @@ -360,22 +328,6 @@ func checkDefaultEventWrite(t *testing.T, authz Authorizer, prefix string, entCt require.Equal(t, Default, authz.EventWrite(prefix, entCtx)) } -func checkDefaultIdentityRead(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { - require.Equal(t, Default, authz.IdentityRead(prefix, entCtx)) -} - -func checkDefaultIdentityReadAll(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { - require.Equal(t, Default, authz.IdentityReadAll(entCtx)) -} - -func checkDefaultIdentityWrite(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { - require.Equal(t, Default, authz.IdentityWrite(prefix, entCtx)) -} - -func checkDefaultIdentityWriteAny(t *testing.T, authz Authorizer, _ string, entCtx *AuthorizerContext) { - require.Equal(t, Default, authz.IdentityWriteAny(entCtx)) -} - func checkDefaultIntentionDefaultAllow(t *testing.T, authz Authorizer, prefix string, entCtx *AuthorizerContext) { require.Equal(t, Default, authz.IntentionDefaultAllow(entCtx)) } @@ -516,10 +468,6 @@ func TestACL(t *testing.T) { {name: "DenyIntentionDefaultAllow", check: checkDenyIntentionDefaultAllow}, {name: "DenyIntentionRead", check: checkDenyIntentionRead}, {name: "DenyIntentionWrite", check: checkDenyIntentionWrite}, - {name: "DenyIdentityRead", check: checkDenyIdentityRead}, - {name: "DenyIdentityReadAll", check: checkDenyIdentityReadAll}, - {name: "DenyIdentityWrite", check: checkDenyIdentityWrite}, - {name: "DenyIdentityWriteAny", check: checkDenyIdentityWriteAny}, {name: "DenyKeyRead", check: checkDenyKeyRead}, {name: "DenyKeyringRead", check: checkDenyKeyringRead}, {name: "DenyKeyringWrite", check: checkDenyKeyringWrite}, @@ -554,10 +502,6 @@ func TestACL(t *testing.T) { {name: "AllowAgentWrite", check: checkAllowAgentWrite}, {name: "AllowEventRead", check: checkAllowEventRead}, {name: "AllowEventWrite", check: checkAllowEventWrite}, - {name: "AllowIdentityRead", check: checkAllowIdentityRead}, - {name: "AllowIdentityReadAll", check: checkAllowIdentityReadAll}, - {name: "AllowIdentityWrite", check: checkAllowIdentityWrite}, - {name: "AllowIdentityWriteAny", check: checkAllowIdentityWriteAny}, {name: "AllowIntentionDefaultAllow", check: checkAllowIntentionDefaultAllow}, {name: "AllowIntentionRead", check: checkAllowIntentionRead}, {name: "AllowIntentionWrite", check: checkAllowIntentionWrite}, @@ -597,10 +541,6 @@ func TestACL(t *testing.T) { {name: "AllowAgentWrite", check: checkAllowAgentWrite}, {name: "AllowEventRead", check: checkAllowEventRead}, {name: "AllowEventWrite", check: checkAllowEventWrite}, - {name: "AllowIdentityRead", check: checkAllowIdentityRead}, - {name: "AllowIdentityReadAll", check: checkAllowIdentityReadAll}, - {name: "AllowIdentityWrite", check: checkAllowIdentityWrite}, - {name: "AllowIdentityWriteAny", check: checkAllowIdentityWriteAny}, {name: "AllowIntentionDefaultAllow", check: checkAllowIntentionDefaultAllow}, {name: "AllowIntentionRead", check: checkAllowIntentionRead}, {name: "AllowIntentionWrite", check: checkAllowIntentionWrite}, @@ -1000,134 +940,6 @@ func TestACL(t *testing.T) { {name: "ChildOverrideWriteAllowed", prefix: "override", check: checkAllowAgentWrite}, }, }, - { - name: "IdentityDefaultAllowPolicyDeny", - defaultPolicy: AllowAll(), - policyStack: []*Policy{ - { - PolicyRules: PolicyRules{ - Identities: []*IdentityRule{ - { - Name: "foo", - Policy: PolicyDeny, - }, - }, - IdentityPrefixes: []*IdentityRule{ - { - Name: "prefix", - Policy: PolicyDeny, - }, - }, - }, - }, - }, - checks: []aclCheck{ - {name: "IdentityFooReadDenied", prefix: "foo", check: checkDenyIdentityRead}, - {name: "IdentityFooWriteDenied", prefix: "foo", check: checkDenyIdentityWrite}, - {name: "IdentityPrefixReadDenied", prefix: "prefix", check: checkDenyIdentityRead}, - {name: "IdentityPrefixWriteDenied", prefix: "prefix", check: checkDenyIdentityWrite}, - {name: "IdentityBarReadAllowed", prefix: "fail", check: checkAllowIdentityRead}, - {name: "IdentityBarWriteAllowed", prefix: "fail", check: checkAllowIdentityWrite}, - }, - }, - { - name: "IdentityDefaultDenyPolicyAllow", - defaultPolicy: DenyAll(), - policyStack: []*Policy{ - { - PolicyRules: PolicyRules{ - Identities: []*IdentityRule{ - { - Name: "foo", - Policy: PolicyWrite, - }, - }, - IdentityPrefixes: []*IdentityRule{ - { - Name: "prefix", - Policy: PolicyRead, - }, - }, - }, - }, - }, - checks: []aclCheck{ - {name: "IdentityFooReadAllowed", prefix: "foo", check: checkAllowIdentityRead}, - {name: "IdentityFooWriteAllowed", prefix: "foo", check: checkAllowIdentityWrite}, - {name: "IdentityPrefixReadAllowed", prefix: "prefix", check: checkAllowIdentityRead}, - {name: "IdentityPrefixWriteDenied", prefix: "prefix", check: checkDenyIdentityWrite}, - {name: "IdentityBarReadDenied", prefix: "fail", check: checkDenyIdentityRead}, - {name: "IdentityBarWriteDenied", prefix: "fail", check: checkDenyIdentityWrite}, - }, - }, - { - name: "IdentityDefaultDenyPolicyComplex", - defaultPolicy: DenyAll(), - policyStack: []*Policy{ - { - PolicyRules: PolicyRules{ - Identities: []*IdentityRule{ - { - Name: "football", - Policy: PolicyRead, - }, - { - Name: "prefix-forbidden", - Policy: PolicyDeny, - Intentions: PolicyDeny, - }, - }, - IdentityPrefixes: []*IdentityRule{ - { - Name: "foo", - Policy: PolicyWrite, - Intentions: PolicyWrite, - }, - { - Name: "prefix", - Policy: PolicyRead, - Intentions: PolicyWrite, - }, - }, - }, - }, - { - PolicyRules: PolicyRules{ - Identities: []*IdentityRule{ - { - Name: "foozball", - Policy: PolicyWrite, - Intentions: PolicyRead, - }, - }, - }, - }, - }, - checks: []aclCheck{ - {name: "IdentityReadAllowed", prefix: "foo", check: checkAllowIdentityRead}, - {name: "IdentityWriteAllowed", prefix: "foo", check: checkAllowIdentityWrite}, - {name: "TrafficPermissionsReadAllowed", prefix: "foo", check: checkAllowTrafficPermissionsRead}, - {name: "TrafficPermissionsWriteAllowed", prefix: "foo", check: checkAllowTrafficPermissionsWrite}, - {name: "IdentityReadAllowed", prefix: "football", check: checkAllowIdentityRead}, - {name: "IdentityWriteDenied", prefix: "football", check: checkDenyIdentityWrite}, - {name: "TrafficPermissionsReadAllowed", prefix: "football", check: checkAllowTrafficPermissionsRead}, - // This might be surprising but omitting intention rule gives at most intention:read - // if we have identity:write perms. This matches services as well. - {name: "TrafficPermissionsWriteDenied", prefix: "football", check: checkDenyTrafficPermissionsWrite}, - {name: "IdentityReadAllowed", prefix: "prefix", check: checkAllowIdentityRead}, - {name: "IdentityWriteDenied", prefix: "prefix", check: checkDenyIdentityWrite}, - {name: "TrafficPermissionsReadAllowed", prefix: "prefix", check: checkAllowTrafficPermissionsRead}, - {name: "TrafficPermissionsWriteDenied", prefix: "prefix", check: checkAllowTrafficPermissionsWrite}, - {name: "IdentityReadDenied", prefix: "prefix-forbidden", check: checkDenyIdentityRead}, - {name: "IdentityWriteDenied", prefix: "prefix-forbidden", check: checkDenyIdentityWrite}, - {name: "TrafficPermissionsReadDenied", prefix: "prefix-forbidden", check: checkDenyTrafficPermissionsRead}, - {name: "TrafficPermissionsWriteDenied", prefix: "prefix-forbidden", check: checkDenyTrafficPermissionsWrite}, - {name: "IdentityReadAllowed", prefix: "foozball", check: checkAllowIdentityRead}, - {name: "IdentityWriteAllowed", prefix: "foozball", check: checkAllowIdentityWrite}, - {name: "TrafficPermissionsReadAllowed", prefix: "foozball", check: checkAllowTrafficPermissionsRead}, - {name: "TrafficPermissionsWriteDenied", prefix: "foozball", check: checkDenyTrafficPermissionsWrite}, - }, - }, { name: "KeyringDefaultAllowPolicyDeny", defaultPolicy: AllowAll(), diff --git a/acl/authorizer.go b/acl/authorizer.go index 39bac5f7b0..937d861129 100644 --- a/acl/authorizer.go +++ b/acl/authorizer.go @@ -43,7 +43,6 @@ const ( ResourceACL Resource = "acl" ResourceAgent Resource = "agent" ResourceEvent Resource = "event" - ResourceIdentity Resource = "identity" ResourceIntention Resource = "intention" ResourceKey Resource = "key" ResourceKeyring Resource = "keyring" @@ -78,19 +77,6 @@ type Authorizer interface { // EventWrite determines if a specific event may be fired. EventWrite(string, *AuthorizerContext) EnforcementDecision - // IdentityRead checks for permission to read a given workload identity. - IdentityRead(string, *AuthorizerContext) EnforcementDecision - - // IdentityReadAll checks for permission to read all workload identities. - IdentityReadAll(*AuthorizerContext) EnforcementDecision - - // IdentityWrite checks for permission to create or update a given - // workload identity. - IdentityWrite(string, *AuthorizerContext) EnforcementDecision - - // IdentityWriteAny checks for write permission on any workload identity. - IdentityWriteAny(*AuthorizerContext) EnforcementDecision - // IntentionDefaultAllow determines the default authorized behavior // when no intentions match a Connect request. // @@ -267,40 +253,6 @@ func (a AllowAuthorizer) EventWriteAllowed(name string, ctx *AuthorizerContext) return nil } -// IdentityReadAllowed checks for permission to read a given workload identity, -func (a AllowAuthorizer) IdentityReadAllowed(name string, ctx *AuthorizerContext) error { - if a.Authorizer.IdentityRead(name, ctx) != Allow { - return PermissionDeniedByACL(a, ctx, ResourceIdentity, AccessRead, name) - } - return nil -} - -// IdentityReadAllAllowed checks for permission to read all workload identities. -func (a AllowAuthorizer) IdentityReadAllAllowed(ctx *AuthorizerContext) error { - if a.Authorizer.IdentityReadAll(ctx) != Allow { - // This is only used to gate certain UI functions right now (e.g metrics) - return PermissionDeniedByACL(a, ctx, ResourceIdentity, AccessRead, "all identities") // read - } - return nil -} - -// IdentityWriteAllowed checks for permission to create or update a given -// workload identity. -func (a AllowAuthorizer) IdentityWriteAllowed(name string, ctx *AuthorizerContext) error { - if a.Authorizer.IdentityWrite(name, ctx) != Allow { - return PermissionDeniedByACL(a, ctx, ResourceIdentity, AccessWrite, name) - } - return nil -} - -// IdentityWriteAnyAllowed checks for write permission on any workload identity -func (a AllowAuthorizer) IdentityWriteAnyAllowed(ctx *AuthorizerContext) error { - if a.Authorizer.IdentityWriteAny(ctx) != Allow { - return PermissionDeniedByACL(a, ctx, ResourceIdentity, AccessWrite, "any identity") - } - return nil -} - // IntentionReadAllowed determines if a specific intention can be read. func (a AllowAuthorizer) IntentionReadAllowed(name string, ctx *AuthorizerContext) error { if a.Authorizer.IntentionRead(name, ctx) != Allow { @@ -579,13 +531,6 @@ func Enforce(authz Authorizer, rsc Resource, segment string, access string, ctx case "write": return authz.EventWrite(segment, ctx), nil } - case ResourceIdentity: - switch lowerAccess { - case "read": - return authz.IdentityRead(segment, ctx), nil - case "write": - return authz.IdentityWrite(segment, ctx), nil - } case ResourceIntention: switch lowerAccess { case "read": diff --git a/acl/authorizer_test.go b/acl/authorizer_test.go index d538a04ad7..09cba85fa6 100644 --- a/acl/authorizer_test.go +++ b/acl/authorizer_test.go @@ -188,34 +188,6 @@ func TestACL_Enforce(t *testing.T) { ret: Deny, err: "Invalid access level", }, - { - method: "IdentityRead", - resource: ResourceIdentity, - segment: "foo", - access: "read", - ret: Deny, - }, - { - method: "IdentityRead", - resource: ResourceIdentity, - segment: "foo", - access: "read", - ret: Allow, - }, - { - method: "IdentityWrite", - resource: ResourceIdentity, - segment: "foo", - access: "write", - ret: Deny, - }, - { - method: "IdentityWrite", - resource: ResourceIdentity, - segment: "foo", - access: "write", - ret: Allow, - }, { method: "IntentionRead", resource: ResourceIntention, diff --git a/acl/chained_authorizer.go b/acl/chained_authorizer.go index 26f0c2dfe7..15016e9849 100644 --- a/acl/chained_authorizer.go +++ b/acl/chained_authorizer.go @@ -80,35 +80,6 @@ func (c *ChainedAuthorizer) EventWrite(name string, entCtx *AuthorizerContext) E }) } -// IdentityRead checks for permission to read a given workload identity. -func (c *ChainedAuthorizer) IdentityRead(name string, entCtx *AuthorizerContext) EnforcementDecision { - return c.executeChain(func(authz Authorizer) EnforcementDecision { - return authz.IdentityRead(name, entCtx) - }) -} - -// IdentityReadAll checks for permission to read all workload identities. -func (c *ChainedAuthorizer) IdentityReadAll(entCtx *AuthorizerContext) EnforcementDecision { - return c.executeChain(func(authz Authorizer) EnforcementDecision { - return authz.IdentityReadAll(entCtx) - }) -} - -// IdentityWrite checks for permission to create or update a given -// workload identity. -func (c *ChainedAuthorizer) IdentityWrite(name string, entCtx *AuthorizerContext) EnforcementDecision { - return c.executeChain(func(authz Authorizer) EnforcementDecision { - return authz.IdentityWrite(name, entCtx) - }) -} - -// IdentityWriteAny checks for write permission on any workload identity. -func (c *ChainedAuthorizer) IdentityWriteAny(entCtx *AuthorizerContext) EnforcementDecision { - return c.executeChain(func(authz Authorizer) EnforcementDecision { - return authz.IdentityWriteAny(entCtx) - }) -} - // IntentionDefaultAllow determines the default authorized behavior // when no intentions match a Connect request. func (c *ChainedAuthorizer) IntentionDefaultAllow(entCtx *AuthorizerContext) EnforcementDecision { diff --git a/acl/policy.go b/acl/policy.go index 0c88a9041b..86c9e83cfc 100644 --- a/acl/policy.go +++ b/acl/policy.go @@ -59,8 +59,6 @@ type PolicyRules struct { ACL string `hcl:"acl,expand"` Agents []*AgentRule `hcl:"agent,expand"` AgentPrefixes []*AgentRule `hcl:"agent_prefix,expand"` - Identities []*IdentityRule `hcl:"identity,expand"` - IdentityPrefixes []*IdentityRule `hcl:"identity_prefix,expand"` Keys []*KeyRule `hcl:"key,expand"` KeyPrefixes []*KeyRule `hcl:"key_prefix,expand"` Nodes []*NodeRule `hcl:"node,expand"` @@ -77,6 +75,11 @@ type PolicyRules struct { Operator string `hcl:"operator"` Mesh string `hcl:"mesh"` Peering string `hcl:"peering"` + + // Deprecated: exists just to track the former field for decoding + Identities []*IdentityRule `hcl:"identity,expand"` + // Deprecated: exists just to track the former field for decoding + IdentityPrefixes []*IdentityRule `hcl:"identity_prefix,expand"` } // Policy is used to represent the policy specified by an ACL configuration. @@ -93,6 +96,8 @@ type AgentRule struct { } // IdentityRule represents a policy for a workload identity +// +// Deprecated: exists just to track the former field for decoding type IdentityRule struct { Name string `hcl:",key"` Policy string @@ -183,29 +188,9 @@ func (pr *PolicyRules) Validate(conf *Config) error { } } - // Validate the identity policies - for _, id := range pr.Identities { - if !isPolicyValid(id.Policy, false) { - return fmt.Errorf("Invalid identity policy: %#v", id) - } - if id.Intentions != "" && !isPolicyValid(id.Intentions, false) { - return fmt.Errorf("Invalid identity intentions policy: %#v", id) - } - if err := id.EnterpriseRule.Validate(id.Policy, conf); err != nil { - return fmt.Errorf("Invalid identity enterprise policy: %#v, got error: %v", id, err) - } - } - for _, id := range pr.IdentityPrefixes { - if !isPolicyValid(id.Policy, false) { - return fmt.Errorf("Invalid identity_prefix policy: %#v", id) - } - if id.Intentions != "" && !isPolicyValid(id.Intentions, false) { - return fmt.Errorf("Invalid identity_prefix intentions policy: %#v", id) - } - if err := id.EnterpriseRule.Validate(id.Policy, conf); err != nil { - return fmt.Errorf("Invalid identity_prefix enterprise policy: %#v, got error: %v", id, err) - } - } + // Identity rules are deprecated, zero them out. + pr.Identities = nil + pr.IdentityPrefixes = nil // Validate the key policy for _, kp := range pr.Keys { diff --git a/acl/policy_authorizer.go b/acl/policy_authorizer.go index 11d19609ef..16ffa743f9 100644 --- a/acl/policy_authorizer.go +++ b/acl/policy_authorizer.go @@ -14,9 +14,6 @@ type policyAuthorizer struct { // agentRules contain the exact-match agent policies agentRules *radix.Tree - // identityRules contains the identity exact-match policies - identityRules *radix.Tree - // intentionRules contains the service intention exact-match policies intentionRules *radix.Tree @@ -186,48 +183,6 @@ func (p *policyAuthorizer) loadRules(policy *PolicyRules) error { } } - // Load the identity policy (exact matches) - for _, id := range policy.Identities { - if err := insertPolicyIntoRadix(id.Name, id.Policy, &id.EnterpriseRule, p.identityRules, false); err != nil { - return err - } - - intention := id.Intentions - if intention == "" { - switch id.Policy { - case PolicyRead, PolicyWrite: - intention = PolicyRead - default: - intention = PolicyDeny - } - } - - if err := insertPolicyIntoRadix(id.Name, intention, &id.EnterpriseRule, p.trafficPermissionsRules, false); err != nil { - return err - } - } - - // Load the identity policy (prefix matches) - for _, id := range policy.IdentityPrefixes { - if err := insertPolicyIntoRadix(id.Name, id.Policy, &id.EnterpriseRule, p.identityRules, true); err != nil { - return err - } - - intention := id.Intentions - if intention == "" { - switch id.Policy { - case PolicyRead, PolicyWrite: - intention = PolicyRead - default: - intention = PolicyDeny - } - } - - if err := insertPolicyIntoRadix(id.Name, intention, &id.EnterpriseRule, p.trafficPermissionsRules, true); err != nil { - return err - } - } - // Load the key policy (exact matches) for _, kp := range policy.Keys { if err := insertPolicyIntoRadix(kp.Prefix, kp.Policy, &kp.EnterpriseRule, p.keyRules, false); err != nil { @@ -397,7 +352,6 @@ func newPolicyAuthorizer(policies []*Policy, ent *Config) (*policyAuthorizer, er func newPolicyAuthorizerFromRules(rules *PolicyRules, ent *Config) (*policyAuthorizer, error) { p := &policyAuthorizer{ agentRules: radix.New(), - identityRules: radix.New(), intentionRules: radix.New(), trafficPermissionsRules: radix.New(), keyRules: radix.New(), @@ -578,33 +532,6 @@ func (p *policyAuthorizer) EventWrite(name string, _ *AuthorizerContext) Enforce return Default } -// IdentityRead checks for permission to read a given workload identity. -func (p *policyAuthorizer) IdentityRead(name string, _ *AuthorizerContext) EnforcementDecision { - if rule, ok := getPolicy(name, p.identityRules); ok { - return enforce(rule.access, AccessRead) - } - return Default -} - -// IdentityReadAll checks for permission to read all workload identities. -func (p *policyAuthorizer) IdentityReadAll(_ *AuthorizerContext) EnforcementDecision { - return p.allAllowed(p.identityRules, AccessRead) -} - -// IdentityWrite checks for permission to create or update a given -// workload identity. -func (p *policyAuthorizer) IdentityWrite(name string, _ *AuthorizerContext) EnforcementDecision { - if rule, ok := getPolicy(name, p.identityRules); ok { - return enforce(rule.access, AccessWrite) - } - return Default -} - -// IdentityWriteAny checks for write permission on any workload identity. -func (p *policyAuthorizer) IdentityWriteAny(_ *AuthorizerContext) EnforcementDecision { - return p.anyAllowed(p.identityRules, AccessWrite) -} - // IntentionDefaultAllow returns whether the default behavior when there are // no matching intentions is to allow or deny. func (p *policyAuthorizer) IntentionDefaultAllow(_ *AuthorizerContext) EnforcementDecision { diff --git a/acl/policy_authorizer_test.go b/acl/policy_authorizer_test.go index 96272d8b12..a2f9b929f1 100644 --- a/acl/policy_authorizer_test.go +++ b/acl/policy_authorizer_test.go @@ -41,9 +41,6 @@ func TestPolicyAuthorizer(t *testing.T) { {name: "DefaultAgentWrite", prefix: "foo", check: checkDefaultAgentWrite}, {name: "DefaultEventRead", prefix: "foo", check: checkDefaultEventRead}, {name: "DefaultEventWrite", prefix: "foo", check: checkDefaultEventWrite}, - {name: "DefaultIdentityRead", prefix: "foo", check: checkDefaultIdentityRead}, - {name: "DefaultIdentityWrite", prefix: "foo", check: checkDefaultIdentityWrite}, - {name: "DefaultIdentityWriteAny", prefix: "", check: checkDefaultIdentityWriteAny}, {name: "DefaultIntentionDefaultAllow", prefix: "foo", check: checkDefaultIntentionDefaultAllow}, {name: "DefaultIntentionRead", prefix: "foo", check: checkDefaultIntentionRead}, {name: "DefaultIntentionWrite", prefix: "foo", check: checkDefaultIntentionWrite}, @@ -190,29 +187,6 @@ func TestPolicyAuthorizer(t *testing.T) { Policy: PolicyRead, }, }, - Identities: []*IdentityRule{ - { - Name: "foo", - Policy: PolicyWrite, - Intentions: PolicyWrite, - }, - { - Name: "football", - Policy: PolicyDeny, - }, - }, - IdentityPrefixes: []*IdentityRule{ - { - Name: "foot", - Policy: PolicyRead, - Intentions: PolicyRead, - }, - { - Name: "fo", - Policy: PolicyRead, - Intentions: PolicyRead, - }, - }, Keys: []*KeyRule{ { Prefix: "foo", @@ -400,22 +374,6 @@ func TestPolicyAuthorizer(t *testing.T) { {name: "ServiceWriteAnyAllowed", prefix: "", check: checkAllowServiceWriteAny}, {name: "ServiceReadWithinPrefixDenied", prefix: "foot", check: checkDenyServiceReadPrefix}, - {name: "IdentityReadPrefixAllowed", prefix: "fo", check: checkAllowIdentityRead}, - {name: "IdentityWritePrefixDenied", prefix: "fo", check: checkDenyIdentityWrite}, - {name: "IdentityReadPrefixAllowed", prefix: "for", check: checkAllowIdentityRead}, - {name: "IdentityWritePrefixDenied", prefix: "for", check: checkDenyIdentityWrite}, - {name: "IdentityReadAllowed", prefix: "foo", check: checkAllowIdentityRead}, - {name: "IdentityWriteAllowed", prefix: "foo", check: checkAllowIdentityWrite}, - {name: "IdentityReadPrefixAllowed", prefix: "foot", check: checkAllowIdentityRead}, - {name: "IdentityWritePrefixDenied", prefix: "foot", check: checkDenyIdentityWrite}, - {name: "IdentityReadPrefixAllowed", prefix: "foot2", check: checkAllowIdentityRead}, - {name: "IdentityWritePrefixDenied", prefix: "foot2", check: checkDenyIdentityWrite}, - {name: "IdentityReadPrefixAllowed", prefix: "food", check: checkAllowIdentityRead}, - {name: "IdentityWritePrefixDenied", prefix: "food", check: checkDenyIdentityWrite}, - {name: "IdentityReadDenied", prefix: "football", check: checkDenyIdentityRead}, - {name: "IdentityWriteDenied", prefix: "football", check: checkDenyIdentityWrite}, - {name: "IdentityWriteAnyAllowed", prefix: "", check: checkAllowIdentityWriteAny}, - {name: "IntentionReadPrefixAllowed", prefix: "fo", check: checkAllowIntentionRead}, {name: "IntentionWritePrefixDenied", prefix: "fo", check: checkDenyIntentionWrite}, {name: "IntentionReadPrefixAllowed", prefix: "for", check: checkAllowIntentionRead}, diff --git a/acl/policy_test.go b/acl/policy_test.go index 599c8c977e..2ce0b32892 100644 --- a/acl/policy_test.go +++ b/acl/policy_test.go @@ -42,12 +42,6 @@ func TestPolicySourceParse(t *testing.T) { event "bar" { policy = "deny" } - identity_prefix "" { - policy = "write" - } - identity "foo" { - policy = "read" - } key_prefix "" { policy = "read" } @@ -123,16 +117,6 @@ func TestPolicySourceParse(t *testing.T) { "policy": "deny" } }, - "identity_prefix": { - "": { - "policy": "write" - } - }, - "identity": { - "foo": { - "policy": "read" - } - }, "key_prefix": { "": { "policy": "read" @@ -233,18 +217,6 @@ func TestPolicySourceParse(t *testing.T) { Policy: PolicyDeny, }, }, - IdentityPrefixes: []*IdentityRule{ - { - Name: "", - Policy: PolicyWrite, - }, - }, - Identities: []*IdentityRule{ - { - Name: "foo", - Policy: PolicyRead, - }, - }, Keyring: PolicyDeny, KeyPrefixes: []*KeyRule{ { @@ -331,39 +303,6 @@ func TestPolicySourceParse(t *testing.T) { }, }}, }, - { - Name: "Identity No Intentions", - Rules: `identity "foo" { policy = "write" }`, - RulesJSON: `{ "identity": { "foo": { "policy": "write" }}}`, - Expected: &Policy{PolicyRules: PolicyRules{ - Identities: []*IdentityRule{ - { - Name: "foo", - Policy: "write", - }, - }, - }}, - }, - { - Name: "Identity Intentions", - Rules: `identity "foo" { policy = "write" intentions = "read" }`, - RulesJSON: `{ "identity": { "foo": { "policy": "write", "intentions": "read" }}}`, - Expected: &Policy{PolicyRules: PolicyRules{ - Identities: []*IdentityRule{ - { - Name: "foo", - Policy: "write", - Intentions: "read", - }, - }, - }}, - }, - { - Name: "Identity Intention: invalid value", - Rules: `identity "foo" { policy = "write" intentions = "foo" }`, - RulesJSON: `{ "identity": { "foo": { "policy": "write", "intentions": "foo" }}}`, - Err: "Invalid identity intentions policy", - }, { Name: "Service No Intentions", Rules: `service "foo" { policy = "write" }`, @@ -415,18 +354,6 @@ func TestPolicySourceParse(t *testing.T) { RulesJSON: `{ "agent_prefix": { "foo": { "policy": "nope" }}}`, Err: "Invalid agent_prefix policy", }, - { - Name: "Bad Policy - Identity", - Rules: `identity "foo" { policy = "nope" }`, - RulesJSON: `{ "identity": { "foo": { "policy": "nope" }}}`, - Err: "Invalid identity policy", - }, - { - Name: "Bad Policy - Identity Prefix", - Rules: `identity_prefix "foo" { policy = "nope" }`, - RulesJSON: `{ "identity_prefix": { "foo": { "policy": "nope" }}}`, - Err: "Invalid identity_prefix policy", - }, { Name: "Bad Policy - Key", Rules: `key "foo" { policy = "nope" }`, @@ -758,109 +685,6 @@ func TestMergePolicies(t *testing.T) { }, }}, }, - { - name: "Identities", - input: []*Policy{ - {PolicyRules: PolicyRules{ - Identities: []*IdentityRule{ - { - Name: "foo", - Policy: PolicyWrite, - Intentions: PolicyWrite, - }, - { - Name: "bar", - Policy: PolicyRead, - Intentions: PolicyRead, - }, - { - Name: "baz", - Policy: PolicyWrite, - Intentions: PolicyWrite, - }, - }, - IdentityPrefixes: []*IdentityRule{ - { - Name: "000", - Policy: PolicyWrite, - Intentions: PolicyWrite, - }, - { - Name: "111", - Policy: PolicyRead, - Intentions: PolicyRead, - }, - { - Name: "222", - Policy: PolicyWrite, - Intentions: PolicyWrite, - }, - }, - }}, - {PolicyRules: PolicyRules{ - Identities: []*IdentityRule{ - { - Name: "foo", - Policy: PolicyRead, - Intentions: PolicyRead, - }, - { - Name: "baz", - Policy: PolicyDeny, - Intentions: PolicyDeny, - }, - }, - IdentityPrefixes: []*IdentityRule{ - { - Name: "000", - Policy: PolicyRead, - Intentions: PolicyRead, - }, - { - Name: "222", - Policy: PolicyDeny, - Intentions: PolicyDeny, - }, - }, - }}, - }, - expected: &Policy{PolicyRules: PolicyRules{ - Identities: []*IdentityRule{ - { - Name: "foo", - Policy: PolicyWrite, - Intentions: PolicyWrite, - }, - { - Name: "bar", - Policy: PolicyRead, - Intentions: PolicyRead, - }, - { - Name: "baz", - Policy: PolicyDeny, - Intentions: PolicyDeny, - }, - }, - IdentityPrefixes: []*IdentityRule{ - { - Name: "000", - Policy: PolicyWrite, - Intentions: PolicyWrite, - }, - { - Name: "111", - Policy: PolicyRead, - Intentions: PolicyRead, - }, - { - Name: "222", - Policy: PolicyDeny, - Intentions: PolicyDeny, - }, - }, - }}, - }, { name: "Node", input: []*Policy{ diff --git a/agent/acl_endpoint_test.go b/agent/acl_endpoint_test.go index 060f1f03ef..7b484e092b 100644 --- a/agent/acl_endpoint_test.go +++ b/agent/acl_endpoint_test.go @@ -15,9 +15,10 @@ import ( "time" "github.com/go-jose/go-jose/v3/jwt" - "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/require" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/consul/authmethod/testauth" "github.com/hashicorp/consul/agent/structs" @@ -1407,7 +1408,7 @@ func TestACL_HTTP(t *testing.T) { var list map[string]api.ACLTemplatedPolicyResponse require.NoError(t, json.NewDecoder(resp.Body).Decode(&list)) - require.Len(t, list, 7) + require.Len(t, list, 6) require.Equal(t, api.ACLTemplatedPolicyResponse{ TemplateName: api.ACLTemplatedPolicyServiceName, @@ -2225,7 +2226,7 @@ func TestACL_Authorize(t *testing.T) { policyReq := structs.ACLPolicySetRequest{ Policy: structs.ACLPolicy{ Name: "test", - Rules: `acl = "read" operator = "write" identity_prefix "" { policy = "read"} 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", WriteRequest: structs.WriteRequest{Token: TestDefaultInitialManagementToken}, @@ -2311,16 +2312,6 @@ func TestACL_Authorize(t *testing.T) { Segment: "foo", Access: "write", }, - { - Resource: "identity", - Segment: "foo", - Access: "read", - }, - { - Resource: "identity", - Segment: "foo", - Access: "write", - }, { Resource: "intention", Segment: "foo", @@ -2471,16 +2462,6 @@ func TestACL_Authorize(t *testing.T) { Segment: "foo", Access: "write", }, - { - Resource: "identity", - Segment: "foo", - Access: "read", - }, - { - Resource: "identity", - Segment: "foo", - Access: "write", - }, { Resource: "intention", Segment: "foo", @@ -2587,8 +2568,6 @@ func TestACL_Authorize(t *testing.T) { false, // agent:write false, // event:read false, // event:write - true, // identity:read - false, // identity:write true, // intentions:read false, // intention:write false, // key:read diff --git a/agent/acl_test.go b/agent/acl_test.go index 0958db8db6..8c5eace1f1 100644 --- a/agent/acl_test.go +++ b/agent/acl_test.go @@ -11,6 +11,8 @@ import ( "time" "github.com/armon/go-metrics" + "github.com/stretchr/testify/require" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/serf/serf" @@ -21,12 +23,11 @@ import ( "github.com/hashicorp/consul/agent/local" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/types" - - "github.com/stretchr/testify/require" ) type authzResolver func(string) (structs.ACLIdentity, acl.Authorizer, error) @@ -128,7 +129,7 @@ func (a *TestACLAgent) ResolveTokenAndDefaultMeta(secretID string, entMeta *acl. } // All of these are stubs to satisfy the interface -func (a *TestACLAgent) GetLANCoordinate() (lib.CoordinateSet, error) { +func (a *TestACLAgent) GetLANCoordinate() (librtt.CoordinateSet, error) { return nil, fmt.Errorf("Unimplemented") } func (a *TestACLAgent) Leave() error { diff --git a/agent/ae/ae.go b/agent/ae/ae.go index f8b9a331d1..94db2a7cf0 100644 --- a/agent/ae/ae.go +++ b/agent/ae/ae.go @@ -152,14 +152,6 @@ const ( retryFullSyncState fsmState = "retryFullSync" ) -// HardDisableSync is like PauseSync but is one-way. It causes other -// Pause/Resume/Start operations to be completely ignored. -func (s *StateSyncer) HardDisableSync() { - s.pauseLock.Lock() - s.hardDisabled = true - s.pauseLock.Unlock() -} - // Run is the long running method to perform state synchronization // between local and remote servers. func (s *StateSyncer) Run() { diff --git a/agent/agent.go b/agent/agent.go index d066b4cba6..c5fb3cc886 100644 --- a/agent/agent.go +++ b/agent/agent.go @@ -48,8 +48,6 @@ import ( "github.com/hashicorp/consul/agent/consul" rpcRate "github.com/hashicorp/consul/agent/consul/rate" "github.com/hashicorp/consul/agent/consul/servercert" - "github.com/hashicorp/consul/agent/discovery" - "github.com/hashicorp/consul/agent/dns" external "github.com/hashicorp/consul/agent/grpc-external" grpcDNS "github.com/hashicorp/consul/agent/grpc-external/services/dns" middleware "github.com/hashicorp/consul/agent/grpc-middleware" @@ -70,7 +68,7 @@ import ( "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/api/watch" libdns "github.com/hashicorp/consul/internal/dnsutil" - proxytracker "github.com/hashicorp/consul/internal/mesh/proxy-tracker" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/ipaddr" "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/lib/file" @@ -189,7 +187,7 @@ type delegate interface { // are ancillary members of. // // NOTE: This assumes coordinates are enabled, so check that before calling. - GetLANCoordinate() (lib.CoordinateSet, error) + GetLANCoordinate() (librtt.CoordinateSet, error) // 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 @@ -221,7 +219,7 @@ type notifier interface { Notify(string) error } -// dnsServer abstracts the V1 and V2 implementations of the DNS server. +// dnsServer abstracts the implementations of the DNS server. type dnsServer interface { GetAddr() string ListenAndServe(string, string, func()) error @@ -353,10 +351,6 @@ type Agent struct { // dnsServer provides the DNS API dnsServers []dnsServer - // catalogDataFetcher is used as an interface to the catalog for service discovery - // (aka DNS). Only applicable to the V2 DNS server (agent/dns). - catalogDataFetcher discovery.CatalogDataFetcher - // apiServers listening for connections. If any of these server goroutines // fail, the agent will be shutdown. apiServers *apiServers @@ -644,9 +638,6 @@ func (a *Agent) Start(ctx context.Context) error { // create the state synchronization manager which performs // regular and on-demand state synchronizations (anti-entropy). a.sync = ae.NewStateSyncer(a.State, c.AEInterval, a.shutdownCh, a.logger) - if a.baseDeps.UseV2Resources() { - a.sync.HardDisableSync() - } err = validateFIPSConfig(a.config) if err != nil { @@ -678,10 +669,6 @@ func (a *Agent) Start(ctx context.Context) error { return fmt.Errorf("failed to start Consul enterprise component: %v", err) } - // proxyTracker will be used in the creation of the XDS server and also - // in the registration of the v2 xds controller - var proxyTracker *proxytracker.ProxyTracker - // Setup either the client or the server. var consulServer *consul.Server if c.ServerMode { @@ -721,13 +708,7 @@ func (a *Agent) Start(ctx context.Context) error { nil, ) - if a.baseDeps.UseV2Resources() { - proxyTracker = proxytracker.NewProxyTracker(proxytracker.ProxyTrackerConfig{ - Logger: a.logger.Named("proxy-tracker"), - SessionLimiter: a.baseDeps.XDSStreamLimiter, - }) - } - consulServer, err = consul.NewServer(consulCfg, a.baseDeps.Deps, a.externalGRPCServer, incomingRPCLimiter, serverLogger, proxyTracker) + consulServer, err = consul.NewServer(consulCfg, a.baseDeps.Deps, a.externalGRPCServer, incomingRPCLimiter, serverLogger) if err != nil { return fmt.Errorf("Failed to start Consul server: %v", err) } @@ -751,10 +732,6 @@ func (a *Agent) Start(ctx context.Context) error { } } } else { - if a.baseDeps.UseV2Resources() { - return fmt.Errorf("can't start agent: client agents are not supported with v2 resources") - } - // the conn is used to connect to the consul server agent conn, err := a.baseDeps.GRPCConnPool.ClientConn(a.baseDeps.RuntimeConfig.Datacenter) if err != nil { @@ -878,14 +855,8 @@ func (a *Agent) Start(ctx context.Context) error { } // start DNS servers - if a.baseDeps.UseV1DNS() { - if err := a.listenAndServeV1DNS(); err != nil { - return err - } - } else { - if err := a.listenAndServeV2DNS(); err != nil { - return err - } + if err := a.listenAndServeDNS(); err != nil { + return err } // Configure the http connection limiter. @@ -906,7 +877,7 @@ func (a *Agent) Start(ctx context.Context) error { } // Start grpc and grpc_tls servers. - if err := a.listenAndServeGRPC(proxyTracker, consulServer); err != nil { + if err := a.listenAndServeGRPC(consulServer); err != nil { return err } @@ -967,28 +938,20 @@ func (a *Agent) configureXDSServer(proxyWatcher xds.ProxyWatcher, server *consul // TODO(agentless): rather than asserting the concrete type of delegate, we // should add a method to the Delegate interface to build a ConfigSource. if server != nil { - switch proxyWatcher.(type) { - case *proxytracker.ProxyTracker: - go func() { - <-a.shutdownCh - proxyWatcher.(*proxytracker.ProxyTracker).Shutdown() - }() - default: - catalogCfg := catalogproxycfg.NewConfigSource(catalogproxycfg.Config{ - NodeName: a.config.NodeName, - LocalState: a.State, - LocalConfigSource: proxyWatcher, - Manager: a.proxyConfig, - GetStore: func() catalogproxycfg.Store { return server.FSM().State() }, - Logger: a.proxyConfig.Logger.Named("server-catalog"), - SessionLimiter: a.baseDeps.XDSStreamLimiter, - }) - go func() { - <-a.shutdownCh - catalogCfg.Shutdown() - }() - proxyWatcher = catalogCfg - } + catalogCfg := catalogproxycfg.NewConfigSource(catalogproxycfg.Config{ + NodeName: a.config.NodeName, + LocalState: a.State, + LocalConfigSource: proxyWatcher, + Manager: a.proxyConfig, + GetStore: func() catalogproxycfg.Store { return server.FSM().State() }, + Logger: a.proxyConfig.Logger.Named("server-catalog"), + SessionLimiter: a.baseDeps.XDSStreamLimiter, + }) + go func() { + <-a.shutdownCh + catalogCfg.Shutdown() + }() + proxyWatcher = catalogCfg } a.xdsServer = xds.NewServer( a.config.NodeName, @@ -1002,16 +965,11 @@ func (a *Agent) configureXDSServer(proxyWatcher xds.ProxyWatcher, server *consul a.xdsServer.Register(a.externalGRPCServer) } -func (a *Agent) listenAndServeGRPC(proxyTracker *proxytracker.ProxyTracker, server *consul.Server) error { +func (a *Agent) listenAndServeGRPC(server *consul.Server) error { if len(a.config.GRPCAddrs) < 1 && len(a.config.GRPCTLSAddrs) < 1 { return nil } - var proxyWatcher xds.ProxyWatcher - if a.baseDeps.UseV2Resources() { - proxyWatcher = proxyTracker - } else { - proxyWatcher = localproxycfg.NewConfigSource(a.proxyConfig) - } + var proxyWatcher xds.ProxyWatcher = localproxycfg.NewConfigSource(a.proxyConfig) a.configureXDSServer(proxyWatcher, server) @@ -1064,7 +1022,7 @@ func (a *Agent) listenAndServeGRPC(proxyTracker *proxytracker.ProxyTracker, serv return nil } -func (a *Agent) listenAndServeV1DNS() error { +func (a *Agent) listenAndServeDNS() error { notif := make(chan net.Addr, len(a.config.DNSAddrs)) errCh := make(chan error, len(a.config.DNSAddrs)) for _, addr := range a.config.DNSAddrs { @@ -1116,92 +1074,6 @@ func (a *Agent) listenAndServeV1DNS() error { return merr.ErrorOrNil() } -func (a *Agent) listenAndServeV2DNS() error { - - // Check the catalog version and decide which implementation of the data fetcher to implement - if a.baseDeps.UseV2Resources() { - a.catalogDataFetcher = discovery.NewV2DataFetcher(a.config, a.delegate.ResourceServiceClient(), a.logger.Named("catalog-data-fetcher")) - } else { - a.catalogDataFetcher = discovery.NewV1DataFetcher(a.config, - a.AgentEnterpriseMeta(), - a.cache.Get, - a.RPC, - a.rpcClientHealth.ServiceNodes, - a.rpcClientConfigEntry.GetSamenessGroup, - a.TranslateServicePort, - a.logger.Named("catalog-data-fetcher")) - } - - // Generate a Query Processor with the appropriate data fetcher - processor := discovery.NewQueryProcessor(a.catalogDataFetcher) - - notif := make(chan net.Addr, len(a.config.DNSAddrs)) - errCh := make(chan error, len(a.config.DNSAddrs)) - - // create server - cfg := dns.Config{ - AgentConfig: a.config, - EntMeta: *a.AgentEnterpriseMeta(), - Logger: a.logger, - Processor: processor, - TokenFunc: a.getTokenFunc(), - TranslateAddressFunc: a.TranslateAddress, - TranslateServiceAddressFunc: a.TranslateServiceAddress, - } - - for _, addr := range a.config.DNSAddrs { - s, err := dns.NewServer(cfg) - if err != nil { - return err - } - a.dnsServers = append(a.dnsServers, s) - - // start server - a.wgServers.Add(1) - go func(addr net.Addr) { - defer a.wgServers.Done() - err := s.ListenAndServe(addr.Network(), addr.String(), func() { notif <- addr }) - if err != nil && !strings.Contains(err.Error(), "accept") { - errCh <- err - } - }(addr) - } - - s, err := dns.NewServer(cfg) - if err != nil { - return fmt.Errorf("failed to create grpc dns server: %w", err) - } - - // Create a v2 compatible grpc dns server - grpcDNS.NewServerV2(grpcDNS.ConfigV2{ - Logger: a.logger.Named("grpc-api.dns"), - DNSRouter: s.Router, - TokenFunc: a.getTokenFunc(), - }).Register(a.externalGRPCServer) - - a.dnsServers = append(a.dnsServers, s) - - // wait for servers to be up - timeout := time.After(time.Second) - var merr *multierror.Error - for range a.config.DNSAddrs { - select { - case addr := <-notif: - a.logger.Info("Started DNS server", - "address", addr.String(), - "network", addr.Network(), - ) - - case err := <-errCh: - merr = multierror.Append(merr, err) - case <-timeout: - merr = multierror.Append(merr, fmt.Errorf("agent: timeout starting DNS servers")) - return merr.ErrorOrNil() - } - } - return merr.ErrorOrNil() -} - // startListeners will return a net.Listener for every address unless an // error is encountered, in which case it will close all previously opened // listeners and return the error. @@ -1562,6 +1434,9 @@ func newConsulConfig(runtimeCfg *config.RuntimeConfig, logger hclog.Logger) (*co cfg.GRPCTLSPort = runtimeCfg.GRPCTLSPort cfg.Segment = runtimeCfg.SegmentName + + cfg.RaftConfig.PreVoteDisabled = runtimeCfg.RaftPreVoteDisabled + if len(runtimeCfg.Segments) > 0 { segments, err := segmentConfig(runtimeCfg) if err != nil { @@ -2149,7 +2024,7 @@ func (a *Agent) SyncPausedCh() <-chan struct{} { // GetLANCoordinate returns the coordinates of this node in the local pools // (assumes coordinates are enabled, so check that before calling). -func (a *Agent) GetLANCoordinate() (lib.CoordinateSet, error) { +func (a *Agent) GetLANCoordinate() (librtt.CoordinateSet, error) { return a.delegate.GetLANCoordinate() } @@ -4413,10 +4288,6 @@ func (a *Agent) reloadConfigInternal(newCfg *config.RuntimeConfig) error { return fmt.Errorf("Failed reloading dns config : %v", err) } } - // This field is only populated for the V2 DNS server - if a.catalogDataFetcher != nil { - a.catalogDataFetcher.LoadConfig(newCfg) - } err := a.reloadEnterprise(newCfg) if err != nil { diff --git a/agent/agent_endpoint.go b/agent/agent_endpoint.go index 5360eafe28..996212c97e 100644 --- a/agent/agent_endpoint.go +++ b/agent/agent_endpoint.go @@ -31,8 +31,8 @@ import ( token_store "github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/envoyextensions/xdscommon" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/ipaddr" - "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/logging" "github.com/hashicorp/consul/logging/monitor" "github.com/hashicorp/consul/types" @@ -82,7 +82,7 @@ func (s *HTTPHandlers) AgentSelf(resp http.ResponseWriter, req *http.Request) (i return nil, err } - var cs lib.CoordinateSet + var cs librtt.CoordinateSet if !s.agent.config.DisableCoordinates { var err error if cs, err = s.agent.GetLANCoordinate(); err != nil { diff --git a/agent/agent_endpoint_test.go b/agent/agent_endpoint_test.go index 9fd76fae4f..69551d7c36 100644 --- a/agent/agent_endpoint_test.go +++ b/agent/agent_endpoint_test.go @@ -21,14 +21,15 @@ import ( "time" "github.com/armon/go-metrics" - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-uuid" - "github.com/hashicorp/serf/serf" "github.com/mitchellh/hashstructure" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "golang.org/x/time/rate" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/serf/serf" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl/resolver" "github.com/hashicorp/consul/agent/config" @@ -79,46 +80,6 @@ func createACLTokenWithAgentReadPolicy(t *testing.T, srv *HTTPHandlers) string { return svcToken.SecretID } -func TestAgentEndpointsFailInV2(t *testing.T) { - t.Parallel() - - a := NewTestAgent(t, `experiments = ["resource-apis"]`) - - checkRequest := func(method, url string) { - t.Run(method+" "+url, func(t *testing.T) { - assertV1CatalogEndpointDoesNotWorkWithV2(t, a, method, url, `{}`) - }) - } - - t.Run("agent-self-with-params", func(t *testing.T) { - req, err := http.NewRequest("GET", "/v1/agent/self?dc=dc1", nil) - require.NoError(t, err) - - resp := httptest.NewRecorder() - a.srv.h.ServeHTTP(resp, req) - require.Equal(t, http.StatusOK, resp.Code) - - _, err = io.ReadAll(resp.Body) - require.NoError(t, err) - }) - - checkRequest("PUT", "/v1/agent/maintenance") - checkRequest("GET", "/v1/agent/services") - checkRequest("GET", "/v1/agent/service/web") - checkRequest("GET", "/v1/agent/checks") - checkRequest("GET", "/v1/agent/health/service/id/web") - checkRequest("GET", "/v1/agent/health/service/name/web") - checkRequest("PUT", "/v1/agent/check/register") - checkRequest("PUT", "/v1/agent/check/deregister/web") - checkRequest("PUT", "/v1/agent/check/pass/web") - checkRequest("PUT", "/v1/agent/check/warn/web") - checkRequest("PUT", "/v1/agent/check/fail/web") - checkRequest("PUT", "/v1/agent/check/update/web") - checkRequest("PUT", "/v1/agent/service/register") - checkRequest("PUT", "/v1/agent/service/deregister/web") - checkRequest("PUT", "/v1/agent/service/maintenance/web") -} - func TestAgent_Services(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") @@ -1660,6 +1621,7 @@ func newDefaultBaseDeps(t *testing.T) BaseDeps { } func TestHTTPHandlers_AgentMetricsStream_ACLDeny(t *testing.T) { + t.Skip("this test panics without a license manager in enterprise") bd := newDefaultBaseDeps(t) bd.Tokens = new(tokenStore.Store) sink := metrics.NewInmemSink(30*time.Millisecond, time.Second) @@ -1691,6 +1653,7 @@ func TestHTTPHandlers_AgentMetricsStream_ACLDeny(t *testing.T) { } func TestHTTPHandlers_AgentMetricsStream(t *testing.T) { + t.Skip("this test panics without a license manager in enterprise") bd := newDefaultBaseDeps(t) bd.Tokens = new(tokenStore.Store) sink := metrics.NewInmemSink(20*time.Millisecond, time.Second) diff --git a/agent/agent_test.go b/agent/agent_test.go index b448ec432a..316adeb3dd 100644 --- a/agent/agent_test.go +++ b/agent/agent_test.go @@ -58,9 +58,9 @@ import ( "github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/internal/go-sso/oidcauth/oidcauthtest" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/ipaddr" - "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/proto/private/pbautoconf" "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil" @@ -3954,7 +3954,7 @@ func TestAgent_GetCoordinate(t *testing.T) { coords, err := a.GetLANCoordinate() require.NoError(t, err) - expected := lib.CoordinateSet{ + expected := librtt.CoordinateSet{ "": &coordinate.Coordinate{ Error: 1.5, Height: 1e-05, diff --git a/agent/blockingquery/blockingquery.go b/agent/blockingquery/blockingquery.go index 3e073a1ffa..32f170cc1c 100644 --- a/agent/blockingquery/blockingquery.go +++ b/agent/blockingquery/blockingquery.go @@ -28,6 +28,8 @@ type QueryFn func(memdb.WatchSet, *state.Store) error // RequestOptions are options used by Server.blockingQuery to modify the // behaviour of the query operation, or to populate response metadata. +// +//go:generate mockery --name RequestOptions --inpackage type RequestOptions interface { GetToken() string GetMinQueryIndex() uint64 @@ -37,6 +39,8 @@ type RequestOptions interface { // ResponseMeta is an interface used to populate the response struct // with metadata about the query and the state of the server. +// +//go:generate mockery --name ResponseMeta --inpackage type ResponseMeta interface { SetLastContact(time.Duration) SetKnownLeader(bool) @@ -47,6 +51,8 @@ type ResponseMeta interface { // FSMServer is interface into the stateful components of a Consul server, such // as memdb or raft leadership. +// +//go:generate mockery --name FSMServer --inpackage type FSMServer interface { ConsistentRead() error DecrementBlockingQueries() uint64 diff --git a/agent/blockingquery/blockingquery_test.go b/agent/blockingquery/blockingquery_test.go index 5861ed3991..494cc41b42 100644 --- a/agent/blockingquery/blockingquery_test.go +++ b/agent/blockingquery/blockingquery_test.go @@ -3,5 +3,338 @@ package blockingquery -// TODO: move tests from the consul package, rpc_test.go, TestServer_blockingQuery -// here using mock for FSMServer w/ structs.QueryOptions and structs.QueryOptions +import ( + "fmt" + "github.com/hashicorp/consul/agent/consul/state" + "github.com/hashicorp/go-memdb" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" + "testing" + "time" +) + +func TestServer_blockingQuery(t *testing.T) { + t.Parallel() + getFSM := func(t *testing.T, additionalCfgFunc func(mockFSM *MockFSMServer)) *MockFSMServer { + fsm := NewMockFSMServer(t) + testCh := make(chan struct{}) + tombstoneGC, err := state.NewTombstoneGC(time.Second, time.Second) + require.NoError(t, err) + store := state.NewStateStore(tombstoneGC) + fsm.On("GetShutdownChannel").Return(testCh) + fsm.On("GetState").Return(store) + fsm.On("SetQueryMeta", mock.Anything, mock.Anything).Return(nil) + if additionalCfgFunc != nil { + additionalCfgFunc(fsm) + } + return fsm + } + + getOpts := func(t *testing.T, additionalCfgFunc func(options *MockRequestOptions)) *MockRequestOptions { + requestOpts := NewMockRequestOptions(t) + requestOpts.On("GetRequireConsistent").Return(false) + requestOpts.On("GetToken").Return("fake-token") + if additionalCfgFunc != nil { + additionalCfgFunc(requestOpts) + } + return requestOpts + } + + getMeta := func(t *testing.T, additionalCfgFunc func(mockMeta *MockResponseMeta)) *MockResponseMeta { + meta := NewMockResponseMeta(t) + if additionalCfgFunc != nil { + additionalCfgFunc(meta) + } + return meta + } + + // Perform a non-blocking query. Note that it's significant that the meta has + // a zero index in response - the implied opts.MinQueryIndex is also zero but + // this should not block still. + t.Run("non-blocking query", func(t *testing.T) { + var calls int + fn := func(_ memdb.WatchSet, _ *state.Store) error { + calls++ + return nil + } + err := Query(getFSM(t, nil), getOpts(t, func(mockOpts *MockRequestOptions) { + mockOpts.On("GetMinQueryIndex").Return(uint64(0)) + }), getMeta(t, nil), fn) + require.NoError(t, err) + require.Equal(t, 1, calls) + }) + + // Perform a blocking query that gets woken up and loops around once. + t.Run("blocking query - single loop", func(t *testing.T) { + opts := getOpts(t, func(options *MockRequestOptions) { + options.On("GetMinQueryIndex").Return(uint64(1)) + options.On("GetMaxQueryTime").Return(1*time.Second, nil) + }) + + meta := getMeta(t, func(mockMeta *MockResponseMeta) { + mockMeta.On("GetIndex").Return(uint64(1)) + }) + + fsm := getFSM(t, func(mockFSM *MockFSMServer) { + mockFSM.On("RPCQueryTimeout", mock.Anything).Return(1 * time.Second) + mockFSM.On("IncrementBlockingQueries").Return(uint64(1)) + mockFSM.On("DecrementBlockingQueries").Return(uint64(1)) + }) + + var calls int + fn := func(ws memdb.WatchSet, _ *state.Store) error { + if calls == 0 { + meta.On("GetIndex").Return(uint64(3)) + + fakeCh := make(chan struct{}) + close(fakeCh) + ws.Add(fakeCh) + } else { + meta.On("GetIndex").Return(uint64(4)) + } + calls++ + return nil + } + err := Query(fsm, opts, meta, fn) + require.NoError(t, err) + require.Equal(t, 2, calls) + }) + + // Perform a blocking query that returns a zero index from blocking func (e.g. + // no state yet). This should still return an empty response immediately, but + // with index of 1 and then block on the next attempt. In one sense zero index + // is not really a valid response from a state method that is not an error but + // in practice a lot of state store operations do return it unless they + // explicitly special checks to turn 0 into 1. Often this is not caught or + // covered by tests but eventually when hit in the wild causes blocking + // clients to busy loop and burn CPU. This test ensure that blockingQuery + // systematically does the right thing to prevent future bugs like that. + t.Run("blocking query with 0 modifyIndex from state func", func(t *testing.T) { + opts := getOpts(t, func(options *MockRequestOptions) { + options.On("GetMinQueryIndex").Return(uint64(0)) + }) + + meta := getMeta(t, func(mockMeta *MockResponseMeta) { + mockMeta.On("GetIndex").Return(uint64(1)) + }) + + fsm := getFSM(t, func(mockFSM *MockFSMServer) { + mockFSM.On("RPCQueryTimeout", mock.Anything).Return(1 * time.Second) + mockFSM.On("IncrementBlockingQueries").Return(uint64(1)) + mockFSM.On("DecrementBlockingQueries").Return(uint64(1)) + }) + var calls int + fn := func(ws memdb.WatchSet, _ *state.Store) error { + if opts.GetMinQueryIndex() > 0 { + // If client requested blocking, block forever. This is simulating + // waiting for the watched resource to be initialized/written to giving + // it a non-zero index. Note the timeout on the query options is relied + // on to stop the test taking forever. + fakeCh := make(chan struct{}) + ws.Add(fakeCh) + } + meta.On("GetIndex").Return(uint64(0)) + calls++ + return nil + } + err := Query(fsm, opts, meta, fn) + require.NoError(t, err) + require.Equal(t, 1, calls) + require.Equal(t, uint64(1), meta.GetIndex(), + "expect fake index of 1 to force client to block on next update") + + // Simulate client making next request + opts = getOpts(t, func(options *MockRequestOptions) { + options.On("GetMinQueryIndex").Return(uint64(1)) + options.On("GetMaxQueryTime").Return(20*time.Millisecond, nil) + }) + + // This time we should block even though the func returns index 0 still + t0 := time.Now() + require.NoError(t, Query(fsm, opts, meta, fn)) + t1 := time.Now() + require.Equal(t, 2, calls) + require.Equal(t, uint64(1), meta.GetIndex(), + "expect fake index of 1 to force client to block on next update") + require.True(t, t1.Sub(t0) > 20*time.Millisecond, + "should have actually blocked waiting for timeout") + + }) + + // Perform a query that blocks and gets interrupted when the state store + // is abandoned. + t.Run("blocking query interrupted by abandonCh", func(t *testing.T) { + opts := getOpts(t, func(options *MockRequestOptions) { + options.On("GetMinQueryIndex").Return(uint64(3)) + options.On("GetMaxQueryTime").Return(20*time.Millisecond, nil) + }) + + meta := getMeta(t, func(mockMeta *MockResponseMeta) { + mockMeta.On("GetIndex").Return(uint64(1)) + }) + + fsm := getFSM(t, func(mockFSM *MockFSMServer) { + mockFSM.On("RPCQueryTimeout", mock.Anything).Return(1 * time.Second) + mockFSM.On("IncrementBlockingQueries").Return(uint64(1)) + mockFSM.On("DecrementBlockingQueries").Return(uint64(1)) + }) + + var calls int + fn := func(_ memdb.WatchSet, _ *state.Store) error { + if calls == 0 { + meta.On("GetIndex").Return(uint64(1)) + + fsm.GetState().Abandon() + } + calls++ + return nil + } + err := Query(fsm, opts, meta, fn) + require.NoError(t, err) + require.Equal(t, 1, calls) + }) + + t.Run("non-blocking query for item that does not exist", func(t *testing.T) { + opts := getOpts(t, func(options *MockRequestOptions) { + options.On("GetMinQueryIndex").Return(uint64(3)) + options.On("GetMaxQueryTime").Return(20*time.Millisecond, nil) + }) + + meta := getMeta(t, func(mockMeta *MockResponseMeta) { + mockMeta.On("GetIndex").Return(uint64(1)) + }) + + fsm := getFSM(t, func(mockFSM *MockFSMServer) { + mockFSM.On("RPCQueryTimeout", mock.Anything).Return(1 * time.Second) + mockFSM.On("IncrementBlockingQueries").Return(uint64(1)) + mockFSM.On("DecrementBlockingQueries").Return(uint64(1)) + }) + calls := 0 + fn := func(_ memdb.WatchSet, _ *state.Store) error { + calls++ + return ErrNotFound + } + + err := Query(fsm, opts, meta, fn) + require.NoError(t, err) + require.Equal(t, 1, calls) + }) + + t.Run("blocking query for item that does not exist", func(t *testing.T) { + opts := getOpts(t, func(options *MockRequestOptions) { + options.On("GetMinQueryIndex").Return(uint64(3)) + options.On("GetMaxQueryTime").Return(100*time.Millisecond, nil) + }) + + meta := getMeta(t, func(mockMeta *MockResponseMeta) { + mockMeta.On("GetIndex").Return(uint64(1)) + }) + + fsm := getFSM(t, func(mockFSM *MockFSMServer) { + mockFSM.On("RPCQueryTimeout", mock.Anything).Return(1 * time.Second) + mockFSM.On("IncrementBlockingQueries").Return(uint64(1)) + mockFSM.On("DecrementBlockingQueries").Return(uint64(1)) + }) + calls := 0 + fn := func(ws memdb.WatchSet, _ *state.Store) error { + calls++ + if calls == 1 { + meta.On("GetIndex").Return(uint64(3)) + + ch := make(chan struct{}) + close(ch) + ws.Add(ch) + return ErrNotFound + } + meta.On("GetIndex").Return(uint64(5)) + return ErrNotFound + } + + err := Query(fsm, opts, meta, fn) + require.NoError(t, err) + require.Equal(t, 2, calls) + }) + + t.Run("blocking query for item that existed and is removed", func(t *testing.T) { + opts := getOpts(t, func(options *MockRequestOptions) { + options.On("GetMinQueryIndex").Return(uint64(3)) + // this query taks 1.002 sceonds locally so setting the timeout to 2 seconds + options.On("GetMaxQueryTime").Return(2*time.Second, nil) + }) + + meta := getMeta(t, func(mockMeta *MockResponseMeta) { + mockMeta.On("GetIndex").Return(uint64(3)) + }) + + fsm := getFSM(t, func(mockFSM *MockFSMServer) { + mockFSM.On("RPCQueryTimeout", mock.Anything).Return(1 * time.Second) + mockFSM.On("IncrementBlockingQueries").Return(uint64(1)) + mockFSM.On("DecrementBlockingQueries").Return(uint64(1)) + }) + calls := 0 + fn := func(ws memdb.WatchSet, _ *state.Store) error { + calls++ + if calls == 1 { + + ch := make(chan struct{}) + close(ch) + ws.Add(ch) + return nil + } + meta = getMeta(t, func(mockMeta *MockResponseMeta) { + meta.On("GetIndex").Return(uint64(5)) + }) + return ErrNotFound + } + + start := time.Now() + require.NoError(t, Query(fsm, opts, meta, fn)) + queryDuration := time.Since(start) + maxQueryDuration, err := opts.GetMaxQueryTime() + require.NoError(t, err) + require.True(t, queryDuration < maxQueryDuration, fmt.Sprintf("query timed out - queryDuration: %v, maxQueryDuration: %v", queryDuration, maxQueryDuration)) + require.NoError(t, err) + require.Equal(t, 2, calls) + }) + + t.Run("blocking query for non-existent item that is created", func(t *testing.T) { + opts := getOpts(t, func(options *MockRequestOptions) { + options.On("GetMinQueryIndex").Return(uint64(3)) + // this query taks 1.002 sceonds locally so setting the timeout to 2 seconds + options.On("GetMaxQueryTime").Return(2*time.Second, nil) + }) + + meta := getMeta(t, func(mockMeta *MockResponseMeta) { + mockMeta.On("GetIndex").Return(uint64(3)) + }) + + fsm := getFSM(t, func(mockFSM *MockFSMServer) { + mockFSM.On("RPCQueryTimeout", mock.Anything).Return(1 * time.Second) + mockFSM.On("IncrementBlockingQueries").Return(uint64(1)) + mockFSM.On("DecrementBlockingQueries").Return(uint64(1)) + }) + calls := 0 + fn := func(ws memdb.WatchSet, _ *state.Store) error { + calls++ + if calls == 1 { + ch := make(chan struct{}) + close(ch) + ws.Add(ch) + return ErrNotFound + } + meta = getMeta(t, func(mockMeta *MockResponseMeta) { + meta.On("GetIndex").Return(uint64(5)) + }) + return nil + } + + start := time.Now() + require.NoError(t, Query(fsm, opts, meta, fn)) + queryDuration := time.Since(start) + maxQueryDuration, err := opts.GetMaxQueryTime() + require.NoError(t, err) + require.True(t, queryDuration < maxQueryDuration, fmt.Sprintf("query timed out - queryDuration: %v, maxQueryDuration: %v", queryDuration, maxQueryDuration)) + require.NoError(t, err) + require.Equal(t, 2, calls) + }) +} diff --git a/agent/blockingquery/mock_FSMServer.go b/agent/blockingquery/mock_FSMServer.go new file mode 100644 index 0000000000..1e62f391b8 --- /dev/null +++ b/agent/blockingquery/mock_FSMServer.go @@ -0,0 +1,122 @@ +// Code generated by mockery v2.32.4. DO NOT EDIT. + +package blockingquery + +import ( + time "time" + + state "github.com/hashicorp/consul/agent/consul/state" + mock "github.com/stretchr/testify/mock" +) + +// MockFSMServer is an autogenerated mock type for the FSMServer type +type MockFSMServer struct { + mock.Mock +} + +// ConsistentRead provides a mock function with given fields: +func (_m *MockFSMServer) ConsistentRead() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// DecrementBlockingQueries provides a mock function with given fields: +func (_m *MockFSMServer) DecrementBlockingQueries() uint64 { + ret := _m.Called() + + var r0 uint64 + if rf, ok := ret.Get(0).(func() uint64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint64) + } + + return r0 +} + +// GetShutdownChannel provides a mock function with given fields: +func (_m *MockFSMServer) GetShutdownChannel() chan struct{} { + ret := _m.Called() + + var r0 chan struct{} + if rf, ok := ret.Get(0).(func() chan struct{}); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(chan struct{}) + } + } + + return r0 +} + +// GetState provides a mock function with given fields: +func (_m *MockFSMServer) GetState() *state.Store { + ret := _m.Called() + + var r0 *state.Store + if rf, ok := ret.Get(0).(func() *state.Store); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*state.Store) + } + } + + return r0 +} + +// IncrementBlockingQueries provides a mock function with given fields: +func (_m *MockFSMServer) IncrementBlockingQueries() uint64 { + ret := _m.Called() + + var r0 uint64 + if rf, ok := ret.Get(0).(func() uint64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint64) + } + + return r0 +} + +// RPCQueryTimeout provides a mock function with given fields: _a0 +func (_m *MockFSMServer) RPCQueryTimeout(_a0 time.Duration) time.Duration { + ret := _m.Called(_a0) + + var r0 time.Duration + if rf, ok := ret.Get(0).(func(time.Duration) time.Duration); ok { + r0 = rf(_a0) + } else { + r0 = ret.Get(0).(time.Duration) + } + + return r0 +} + +// SetQueryMeta provides a mock function with given fields: _a0, _a1 +func (_m *MockFSMServer) SetQueryMeta(_a0 ResponseMeta, _a1 string) { + _m.Called(_a0, _a1) +} + +// NewMockFSMServer creates a new instance of MockFSMServer. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockFSMServer(t interface { + mock.TestingT + Cleanup(func()) +}) *MockFSMServer { + mock := &MockFSMServer{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/agent/blockingquery/mock_RequestOptions.go b/agent/blockingquery/mock_RequestOptions.go new file mode 100644 index 0000000000..7e57c86b36 --- /dev/null +++ b/agent/blockingquery/mock_RequestOptions.go @@ -0,0 +1,94 @@ +// Code generated by mockery v2.32.4. DO NOT EDIT. + +package blockingquery + +import ( + time "time" + + mock "github.com/stretchr/testify/mock" +) + +// MockRequestOptions is an autogenerated mock type for the RequestOptions type +type MockRequestOptions struct { + mock.Mock +} + +// GetMaxQueryTime provides a mock function with given fields: +func (_m *MockRequestOptions) GetMaxQueryTime() (time.Duration, error) { + ret := _m.Called() + + var r0 time.Duration + var r1 error + if rf, ok := ret.Get(0).(func() (time.Duration, error)); ok { + return rf() + } + if rf, ok := ret.Get(0).(func() time.Duration); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(time.Duration) + } + + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetMinQueryIndex provides a mock function with given fields: +func (_m *MockRequestOptions) GetMinQueryIndex() uint64 { + ret := _m.Called() + + var r0 uint64 + if rf, ok := ret.Get(0).(func() uint64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint64) + } + + return r0 +} + +// GetRequireConsistent provides a mock function with given fields: +func (_m *MockRequestOptions) GetRequireConsistent() bool { + ret := _m.Called() + + var r0 bool + if rf, ok := ret.Get(0).(func() bool); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(bool) + } + + return r0 +} + +// GetToken provides a mock function with given fields: +func (_m *MockRequestOptions) GetToken() string { + ret := _m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + +// NewMockRequestOptions creates a new instance of MockRequestOptions. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockRequestOptions(t interface { + mock.TestingT + Cleanup(func()) +}) *MockRequestOptions { + mock := &MockRequestOptions{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/agent/blockingquery/mock_ResponseMeta.go b/agent/blockingquery/mock_ResponseMeta.go new file mode 100644 index 0000000000..c038b4bcd5 --- /dev/null +++ b/agent/blockingquery/mock_ResponseMeta.go @@ -0,0 +1,62 @@ +// Code generated by mockery v2.32.4. DO NOT EDIT. + +package blockingquery + +import ( + time "time" + + mock "github.com/stretchr/testify/mock" +) + +// MockResponseMeta is an autogenerated mock type for the ResponseMeta type +type MockResponseMeta struct { + mock.Mock +} + +// GetIndex provides a mock function with given fields: +func (_m *MockResponseMeta) GetIndex() uint64 { + ret := _m.Called() + + var r0 uint64 + if rf, ok := ret.Get(0).(func() uint64); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(uint64) + } + + return r0 +} + +// SetIndex provides a mock function with given fields: _a0 +func (_m *MockResponseMeta) SetIndex(_a0 uint64) { + _m.Called(_a0) +} + +// SetKnownLeader provides a mock function with given fields: _a0 +func (_m *MockResponseMeta) SetKnownLeader(_a0 bool) { + _m.Called(_a0) +} + +// SetLastContact provides a mock function with given fields: _a0 +func (_m *MockResponseMeta) SetLastContact(_a0 time.Duration) { + _m.Called(_a0) +} + +// SetResultsFilteredByACLs provides a mock function with given fields: _a0 +func (_m *MockResponseMeta) SetResultsFilteredByACLs(_a0 bool) { + _m.Called(_a0) +} + +// NewMockResponseMeta creates a new instance of MockResponseMeta. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewMockResponseMeta(t interface { + mock.TestingT + Cleanup(func()) +}) *MockResponseMeta { + mock := &MockResponseMeta{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/agent/catalog_endpoint_test.go b/agent/catalog_endpoint_test.go index 5a5b2433d5..c06efc748c 100644 --- a/agent/catalog_endpoint_test.go +++ b/agent/catalog_endpoint_test.go @@ -6,18 +6,17 @@ package agent import ( "context" "fmt" - "io" "net/http" "net/http/httptest" "net/url" - "strings" "testing" "time" - "github.com/hashicorp/serf/coordinate" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/hashicorp/serf/coordinate" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" @@ -25,47 +24,10 @@ import ( "github.com/hashicorp/consul/testrpc" ) -func TestCatalogEndpointsFailInV2(t *testing.T) { - t.Parallel() - - a := NewTestAgent(t, `experiments = ["resource-apis"]`) - - checkRequest := func(method, url string) { - t.Run(method+" "+url, func(t *testing.T) { - assertV1CatalogEndpointDoesNotWorkWithV2(t, a, method, url, "{}") - }) - } - - checkRequest("PUT", "/v1/catalog/register") - checkRequest("GET", "/v1/catalog/connect/") - checkRequest("PUT", "/v1/catalog/deregister") - checkRequest("GET", "/v1/catalog/datacenters") - checkRequest("GET", "/v1/catalog/nodes") - checkRequest("GET", "/v1/catalog/services") - checkRequest("GET", "/v1/catalog/service/") - checkRequest("GET", "/v1/catalog/node/") - checkRequest("GET", "/v1/catalog/node-services/") - checkRequest("GET", "/v1/catalog/gateway-services/") -} - -func assertV1CatalogEndpointDoesNotWorkWithV2(t *testing.T, a *TestAgent, method, url string, requestBody string) { - var body io.Reader - switch method { - case http.MethodPost, http.MethodPut: - body = strings.NewReader(requestBody + "\n") - } - - req, err := http.NewRequest(method, url, body) - require.NoError(t, err) - - resp := httptest.NewRecorder() - a.srv.h.ServeHTTP(resp, req) - require.Equal(t, http.StatusBadRequest, resp.Code) - - got, err := io.ReadAll(resp.Body) - require.NoError(t, err) - - require.Contains(t, string(got), structs.ErrUsingV2CatalogExperiment.Error()) +func addQueryParam(req *http.Request, param, value string) { + q := req.URL.Query() + q.Add(param, value) + req.URL.RawQuery = q.Encode() } func TestCatalogRegister_PeeringRegistration(t *testing.T) { diff --git a/agent/checks/alias.go b/agent/checks/alias.go index f75c05b958..200176364e 100644 --- a/agent/checks/alias.go +++ b/agent/checks/alias.go @@ -144,6 +144,9 @@ func (c *CheckAlias) runLocal(stopCh chan struct{}) { type CheckIfServiceIDExists func(*structs.ServiceID) bool func (c *CheckAlias) checkServiceExistsOnRemoteServer(serviceID *structs.ServiceID) (bool, error) { + if serviceID == nil { + return false, fmt.Errorf("serviceID cannot be nil") + } args := c.RPCReq args.Node = c.Node args.AllowStale = true @@ -161,6 +164,12 @@ RETRY_CALL: } return false, err } + + // Do not proceed for nil returned services. + if out.NodeServices == nil { + return false, fmt.Errorf("no services found on node") + } + for _, srv := range out.NodeServices.Services { if serviceID.Matches(srv.CompoundServiceID()) { return true, nil diff --git a/agent/config/builder.go b/agent/config/builder.go index 64e9120fde..5e8b5c215f 100644 --- a/agent/config/builder.go +++ b/agent/config/builder.go @@ -22,12 +22,13 @@ import ( "time" "github.com/armon/go-metrics/prometheus" + "golang.org/x/time/rate" + "github.com/hashicorp/go-bexpr" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-multierror" "github.com/hashicorp/go-sockaddr/template" "github.com/hashicorp/memberlist" - "golang.org/x/time/rate" "github.com/hashicorp/consul/agent/cache" "github.com/hashicorp/consul/agent/checks" @@ -774,6 +775,7 @@ func (b *builder) build() (rt RuntimeConfig, err error) { if err != nil { return RuntimeConfig{}, fmt.Errorf("config_entries.bootstrap[%d]: %s", i, err) } + // Ensure Normalize is called before Validate for accurate validation if err := entry.Normalize(); err != nil { return RuntimeConfig{}, fmt.Errorf("config_entries.bootstrap[%d]: %s", i, err) } @@ -1071,6 +1073,7 @@ func (b *builder) build() (rt RuntimeConfig, err error) { RaftSnapshotThreshold: intVal(c.RaftSnapshotThreshold), RaftSnapshotInterval: b.durationVal("raft_snapshot_interval", c.RaftSnapshotInterval), RaftTrailingLogs: intVal(c.RaftTrailingLogs), + RaftPreVoteDisabled: boolVal(c.RaftPreVoteDisabled), RaftLogStoreConfig: b.raftLogStoreConfigVal(&c.RaftLogStore), ReconnectTimeoutLAN: b.durationVal("reconnect_timeout", c.ReconnectTimeoutLAN), ReconnectTimeoutWAN: b.durationVal("reconnect_timeout_wan", c.ReconnectTimeoutWAN), @@ -1142,23 +1145,6 @@ func (b *builder) build() (rt RuntimeConfig, err error) { return RuntimeConfig{}, fmt.Errorf("cache.entry_fetch_rate must be strictly positive, was: %v", rt.Cache.EntryFetchRate) } - // TODO(CC-6389): Remove once resource-apis is no longer considered experimental and is supported by HCP - if stringslice.Contains(rt.Experiments, consul.CatalogResourceExperimentName) && rt.IsCloudEnabled() { - // Allow override of this check for development/testing purposes. Should not be used in production - if !stringslice.Contains(rt.Experiments, consul.HCPAllowV2ResourceAPIs) { - return RuntimeConfig{}, fmt.Errorf("`experiments` cannot include 'resource-apis' when HCP `cloud` configuration is set") - } - } - - // For now, disallow usage of several v2 experiments in secondary datacenters. - if rt.ServerMode && rt.PrimaryDatacenter != rt.Datacenter { - for _, name := range rt.Experiments { - if !consul.IsExperimentAllowedOnSecondaries(name) { - return RuntimeConfig{}, fmt.Errorf("`experiments` cannot include `%s` for servers in secondary datacenters", name) - } - } - } - if rt.UIConfig.MetricsProvider == "prometheus" { // Handle defaulting for the built-in version of prometheus. if len(rt.UIConfig.MetricsProxy.PathAllowlist) == 0 { diff --git a/agent/config/builder_test.go b/agent/config/builder_test.go index 488fa9c7e0..26d20bdfba 100644 --- a/agent/config/builder_test.go +++ b/agent/config/builder_test.go @@ -615,29 +615,9 @@ func TestBuilder_CheckExperimentsInSecondaryDatacenters(t *testing.T) { "primary server no experiments": { hcl: primary + `experiments = []`, }, - "primary server v2catalog": { - hcl: primary + `experiments = ["resource-apis"]`, - }, - "primary server v1dns": { - hcl: primary + `experiments = ["v1dns"]`, - }, - "primary server v2tenancy": { - hcl: primary + `experiments = ["v2tenancy"]`, - }, "secondary server no experiments": { hcl: secondary + `experiments = []`, }, - "secondary server v2catalog": { - hcl: secondary + `experiments = ["resource-apis"]`, - expectErr: true, - }, - "secondary server v1dns": { - hcl: secondary + `experiments = ["v1dns"]`, - }, - "secondary server v2tenancy": { - hcl: secondary + `experiments = ["v2tenancy"]`, - expectErr: true, - }, } for name, tc := range cases { @@ -647,67 +627,6 @@ func TestBuilder_CheckExperimentsInSecondaryDatacenters(t *testing.T) { } } -func TestBuilder_WarnCloudConfigWithResourceApis(t *testing.T) { - tests := []struct { - name string - hcl string - expectErr bool - }{ - { - name: "base_case", - hcl: ``, - }, - { - name: "resource-apis_no_cloud", - hcl: `experiments = ["resource-apis"]`, - }, - { - name: "cloud-config_no_experiments", - hcl: `cloud{ resource_id = "abc" client_id = "abc" client_secret = "abc"}`, - }, - { - name: "cloud-config_resource-apis_experiment", - hcl: ` - experiments = ["resource-apis"] - cloud{ resource_id = "abc" client_id = "abc" client_secret = "abc"}`, - expectErr: true, - }, - { - name: "cloud-config_other_experiment", - hcl: ` - experiments = ["test"] - cloud{ resource_id = "abc" client_id = "abc" client_secret = "abc"}`, - }, - { - name: "cloud-config_resource-apis_experiment_override", - hcl: ` - experiments = ["resource-apis", "hcp-v2-resource-apis"] - cloud{ resource_id = "abc" client_id = "abc" client_secret = "abc"}`, - }, - } - for _, tc := range tests { - // using dev mode skips the need for a data dir - devMode := true - builderOpts := LoadOpts{ - DevMode: &devMode, - Overrides: []Source{ - FileSource{ - Name: "overrides", - Format: "hcl", - Data: tc.hcl, - }, - }, - } - _, err := Load(builderOpts) - if tc.expectErr { - require.Error(t, err) - require.Contains(t, err.Error(), "cannot include 'resource-apis' when HCP") - } else { - require.NoError(t, err) - } - } -} - func TestBuilder_CloudConfigWithEnvironmentVars(t *testing.T) { tests := map[string]struct { hcl string diff --git a/agent/config/config.deepcopy.go b/agent/config/config.deepcopy.go index 2a5ebfce27..bd9b85fca4 100644 --- a/agent/config/config.deepcopy.go +++ b/agent/config/config.deepcopy.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -// generated by deep-copy -pointer-receiver -o ./config.deepcopy.go -type RuntimeConfig ./; DO NOT EDIT. +// Code generated by deep-copy -pointer-receiver -o ./config.deepcopy.go -type RuntimeConfig ./; DO NOT EDIT. package config @@ -359,6 +356,10 @@ func (o *RuntimeConfig) DeepCopy() *RuntimeConfig { } } } + if o.Cloud.TLSConfig.Certificates[i5].Leaf.Policies != nil { + cp.Cloud.TLSConfig.Certificates[i5].Leaf.Policies = make([]x509.OID, len(o.Cloud.TLSConfig.Certificates[i5].Leaf.Policies)) + copy(cp.Cloud.TLSConfig.Certificates[i5].Leaf.Policies, o.Cloud.TLSConfig.Certificates[i5].Leaf.Policies) + } } } } @@ -698,6 +699,10 @@ func (o *RuntimeConfig) DeepCopy() *RuntimeConfig { } } } + if v5.Leaf.Policies != nil { + cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Policies = make([]x509.OID, len(v5.Leaf.Policies)) + copy(cp_Cloud_TLSConfig_NameToCertificate_v5.Leaf.Policies, v5.Leaf.Policies) + } } } cp.Cloud.TLSConfig.NameToCertificate[k5] = cp_Cloud_TLSConfig_NameToCertificate_v5 diff --git a/agent/config/config.go b/agent/config/config.go index 3201f790a7..4a93c27020 100644 --- a/agent/config/config.go +++ b/agent/config/config.go @@ -8,9 +8,10 @@ import ( "fmt" "time" - "github.com/hashicorp/hcl" "github.com/mitchellh/mapstructure" + "github.com/hashicorp/hcl" + "github.com/hashicorp/consul/lib/decode" ) @@ -214,6 +215,7 @@ type Config struct { RaftSnapshotThreshold *int `mapstructure:"raft_snapshot_threshold" json:"raft_snapshot_threshold,omitempty"` RaftSnapshotInterval *string `mapstructure:"raft_snapshot_interval" json:"raft_snapshot_interval,omitempty"` RaftTrailingLogs *int `mapstructure:"raft_trailing_logs" json:"raft_trailing_logs,omitempty"` + RaftPreVoteDisabled *bool `mapstructure:"raft_prevote_disabled" json:"raft_prevote_disabled,omitempty"` ReconnectTimeoutLAN *string `mapstructure:"reconnect_timeout" json:"reconnect_timeout,omitempty"` ReconnectTimeoutWAN *string `mapstructure:"reconnect_timeout_wan" json:"reconnect_timeout_wan,omitempty"` RejoinAfterLeave *bool `mapstructure:"rejoin_after_leave" json:"rejoin_after_leave,omitempty"` diff --git a/agent/config/default.go b/agent/config/default.go index f07a8bdf46..1b98056aa1 100644 --- a/agent/config/default.go +++ b/agent/config/default.go @@ -146,6 +146,7 @@ func DefaultSource() Source { raft_snapshot_threshold = ` + strconv.Itoa(int(cfg.RaftConfig.SnapshotThreshold)) + ` raft_snapshot_interval = "` + cfg.RaftConfig.SnapshotInterval.String() + `" raft_trailing_logs = ` + strconv.Itoa(int(cfg.RaftConfig.TrailingLogs)) + ` + raft_prevote_disabled = false raft_logstore { wal { segment_size_mb = 64 diff --git a/agent/config/runtime.go b/agent/config/runtime.go index 2ac7ea19d9..e31047354d 100644 --- a/agent/config/runtime.go +++ b/agent/config/runtime.go @@ -10,9 +10,10 @@ import ( "strings" "time" - "github.com/hashicorp/go-uuid" "golang.org/x/time/rate" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/consul/agent/cache" "github.com/hashicorp/consul/agent/consul" consulrate "github.com/hashicorp/consul/agent/consul/rate" @@ -1004,6 +1005,9 @@ type RuntimeConfig struct { // hcl: raft_trailing_logs = int RaftTrailingLogs int + // hcl: raft_prevote_disabled = bool + RaftPreVoteDisabled bool + RaftLogStoreConfig consul.RaftLogStoreConfig // ReconnectTimeoutLAN specifies the amount of time to wait to reconnect with diff --git a/agent/config/runtime_test.go b/agent/config/runtime_test.go index 257f320a55..549429bf86 100644 --- a/agent/config/runtime_test.go +++ b/agent/config/runtime_test.go @@ -6015,24 +6015,6 @@ func TestLoad_IntegrationWithFlags(t *testing.T) { rt.RaftLogStoreConfig.WAL.SegmentSize = 64 * 1024 * 1024 }, }) - run(t, testCase{ - desc: "logstore defaults", - args: []string{ - `-data-dir=` + dataDir, - }, - json: []string{` - { - "experiments": ["resource-apis"] - } - `}, - hcl: []string{`experiments=["resource-apis"]`}, - expected: func(rt *RuntimeConfig) { - rt.DataDir = dataDir - rt.RaftLogStoreConfig.Backend = consul.LogStoreBackendDefault - rt.RaftLogStoreConfig.WAL.SegmentSize = 64 * 1024 * 1024 - rt.Experiments = []string{"resource-apis"} - }, - }) run(t, testCase{ // this was a bug in the initial config commit. Specifying part of this // stanza should still result in sensible defaults for the other parts. @@ -6612,6 +6594,7 @@ func TestLoad_FullConfig(t *testing.T) { RaftSnapshotThreshold: 16384, RaftSnapshotInterval: 30 * time.Second, RaftTrailingLogs: 83749, + RaftPreVoteDisabled: false, ReconnectTimeoutLAN: 23739 * time.Second, ReconnectTimeoutWAN: 26694 * time.Second, RequestLimitsMode: consulrate.ModePermissive, diff --git a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden index 5ead1e2f76..53e533b246 100644 --- a/agent/config/testdata/TestRuntimeConfig_Sanitize.golden +++ b/agent/config/testdata/TestRuntimeConfig_Sanitize.golden @@ -71,6 +71,7 @@ "AutopilotDisableUpgradeMigration": false, "AutopilotLastContactThreshold": "0s", "AutopilotMaxTrailingLogs": 0, + "AutopilotMinQuorum": 0, "AutopilotRedundancyZoneTag": "", "AutopilotServerStabilizationTime": "0s", @@ -299,6 +300,7 @@ "RaftSnapshotInterval": "0s", "RaftSnapshotThreshold": 0, "RaftTrailingLogs": 0, + "RaftPreVoteDisabled": false, "ReadReplica": false, "ReconnectTimeoutLAN": "0s", "ReconnectTimeoutWAN": "0s", diff --git a/agent/config_endpoint_test.go b/agent/config_endpoint_test.go index 8697b55e5b..42de720c79 100644 --- a/agent/config_endpoint_test.go +++ b/agent/config_endpoint_test.go @@ -20,23 +20,6 @@ import ( "github.com/hashicorp/consul/testrpc" ) -func TestConfigEndpointsFailInV2(t *testing.T) { - t.Parallel() - - a := NewTestAgent(t, `experiments = ["resource-apis"]`) - - checkRequest := func(method, url string) { - t.Run(method+" "+url, func(t *testing.T) { - assertV1CatalogEndpointDoesNotWorkWithV2(t, a, method, url, `{"kind":"service-defaults", "name":"web"}`) - }) - } - - checkRequest("GET", "/v1/config/service-defaults") - checkRequest("GET", "/v1/config/service-defaults/web") - checkRequest("DELETE", "/v1/config/service-defaults/web") - checkRequest("PUT", "/v1/config") -} - func TestConfig_Get(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") @@ -612,7 +595,7 @@ func TestConfig_Apply_CAS(t *testing.T) { { "Kind": "service-defaults", "Name": "foo", - "Protocol": "udp" + "Protocol": "http" } `)) req, _ = http.NewRequest("PUT", "/v1/config?cas=0", body) @@ -628,7 +611,7 @@ func TestConfig_Apply_CAS(t *testing.T) { { "Kind": "service-defaults", "Name": "foo", - "Protocol": "udp" + "Protocol": "http" } `)) req, _ = http.NewRequest("PUT", fmt.Sprintf("/v1/config?cas=%d", entry.GetRaftIndex().ModifyIndex), body) diff --git a/agent/connect/uri.go b/agent/connect/uri.go index bc898f7865..d9d5aa037d 100644 --- a/agent/connect/uri.go +++ b/agent/connect/uri.go @@ -23,8 +23,6 @@ type CertURI interface { } var ( - spiffeIDWorkloadIdentityRegexp = regexp.MustCompile( - `^(?:/ap/([^/]+))/ns/([^/]+)/identity/([^/]+)$`) spiffeIDServiceRegexp = regexp.MustCompile( `^(?:/ap/([^/]+))?/ns/([^/]+)/dc/([^/]+)/svc/([^/]+)$`) spiffeIDAgentRegexp = regexp.MustCompile( @@ -96,32 +94,6 @@ func ParseCertURI(input *url.URL) (CertURI, error) { Datacenter: dc, Service: service, }, nil - } else if v := spiffeIDWorkloadIdentityRegexp.FindStringSubmatch(path); v != nil { - // Determine the values. We assume they're reasonable to save cycles, - // but if the raw path is not empty that means that something is - // URL encoded so we go to the slow path. - ap := v[1] - ns := v[2] - workloadIdentity := v[3] - if input.RawPath != "" { - var err error - if ap, err = url.PathUnescape(v[1]); err != nil { - return nil, fmt.Errorf("Invalid admin partition: %s", err) - } - if ns, err = url.PathUnescape(v[2]); err != nil { - return nil, fmt.Errorf("Invalid namespace: %s", err) - } - if workloadIdentity, err = url.PathUnescape(v[3]); err != nil { - return nil, fmt.Errorf("Invalid workload identity: %s", err) - } - } - - return &SpiffeIDWorkloadIdentity{ - TrustDomain: input.Host, - Partition: ap, - Namespace: ns, - WorkloadIdentity: workloadIdentity, - }, nil } else if v := spiffeIDAgentRegexp.FindStringSubmatch(path); v != nil { // Determine the values. We assume they're reasonable to save cycles, // but if the raw path is not empty that means that something is diff --git a/agent/connect/uri_service.go b/agent/connect/uri_service.go index b35d1e0df4..3be7cf4797 100644 --- a/agent/connect/uri_service.go +++ b/agent/connect/uri_service.go @@ -8,7 +8,6 @@ import ( "net/url" "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/proto-public/pbresource" ) // SpiffeIDService is the structure to represent the SPIFFE ID for a service. @@ -53,14 +52,3 @@ func (id SpiffeIDService) uriPath() string { } return path } - -// SpiffeIDFromIdentityRef creates the SPIFFE ID from a workload identity. -// TODO (ishustava): make sure ref type is workload identity. -func SpiffeIDFromIdentityRef(trustDomain string, ref *pbresource.Reference) string { - return SpiffeIDWorkloadIdentity{ - TrustDomain: trustDomain, - Partition: ref.Tenancy.Partition, - Namespace: ref.Tenancy.Namespace, - WorkloadIdentity: ref.Name, - }.URI().String() -} diff --git a/agent/connect/uri_signing.go b/agent/connect/uri_signing.go index 1913ae6bdf..9f2807d2ba 100644 --- a/agent/connect/uri_signing.go +++ b/agent/connect/uri_signing.go @@ -51,12 +51,6 @@ func (id SpiffeIDSigning) CanSign(cu CertURI) bool { // worry about Unicode domains if we start allowing customisation beyond the // built-in cluster ids. return strings.ToLower(other.Host) == id.Host() - case *SpiffeIDWorkloadIdentity: - // The trust domain component of the workload identity SPIFFE ID must be an exact match for now under - // ascii case folding (since hostnames are case-insensitive). Later we might - // worry about Unicode domains if we start allowing customisation beyond the - // built-in cluster ids. - return strings.ToLower(other.TrustDomain) == id.Host() case *SpiffeIDMeshGateway: // The host component of the mesh gateway SPIFFE ID must be an exact match for now under // ascii case folding (since hostnames are case-insensitive). Later we might diff --git a/agent/connect/uri_signing_test.go b/agent/connect/uri_signing_test.go index 737ca46054..edd3d46893 100644 --- a/agent/connect/uri_signing_test.go +++ b/agent/connect/uri_signing_test.go @@ -98,30 +98,6 @@ func TestSpiffeIDSigning_CanSign(t *testing.T) { input: &SpiffeIDService{Host: TestClusterID + ".fake", Namespace: "default", Datacenter: "dc1", Service: "web"}, want: false, }, - { - name: "workload - good", - id: testSigning, - input: &SpiffeIDWorkloadIdentity{TrustDomain: TestClusterID + ".consul", Namespace: "default", WorkloadIdentity: "web"}, - want: true, - }, - { - name: "workload - good mixed case", - id: testSigning, - input: &SpiffeIDWorkloadIdentity{TrustDomain: strings.ToUpper(TestClusterID) + ".CONsuL", Namespace: "defAUlt", WorkloadIdentity: "WEB"}, - want: true, - }, - { - name: "workload - different cluster", - id: testSigning, - input: &SpiffeIDWorkloadIdentity{TrustDomain: "55555555-4444-3333-2222-111111111111.consul", Namespace: "default", WorkloadIdentity: "web"}, - want: false, - }, - { - name: "workload - different TLD", - id: testSigning, - input: &SpiffeIDWorkloadIdentity{TrustDomain: TestClusterID + ".fake", Namespace: "default", WorkloadIdentity: "web"}, - want: false, - }, { name: "mesh gateway - good", id: testSigning, diff --git a/agent/connect/uri_test.go b/agent/connect/uri_test.go index 5211684597..fcbcf42ab3 100644 --- a/agent/connect/uri_test.go +++ b/agent/connect/uri_test.go @@ -51,61 +51,6 @@ func TestParseCertURIFromString(t *testing.T) { }, ParseError: "", }, - { - Name: "basic workload ID", - URI: "spiffe://1234.consul/ap/default/ns/default/identity/web", - Struct: &SpiffeIDWorkloadIdentity{ - TrustDomain: "1234.consul", - Partition: defaultEntMeta.PartitionOrDefault(), - Namespace: "default", - WorkloadIdentity: "web", - }, - ParseError: "", - }, - { - Name: "basic workload ID with nondefault partition", - URI: "spiffe://1234.consul/ap/bizdev/ns/default/identity/web", - Struct: &SpiffeIDWorkloadIdentity{ - TrustDomain: "1234.consul", - Partition: "bizdev", - Namespace: "default", - WorkloadIdentity: "web", - }, - ParseError: "", - }, - { - Name: "workload ID error - missing identity", - URI: "spiffe://1234.consul/ns/default", - Struct: &SpiffeIDWorkloadIdentity{ - TrustDomain: "1234.consul", - Partition: defaultEntMeta.PartitionOrDefault(), - Namespace: "default", - WorkloadIdentity: "web", - }, - ParseError: "SPIFFE ID is not in the expected format", - }, - { - Name: "workload ID error - missing partition", - URI: "spiffe://1234.consul/ns/default/identity/web", - Struct: &SpiffeIDWorkloadIdentity{ - TrustDomain: "1234.consul", - Partition: defaultEntMeta.PartitionOrDefault(), - Namespace: "default", - WorkloadIdentity: "web", - }, - ParseError: "SPIFFE ID is not in the expected format", - }, - { - Name: "workload ID error - missing namespace", - URI: "spiffe://1234.consul/ap/default/identity/web", - Struct: &SpiffeIDWorkloadIdentity{ - TrustDomain: "1234.consul", - Partition: defaultEntMeta.PartitionOrDefault(), - Namespace: "default", - WorkloadIdentity: "web", - }, - ParseError: "SPIFFE ID is not in the expected format", - }, { Name: "basic agent ID", URI: "spiffe://1234.consul/agent/client/dc/dc1/id/uuid", diff --git a/agent/connect/uri_workload_identity.go b/agent/connect/uri_workload_identity.go deleted file mode 100644 index 83e022bde6..0000000000 --- a/agent/connect/uri_workload_identity.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package connect - -import ( - "fmt" - "net/url" -) - -// SpiffeIDWorkloadIdentity is the structure to represent the SPIFFE ID for a workload. -type SpiffeIDWorkloadIdentity struct { - TrustDomain string - Partition string - Namespace string - WorkloadIdentity string -} - -// URI returns the *url.URL for this SPIFFE ID. -func (id SpiffeIDWorkloadIdentity) URI() *url.URL { - var result url.URL - result.Scheme = "spiffe" - result.Host = id.TrustDomain - result.Path = id.uriPath() - return &result -} - -func (id SpiffeIDWorkloadIdentity) uriPath() string { - // Although CE has no support for partitions, it still needs to be able to - // handle exportedPartition from peered Consul Enterprise clusters in order - // to generate the correct SpiffeID. - // We intentionally avoid using pbpartition.DefaultName here to be CE friendly. - path := fmt.Sprintf("/ap/%s/ns/%s/identity/%s", - id.Partition, - id.Namespace, - id.WorkloadIdentity, - ) - - return path -} diff --git a/agent/connect/uri_workload_identity_ce.go b/agent/connect/uri_workload_identity_ce.go deleted file mode 100644 index 0350561634..0000000000 --- a/agent/connect/uri_workload_identity_ce.go +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -//go:build !consulent - -package connect - -import ( - "github.com/hashicorp/consul/acl" -) - -// TODO: this will need to somehow be updated to set namespace here when we include namespaces in CE - -// GetEnterpriseMeta will synthesize an EnterpriseMeta struct from the SpiffeIDWorkloadIdentity. -// in CE this just returns an empty (but never nil) struct pointer -func (id SpiffeIDWorkloadIdentity) GetEnterpriseMeta() *acl.EnterpriseMeta { - return &acl.EnterpriseMeta{} -} diff --git a/agent/connect/uri_workload_identity_test.go b/agent/connect/uri_workload_identity_test.go deleted file mode 100644 index 94beb80f58..0000000000 --- a/agent/connect/uri_workload_identity_test.go +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package connect - -import ( - "testing" - - "github.com/stretchr/testify/require" -) - -func TestSpiffeIDWorkloadURI(t *testing.T) { - t.Run("spiffe id workload uri default tenancy", func(t *testing.T) { - wl := &SpiffeIDWorkloadIdentity{ - TrustDomain: "1234.consul", - WorkloadIdentity: "web", - Partition: "default", - Namespace: "default", - } - require.Equal(t, "spiffe://1234.consul/ap/default/ns/default/identity/web", wl.URI().String()) - }) - t.Run("spiffe id workload uri non-default tenancy", func(t *testing.T) { - wl := &SpiffeIDWorkloadIdentity{ - TrustDomain: "1234.consul", - WorkloadIdentity: "web", - Partition: "part1", - Namespace: "dev", - } - require.Equal(t, "spiffe://1234.consul/ap/part1/ns/dev/identity/web", wl.URI().String()) - }) -} diff --git a/agent/consul/catalog_endpoint_test.go b/agent/consul/catalog_endpoint_test.go index d1fb2a6e97..18827dc981 100644 --- a/agent/consul/catalog_endpoint_test.go +++ b/agent/consul/catalog_endpoint_test.go @@ -10,19 +10,18 @@ import ( "testing" "time" - "github.com/hashicorp/go-uuid" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" "github.com/hashicorp/consul-net-rpc/net/rpc" + "github.com/hashicorp/go-uuid" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl/resolver" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/lib/stringslice" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" @@ -1234,9 +1233,9 @@ func TestCatalog_ListNodes_DistanceSort(t *testing.T) { // Set all but one of the nodes to known coordinates. updates := structs.Coordinates{ - {Node: "foo", Coord: lib.GenerateCoordinate(2 * time.Millisecond)}, - {Node: "bar", Coord: lib.GenerateCoordinate(5 * time.Millisecond)}, - {Node: "baz", Coord: lib.GenerateCoordinate(1 * time.Millisecond)}, + {Node: "foo", Coord: librtt.GenerateCoordinate(2 * time.Millisecond)}, + {Node: "bar", Coord: librtt.GenerateCoordinate(5 * time.Millisecond)}, + {Node: "baz", Coord: librtt.GenerateCoordinate(1 * time.Millisecond)}, } if err := s1.fsm.State().CoordinateBatchUpdate(5, updates); err != nil { t.Fatalf("err: %v", err) @@ -2138,9 +2137,9 @@ func TestCatalog_ListServiceNodes_DistanceSort(t *testing.T) { // Set all but one of the nodes to known coordinates. updates := structs.Coordinates{ - {Node: "foo", Coord: lib.GenerateCoordinate(2 * time.Millisecond)}, - {Node: "bar", Coord: lib.GenerateCoordinate(5 * time.Millisecond)}, - {Node: "baz", Coord: lib.GenerateCoordinate(1 * time.Millisecond)}, + {Node: "foo", Coord: librtt.GenerateCoordinate(2 * time.Millisecond)}, + {Node: "bar", Coord: librtt.GenerateCoordinate(5 * time.Millisecond)}, + {Node: "baz", Coord: librtt.GenerateCoordinate(1 * time.Millisecond)}, } if err := s1.fsm.State().CoordinateBatchUpdate(9, updates); err != nil { t.Fatalf("err: %v", err) diff --git a/agent/consul/client.go b/agent/consul/client.go index f47a2871d1..3632828794 100644 --- a/agent/consul/client.go +++ b/agent/consul/client.go @@ -14,15 +14,17 @@ import ( "github.com/armon/go-metrics" "github.com/armon/go-metrics/prometheus" + "golang.org/x/time/rate" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/serf/serf" - "golang.org/x/time/rate" "github.com/hashicorp/consul/acl" rpcRate "github.com/hashicorp/consul/agent/consul/rate" "github.com/hashicorp/consul/agent/pool" "github.com/hashicorp/consul/agent/router" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/logging" "github.com/hashicorp/consul/proto-public/pbresource" @@ -439,13 +441,13 @@ func (c *Client) Stats() map[string]map[string]string { // are ancillary members of. // // NOTE: This assumes coordinates are enabled, so check that before calling. -func (c *Client) GetLANCoordinate() (lib.CoordinateSet, error) { +func (c *Client) GetLANCoordinate() (librtt.CoordinateSet, error) { lan, err := c.serf.GetCoordinate() if err != nil { return nil, err } - cs := lib.CoordinateSet{c.config.Segment: lan} + cs := librtt.CoordinateSet{c.config.Segment: lan} return cs, nil } diff --git a/agent/consul/client_serf.go b/agent/consul/client_serf.go index c92fdd1726..a6f479c04e 100644 --- a/agent/consul/client_serf.go +++ b/agent/consul/client_serf.go @@ -12,8 +12,8 @@ import ( "github.com/hashicorp/serf/serf" "github.com/hashicorp/consul/agent/metadata" + "github.com/hashicorp/consul/internal/gossip/libserf" "github.com/hashicorp/consul/lib" - libserf "github.com/hashicorp/consul/lib/serf" "github.com/hashicorp/consul/logging" "github.com/hashicorp/consul/types" ) diff --git a/agent/consul/config.go b/agent/consul/config.go index 03fb588eb0..1cba0f4ef1 100644 --- a/agent/consul/config.go +++ b/agent/consul/config.go @@ -9,16 +9,17 @@ import ( "os" "time" + "golang.org/x/time/rate" + "github.com/hashicorp/memberlist" "github.com/hashicorp/raft" "github.com/hashicorp/serf/serf" - "golang.org/x/time/rate" "github.com/hashicorp/consul/agent/checks" consulrate "github.com/hashicorp/consul/agent/consul/rate" hcpconfig "github.com/hashicorp/consul/agent/hcp/config" "github.com/hashicorp/consul/agent/structs" - libserf "github.com/hashicorp/consul/lib/serf" + "github.com/hashicorp/consul/internal/gossip/libserf" "github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/consul/types" "github.com/hashicorp/consul/version" diff --git a/agent/consul/config_endpoint.go b/agent/consul/config_endpoint.go index a78859c350..96906dac68 100644 --- a/agent/consul/config_endpoint.go +++ b/agent/consul/config_endpoint.go @@ -10,10 +10,11 @@ import ( metrics "github.com/armon/go-metrics" "github.com/armon/go-metrics/prometheus" + hashstructure_v2 "github.com/mitchellh/hashstructure/v2" + "github.com/hashicorp/go-bexpr" "github.com/hashicorp/go-hclog" memdb "github.com/hashicorp/go-memdb" - hashstructure_v2 "github.com/mitchellh/hashstructure/v2" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/configentry" @@ -85,6 +86,7 @@ func (c *ConfigEntry) Apply(args *structs.ConfigEntryRequest, reply *bool) error } // Normalize and validate the incoming config entry as if it came from a user. + // Ensure Normalize is called before Validate for accurate validation if err := args.Entry.Normalize(); err != nil { return err } diff --git a/agent/consul/config_replication_test.go b/agent/consul/config_replication_test.go index e2c4fbee8d..3117e046a4 100644 --- a/agent/consul/config_replication_test.go +++ b/agent/consul/config_replication_test.go @@ -6,11 +6,11 @@ package consul import ( "context" "fmt" - "github.com/oklog/ulid/v2" - "github.com/stretchr/testify/assert" "os" "testing" + "github.com/oklog/ulid/v2" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/hashicorp/consul/agent/structs" @@ -129,7 +129,7 @@ func TestReplication_ConfigEntries(t *testing.T) { Entry: &structs.ServiceConfigEntry{ Kind: structs.ServiceDefaults, Name: fmt.Sprintf("svc-%d", i), - Protocol: "udp", + Protocol: "tcp", }, } diff --git a/agent/consul/coordinate_endpoint_test.go b/agent/consul/coordinate_endpoint_test.go index 1c693ba83b..6ca7a32595 100644 --- a/agent/consul/coordinate_endpoint_test.go +++ b/agent/consul/coordinate_endpoint_test.go @@ -12,15 +12,15 @@ import ( "testing" "time" - "github.com/hashicorp/serf/coordinate" "github.com/stretchr/testify/require" msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" "github.com/hashicorp/consul-net-rpc/net/rpc" + "github.com/hashicorp/serf/coordinate" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/consul/testrpc" ) @@ -92,13 +92,13 @@ func TestCoordinate_Update(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - require.Equal(t, lib.CoordinateSet{}, c) + require.Equal(t, librtt.CoordinateSet{}, c) _, c, err = state.Coordinate(nil, "node2", nil) if err != nil { t.Fatalf("err: %v", err) } - require.Equal(t, lib.CoordinateSet{}, c) + require.Equal(t, librtt.CoordinateSet{}, c) // Send another update for the second node. It should take precedence // since there will be two updates in the same batch. @@ -113,7 +113,7 @@ func TestCoordinate_Update(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - expected := lib.CoordinateSet{ + expected := librtt.CoordinateSet{ "": arg1.Coord, } require.Equal(t, expected, c) @@ -122,7 +122,7 @@ func TestCoordinate_Update(t *testing.T) { if err != nil { t.Fatalf("err: %v", err) } - expected = lib.CoordinateSet{ + expected = librtt.CoordinateSet{ "": arg2.Coord, } require.Equal(t, expected, c) diff --git a/agent/consul/health_endpoint.go b/agent/consul/health_endpoint.go index d26bbcd3b6..6f00ec4b08 100644 --- a/agent/consul/health_endpoint.go +++ b/agent/consul/health_endpoint.go @@ -8,15 +8,15 @@ import ( "sort" "github.com/armon/go-metrics" - bexpr "github.com/hashicorp/go-bexpr" - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-memdb" hashstructure_v2 "github.com/mitchellh/hashstructure/v2" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/configentry" "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/go-bexpr" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-memdb" ) // Health endpoint is used to query the health information @@ -250,68 +250,86 @@ func (h *Health) ServiceNodes(args *structs.ServiceSpecificRequest, reply *struc func(ws memdb.WatchSet, state *state.Store) error { var thisReply structs.IndexedCheckServiceNodes - index, nodes, err := f(ws, state, args) + sgIdx, sgArgs, err := h.getArgsForSamenessGroupMembers(args, ws, state) if err != nil { return err } - resolvedNodes := nodes - if args.MergeCentralConfig { - for _, node := range resolvedNodes { - ns := node.Service - if ns.IsSidecarProxy() || ns.IsGateway() { - cfgIndex, mergedns, err := configentry.MergeNodeServiceWithCentralConfig(ws, state, ns, h.logger) - if err != nil { - return err - } - if cfgIndex > index { - index = cfgIndex - } - *node.Service = *mergedns - } - } - - // Generate a hash of the resolvedNodes driving this response. - // Use it to determine if the response is identical to a prior wakeup. - newMergeHash, err := hashstructure_v2.Hash(resolvedNodes, hashstructure_v2.FormatV2, nil) + for _, arg := range sgArgs { + index, nodes, err := f(ws, state, arg) if err != nil { - return fmt.Errorf("error hashing reply for spurious wakeup suppression: %w", err) - } - if ranMergeOnce && priorMergeHash == newMergeHash { - // the below assignment is not required as the if condition already validates equality, - // but makes it more clear that prior value is being reset to the new hash on each run. - priorMergeHash = newMergeHash - reply.Index = index - // NOTE: the prior response is still alive inside of *reply, which is desirable - return errNotChanged - } else { - priorMergeHash = newMergeHash - ranMergeOnce = true + return err } + resolvedNodes := nodes + if arg.MergeCentralConfig { + for _, node := range resolvedNodes { + ns := node.Service + if ns.IsSidecarProxy() || ns.IsGateway() { + cfgIndex, mergedns, err := configentry.MergeNodeServiceWithCentralConfig(ws, state, ns, h.logger) + if err != nil { + return err + } + if cfgIndex > index { + index = cfgIndex + } + *node.Service = *mergedns + } + } + + // Generate a hash of the resolvedNodes driving this response. + // Use it to determine if the response is identical to a prior wakeup. + newMergeHash, err := hashstructure_v2.Hash(resolvedNodes, hashstructure_v2.FormatV2, nil) + if err != nil { + return fmt.Errorf("error hashing reply for spurious wakeup suppression: %w", err) + } + if ranMergeOnce && priorMergeHash == newMergeHash { + // the below assignment is not required as the if condition already validates equality, + // but makes it more clear that prior value is being reset to the new hash on each run. + priorMergeHash = newMergeHash + reply.Index = index + // NOTE: the prior response is still alive inside of *reply, which is desirable + return errNotChanged + } else { + priorMergeHash = newMergeHash + ranMergeOnce = true + } + + } + + thisReply.Index, thisReply.Nodes = index, resolvedNodes + + if len(arg.NodeMetaFilters) > 0 { + thisReply.Nodes = nodeMetaFilter(arg.NodeMetaFilters, thisReply.Nodes) + } + + raw, err := filter.Execute(thisReply.Nodes) + if err != nil { + return err + } + filteredNodes := raw.(structs.CheckServiceNodes) + thisReply.Nodes = filteredNodes.Filter(structs.CheckServiceNodeFilterOptions{FilterType: arg.HealthFilterType}) + + // 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 := h.srv.filterACL(arg.Token, &thisReply); err != nil { + return err + } + + if err := h.srv.sortNodesByDistanceFrom(arg.Source, thisReply.Nodes); err != nil { + return err + } + if len(thisReply.Nodes) > 0 { + break + } } - thisReply.Index, thisReply.Nodes = index, resolvedNodes - - if len(args.NodeMetaFilters) > 0 { - thisReply.Nodes = nodeMetaFilter(args.NodeMetaFilters, thisReply.Nodes) - } - - raw, err := filter.Execute(thisReply.Nodes) - if err != nil { - return err - } - thisReply.Nodes = raw.(structs.CheckServiceNodes) - - // 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 := h.srv.filterACL(args.Token, &thisReply); err != nil { - return err - } - - if err := h.srv.sortNodesByDistanceFrom(args.Source, thisReply.Nodes); err != nil { - return err + // If sameness group was used, evaluate the index of the sameness group + // and update the index of the response if it is greater. If sameness group is not + // used, the sgIdx will be 0 in this evaluation. + if sgIdx > thisReply.Index { + thisReply.Index = sgIdx } *reply = thisReply diff --git a/agent/consul/health_endpoint_ce.go b/agent/consul/health_endpoint_ce.go new file mode 100644 index 0000000000..1580054e5d --- /dev/null +++ b/agent/consul/health_endpoint_ce.go @@ -0,0 +1,38 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:build !consulent + +package consul + +import ( + "errors" + + "github.com/hashicorp/go-memdb" + + "github.com/hashicorp/consul/agent/consul/state" + "github.com/hashicorp/consul/agent/structs" +) + +// getArgsForSamenessGroupMembers returns the arguments for the sameness group members if SamenessGroup +// field is set in the ServiceSpecificRequest. It returns the index of the sameness group, the arguments +// for the sameness group members and an error if any. +// If SamenessGroup is not set, it returns: +// - the index 0 +// - an array containing the original arguments +// - nil error +// If SamenessGroup is set on CE, it returns:: +// - the index of 0 +// - nil array +// - an error indicating that sameness groups are not supported in consul CE +// If SamenessGroup is set on ENT, it returns: +// - the index of the sameness group +// - an array containing the arguments for the sameness group members +// - nil error +func (h *Health) getArgsForSamenessGroupMembers(args *structs.ServiceSpecificRequest, + ws memdb.WatchSet, state *state.Store) (uint64, []*structs.ServiceSpecificRequest, error) { + if args.SamenessGroup != "" { + return 0, nil, errors.New("sameness groups are not supported in consul CE") + } + return 0, []*structs.ServiceSpecificRequest{args}, nil +} diff --git a/agent/consul/health_endpoint_test.go b/agent/consul/health_endpoint_test.go index b47159c229..07f23cc2e0 100644 --- a/agent/consul/health_endpoint_test.go +++ b/agent/consul/health_endpoint_test.go @@ -15,7 +15,7 @@ import ( "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/consul/testrpc" @@ -193,8 +193,8 @@ func TestHealth_ChecksInState_DistanceSort(t *testing.T) { t.Fatalf("err: %v", err) } updates := structs.Coordinates{ - {Node: "foo", Coord: lib.GenerateCoordinate(1 * time.Millisecond)}, - {Node: "bar", Coord: lib.GenerateCoordinate(2 * time.Millisecond)}, + {Node: "foo", Coord: librtt.GenerateCoordinate(1 * time.Millisecond)}, + {Node: "bar", Coord: librtt.GenerateCoordinate(2 * time.Millisecond)}, } if err := s1.fsm.State().CoordinateBatchUpdate(3, updates); err != nil { t.Fatalf("err: %v", err) @@ -482,8 +482,8 @@ func TestHealth_ServiceChecks_DistanceSort(t *testing.T) { t.Fatalf("err: %v", err) } updates := structs.Coordinates{ - {Node: "foo", Coord: lib.GenerateCoordinate(1 * time.Millisecond)}, - {Node: "bar", Coord: lib.GenerateCoordinate(2 * time.Millisecond)}, + {Node: "foo", Coord: librtt.GenerateCoordinate(1 * time.Millisecond)}, + {Node: "bar", Coord: librtt.GenerateCoordinate(2 * time.Millisecond)}, } if err := s1.fsm.State().CoordinateBatchUpdate(3, updates); err != nil { t.Fatalf("err: %v", err) @@ -969,8 +969,8 @@ func TestHealth_ServiceNodes_DistanceSort(t *testing.T) { t.Fatalf("err: %v", err) } updates := structs.Coordinates{ - {Node: "foo", Coord: lib.GenerateCoordinate(1 * time.Millisecond)}, - {Node: "bar", Coord: lib.GenerateCoordinate(2 * time.Millisecond)}, + {Node: "foo", Coord: librtt.GenerateCoordinate(1 * time.Millisecond)}, + {Node: "bar", Coord: librtt.GenerateCoordinate(2 * time.Millisecond)}, } if err := s1.fsm.State().CoordinateBatchUpdate(3, updates); err != nil { t.Fatalf("err: %v", err) diff --git a/agent/consul/kvs_endpoint.go b/agent/consul/kvs_endpoint.go index 65dc2cd56d..bfad9fa193 100644 --- a/agent/consul/kvs_endpoint.go +++ b/agent/consul/kvs_endpoint.go @@ -42,7 +42,8 @@ func kvsPreApply(logger hclog.Logger, srv *Server, authz resolver.Result, op api return false, fmt.Errorf("Must provide key") } - // Apply the ACL policy if any. + // Apply the ACL policy if any, and validate operation. + // enumcover:api.KVOp switch op { case api.KVDeleteTree: var authzContext acl.AuthorizerContext @@ -66,13 +67,15 @@ func kvsPreApply(logger hclog.Logger, srv *Server, authz resolver.Result, op api return false, err } - default: + case api.KVCheckNotExists, api.KVUnlock, api.KVLock, api.KVCAS, api.KVDeleteCAS, api.KVDelete, api.KVSet: var authzContext acl.AuthorizerContext dirEnt.FillAuthzContext(&authzContext) if err := authz.ToAllowAuthorizer().KeyWriteAllowed(dirEnt.Key, &authzContext); err != nil { return false, err } + default: + return false, fmt.Errorf("unknown KV operation: %s", op) } // If this is a lock, we must check for a lock-delay. Since lock-delay diff --git a/agent/consul/leader.go b/agent/consul/leader.go index 53312c7fe5..8b0db34b23 100644 --- a/agent/consul/leader.go +++ b/agent/consul/leader.go @@ -5,7 +5,6 @@ package consul import ( "context" - "errors" "fmt" "net" "strconv" @@ -16,10 +15,7 @@ import ( "github.com/armon/go-metrics" "github.com/armon/go-metrics/prometheus" - "github.com/google/go-cmp/cmp" - "github.com/oklog/ulid/v2" "golang.org/x/time/rate" - "google.golang.org/protobuf/types/known/anypb" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-uuid" @@ -32,13 +28,8 @@ import ( "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/structs/aclfilter" "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/internal/resource" - "github.com/hashicorp/consul/internal/storage" "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/logging" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" ) var LeaderSummaries = []prometheus.SummaryDefinition{ @@ -349,18 +340,6 @@ func (s *Server) establishLeadership(ctx context.Context) error { s.startLogVerification(ctx) } - if s.useV2Tenancy { - if err := s.initTenancy(ctx, s.storageBackend); err != nil { - return err - } - } - - if s.useV2Resources { - if err := s.initConsulService(ctx, pbresource.NewResourceServiceClient(s.insecureSafeGRPCChan)); err != nil { - return err - } - } - if s.config.Reporting.License.Enabled && s.reportingManager != nil { s.reportingManager.StartReportingAgent() } @@ -1310,121 +1289,3 @@ func (s *serversIntentionsAsConfigEntriesInfo) update(srv *metadata.Server) bool // prevent continuing server evaluation return false } - -func (s *Server) initConsulService(ctx context.Context, client pbresource.ResourceServiceClient) error { - service := &pbcatalog.Service{ - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{consulWorkloadPrefix}, - }, - Ports: []*pbcatalog.ServicePort{ - { - TargetPort: consulPortNameServer, - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - // No virtual port defined for now, as we assume this is generally for Service Discovery - }, - }, - } - - serviceData, err := anypb.New(service) - if err != nil { - return fmt.Errorf("could not convert Service to `any` message: %w", err) - } - - // create a default namespace in default partition - serviceID := &pbresource.ID{ - Type: pbcatalog.ServiceType, - Name: structs.ConsulServiceName, - Tenancy: resource.DefaultNamespacedTenancy(), - } - - serviceResource := &pbresource.Resource{ - Id: serviceID, - Data: serviceData, - } - - res, err := client.Read(ctx, &pbresource.ReadRequest{Id: serviceID}) - if err != nil && !grpcNotFoundErr(err) { - return fmt.Errorf("failed to read the %s Service: %w", structs.ConsulServiceName, err) - } - - if err == nil { - existingService := res.GetResource() - s.logger.Debug("existingService consul Service found") - - // If the Service is identical, we're done. - if cmp.Equal(serviceResource, existingService, resourceCmpOptions...) { - s.logger.Debug("no updates to perform on consul Service") - return nil - } - - // If the existing Service is different, add the Version to the patch for CAS write. - serviceResource.Id = existingService.Id - serviceResource.Version = existingService.Version - } - - _, err = client.Write(ctx, &pbresource.WriteRequest{Resource: serviceResource}) - if err != nil { - return fmt.Errorf("failed to create the %s service: %w", structs.ConsulServiceName, err) - } - - s.logger.Info("Created consul Service in catalog") - return nil -} - -func (s *Server) initTenancy(ctx context.Context, b storage.Backend) error { - // we write these defaults directly to the storage backend - // without going through the resource service since tenancy - // validation hooks block writes to the default namespace - // and partition. - if err := s.createDefaultPartition(ctx, b); err != nil { - return err - } - - if err := s.createDefaultNamespace(ctx, b); err != nil { - return err - } - return nil -} - -func (s *Server) createDefaultNamespace(ctx context.Context, b storage.Backend) error { - readID := &pbresource.ID{ - Type: pbtenancy.NamespaceType, - Name: resource.DefaultNamespaceName, - Tenancy: resource.DefaultPartitionedTenancy(), - } - - read, err := b.Read(ctx, storage.StrongConsistency, readID) - - if err != nil && !errors.Is(err, storage.ErrNotFound) { - return fmt.Errorf("failed to read the %q namespace: %v", resource.DefaultNamespaceName, err) - } - if read == nil && errors.Is(err, storage.ErrNotFound) { - nsData, err := anypb.New(&pbtenancy.Namespace{Description: "default namespace in default partition"}) - if err != nil { - return err - } - - // create a default namespace in default partition - nsID := &pbresource.ID{ - Type: pbtenancy.NamespaceType, - Name: resource.DefaultNamespaceName, - Tenancy: resource.DefaultPartitionedTenancy(), - Uid: ulid.Make().String(), - } - - _, err = b.WriteCAS(ctx, &pbresource.Resource{ - Id: nsID, - Generation: ulid.Make().String(), - Data: nsData, - Metadata: map[string]string{ - "generated_at": time.Now().Format(time.RFC3339), - }, - }) - - if err != nil { - return fmt.Errorf("failed to create the %q namespace: %v", resource.DefaultNamespaceName, err) - } - } - s.logger.Info("Created", "namespace", resource.DefaultNamespaceName) - return nil -} diff --git a/agent/consul/leader_ce.go b/agent/consul/leader_ce.go deleted file mode 100644 index 2d67b7bded..0000000000 --- a/agent/consul/leader_ce.go +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -//go:build !consulent - -package consul - -import ( - "context" - - "github.com/hashicorp/consul/internal/storage" -) - -func (s *Server) createDefaultPartition(ctx context.Context, b storage.Backend) error { - // no-op - return nil -} diff --git a/agent/consul/leader_ce_test.go b/agent/consul/leader_ce_test.go index 86da505c3a..367a9fbcae 100644 --- a/agent/consul/leader_ce_test.go +++ b/agent/consul/leader_ce_test.go @@ -6,17 +6,7 @@ package consul import ( - "context" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/internal/resource" - "github.com/hashicorp/consul/internal/storage" - libserf "github.com/hashicorp/consul/lib/serf" - "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" - "github.com/hashicorp/consul/testrpc" + "github.com/hashicorp/consul/internal/gossip/libserf" ) func updateSerfTags(s *Server, key, value string) { @@ -26,41 +16,3 @@ func updateSerfTags(s *Server, key, value string) { libserf.UpdateTag(s.serfWAN, key, value) } } - -func TestServer_InitTenancy(t *testing.T) { - t.Parallel() - - _, conf := testServerConfig(t) - deps := newDefaultDeps(t, conf) - deps.Experiments = []string{"v2tenancy"} - deps.Registry = NewTypeRegistry() - - s, err := newServerWithDeps(t, conf, deps) - require.NoError(t, err) - - // first initTenancy call happens here - waitForLeaderEstablishment(t, s) - testrpc.WaitForLeader(t, s.RPC, "dc1") - - nsID := &pbresource.ID{ - Type: pbtenancy.NamespaceType, - Tenancy: resource.DefaultPartitionedTenancy(), - Name: resource.DefaultNamespaceName, - } - - ns, err := s.storageBackend.Read(context.Background(), storage.StrongConsistency, nsID) - require.NoError(t, err) - require.Equal(t, resource.DefaultNamespaceName, ns.Id.Name) - - // explicitly call initiTenancy to verify we do not re-create namespace - err = s.initTenancy(context.Background(), s.storageBackend) - require.NoError(t, err) - - // read again - actual, err := s.storageBackend.Read(context.Background(), storage.StrongConsistency, nsID) - require.NoError(t, err) - - require.Equal(t, ns.Id.Uid, actual.Id.Uid) - require.Equal(t, ns.Generation, actual.Generation) - require.Equal(t, ns.Version, actual.Version) -} diff --git a/agent/consul/leader_connect_ca.go b/agent/consul/leader_connect_ca.go index 92cdf40a6a..ee6562912f 100644 --- a/agent/consul/leader_connect_ca.go +++ b/agent/consul/leader_connect_ca.go @@ -14,9 +14,10 @@ import ( "sync" "time" + "golang.org/x/time/rate" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-uuid" - "golang.org/x/time/rate" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/connect" @@ -1455,11 +1456,6 @@ func (c *CAManager) AuthorizeAndSignCertificate(csr *x509.CertificateRequest, au return nil, connect.InvalidCSRError("SPIFFE ID in CSR from a different datacenter: %s, "+ "we are %s", v.Datacenter, dc) } - case *connect.SpiffeIDWorkloadIdentity: - v.GetEnterpriseMeta().FillAuthzContext(&authzContext) - if err := allow.IdentityWriteAllowed(v.WorkloadIdentity, &authzContext); err != nil { - return nil, err - } case *connect.SpiffeIDAgent: v.GetEnterpriseMeta().FillAuthzContext(&authzContext) if err := allow.NodeWriteAllowed(v.Agent, &authzContext); err != nil { @@ -1520,7 +1516,6 @@ func (c *CAManager) SignCertificate(csr *x509.CertificateRequest, spiffeID conne agentID, isAgent := spiffeID.(*connect.SpiffeIDAgent) serverID, isServer := spiffeID.(*connect.SpiffeIDServer) mgwID, isMeshGateway := spiffeID.(*connect.SpiffeIDMeshGateway) - wID, isWorkloadIdentity := spiffeID.(*connect.SpiffeIDWorkloadIdentity) var entMeta acl.EnterpriseMeta switch { @@ -1530,12 +1525,6 @@ func (c *CAManager) SignCertificate(csr *x509.CertificateRequest, spiffeID conne "we are %s", serviceID.Host, signingID.Host()) } entMeta.Merge(serviceID.GetEnterpriseMeta()) - case isWorkloadIdentity: - if !signingID.CanSign(spiffeID) { - return nil, connect.InvalidCSRError("SPIFFE ID in CSR from a different trust domain: %s, "+ - "we are %s", wID.TrustDomain, signingID.Host()) - } - entMeta.Merge(wID.GetEnterpriseMeta()) case isMeshGateway: if !signingID.CanSign(spiffeID) { return nil, connect.InvalidCSRError("SPIFFE ID in CSR from a different trust domain: %s, "+ @@ -1658,9 +1647,6 @@ func (c *CAManager) SignCertificate(csr *x509.CertificateRequest, spiffeID conne case isService: reply.Service = serviceID.Service reply.ServiceURI = cert.URIs[0].String() - case isWorkloadIdentity: - reply.WorkloadIdentity = wID.WorkloadIdentity - reply.WorkloadIdentityURI = cert.URIs[0].String() case isMeshGateway: reply.Kind = structs.ServiceKindMeshGateway reply.KindURI = cert.URIs[0].String() diff --git a/agent/consul/leader_connect_ca_test.go b/agent/consul/leader_connect_ca_test.go index e372c010a7..4560e97380 100644 --- a/agent/consul/leader_connect_ca_test.go +++ b/agent/consul/leader_connect_ca_test.go @@ -19,13 +19,14 @@ import ( "testing" "time" - msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" - "github.com/hashicorp/consul-net-rpc/net/rpc" - vaultapi "github.com/hashicorp/vault/api" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" + msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" + "github.com/hashicorp/consul-net-rpc/net/rpc" + vaultapi "github.com/hashicorp/vault/api" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/connect/ca" @@ -566,7 +567,7 @@ func TestCAManager_Initialize_Logging(t *testing.T) { deps := newDefaultDeps(t, conf1) deps.Logger = logger - s1, err := NewServer(conf1, deps, grpc.NewServer(), nil, logger, nil) + s1, err := NewServer(conf1, deps, grpc.NewServer(), nil, logger) require.NoError(t, err) defer s1.Shutdown() testrpc.WaitForLeader(t, s1.RPC, "dc1") @@ -1317,12 +1318,6 @@ func TestCAManager_AuthorizeAndSignCertificate(t *testing.T) { Host: "test-host", Partition: "test-partition", }.URI() - identityURL := connect.SpiffeIDWorkloadIdentity{ - TrustDomain: "test-trust-domain", - Partition: "test-partition", - Namespace: "test-namespace", - WorkloadIdentity: "test-workload-identity", - }.URI() tests := []struct { name string @@ -1418,15 +1413,6 @@ func TestCAManager_AuthorizeAndSignCertificate(t *testing.T) { } }, }, - { - name: "err_identity_write_not_allowed", - expectErr: "Permission denied", - getCSR: func() *x509.CertificateRequest { - return &x509.CertificateRequest{ - URIs: []*url.URL{identityURL}, - } - }, - }, } for _, tc := range tests { diff --git a/agent/consul/leader_registrator_v2.go b/agent/consul/leader_registrator_v2.go deleted file mode 100644 index 97465e10d1..0000000000 --- a/agent/consul/leader_registrator_v2.go +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package consul - -import ( - "context" - "fmt" - "strconv" - "strings" - - "github.com/google/go-cmp/cmp" - "github.com/google/go-cmp/cmp/cmpopts" - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/serf/serf" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/testing/protocmp" - "google.golang.org/protobuf/types/known/anypb" - - "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/agent/metadata" - "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/internal/resource" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/types" -) - -const ( - consulWorkloadPrefix = "consul-server-" - consulPortNameServer = "server" -) - -var _ ConsulRegistrator = (*V2ConsulRegistrator)(nil) - -var resourceCmpOptions = []cmp.Option{ - protocmp.IgnoreFields(&pbresource.Resource{}, "status", "generation", "version"), - protocmp.IgnoreFields(&pbresource.ID{}, "uid"), - protocmp.Transform(), - // Stringify any type passed to the sorter so that we can reliably compare most values. - cmpopts.SortSlices(func(a, b any) bool { return fmt.Sprintf("%v", a) < fmt.Sprintf("%v", b) }), -} - -type V2ConsulRegistrator struct { - Logger hclog.Logger - NodeName string - EntMeta *acl.EnterpriseMeta - - Client pbresource.ResourceServiceClient -} - -// HandleAliveMember is used to ensure the server is registered as a Workload -// with a passing health check. -func (r V2ConsulRegistrator) HandleAliveMember(member serf.Member, nodeEntMeta *acl.EnterpriseMeta, joinServer func(m serf.Member, parts *metadata.Server) error) error { - valid, parts := metadata.IsConsulServer(member) - if !valid { - return nil - } - - if nodeEntMeta == nil { - nodeEntMeta = structs.NodeEnterpriseMetaInDefaultPartition() - } - - // Attempt to join the consul server, regardless of the existing catalog state - if err := joinServer(member, parts); err != nil { - return err - } - - r.Logger.Info("member joined, creating catalog entries", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - - workloadResource, err := r.createWorkloadFromMember(member, parts, nodeEntMeta) - if err != nil { - return err - } - - // Check if the Workload already exists and if it's the same - res, err := r.Client.Read(context.TODO(), &pbresource.ReadRequest{Id: workloadResource.Id}) - if err != nil && !grpcNotFoundErr(err) { - return fmt.Errorf("error checking for existing Workload %s: %w", workloadResource.Id.Name, err) - } - - if err == nil { - existingWorkload := res.GetResource() - - r.Logger.Debug("existing Workload matching the member found", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - - // If the Workload is identical, move to updating the health status - if cmp.Equal(workloadResource, existingWorkload, resourceCmpOptions...) { - r.Logger.Debug("no updates to perform on member Workload", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - goto HEALTHSTATUS - } - - // If the existing Workload different, add the existing Version into the patch for CAS write - workloadResource.Id = existingWorkload.Id - workloadResource.Version = existingWorkload.Version - } - - if _, err := r.Client.Write(context.TODO(), &pbresource.WriteRequest{Resource: workloadResource}); err != nil { - return fmt.Errorf("failed to write Workload %s: %w", workloadResource.Id.Name, err) - } - - r.Logger.Info("updated consul Workload in catalog", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - -HEALTHSTATUS: - hsResource, err := r.createHealthStatusFromMember(member, workloadResource.Id, true, nodeEntMeta) - if err != nil { - return err - } - - // Check if the HealthStatus already exists and if it's the same - res, err = r.Client.Read(context.TODO(), &pbresource.ReadRequest{Id: hsResource.Id}) - if err != nil && !grpcNotFoundErr(err) { - return fmt.Errorf("error checking for existing HealthStatus %s: %w", hsResource.Id.Name, err) - } - - if err == nil { - existingHS := res.GetResource() - - r.Logger.Debug("existing HealthStatus matching the member found", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - - // If the HealthStatus is identical, we're done. - if cmp.Equal(hsResource, existingHS, resourceCmpOptions...) { - r.Logger.Debug("no updates to perform on member HealthStatus", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - return nil - } - - // If the existing HealthStatus is different, add the Version to the patch for CAS write. - hsResource.Id = existingHS.Id - hsResource.Version = existingHS.Version - } - - if _, err := r.Client.Write(context.TODO(), &pbresource.WriteRequest{Resource: hsResource}); err != nil { - return fmt.Errorf("failed to write HealthStatus %s: %w", hsResource.Id.Name, err) - } - r.Logger.Info("updated consul HealthStatus in catalog", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - return nil -} - -func (r V2ConsulRegistrator) createWorkloadFromMember(member serf.Member, parts *metadata.Server, nodeEntMeta *acl.EnterpriseMeta) (*pbresource.Resource, error) { - workloadMeta := map[string]string{ - "read_replica": strconv.FormatBool(member.Tags["read_replica"] == "1"), - "raft_version": strconv.Itoa(parts.RaftVersion), - "serf_protocol_current": strconv.FormatUint(uint64(member.ProtocolCur), 10), - "serf_protocol_min": strconv.FormatUint(uint64(member.ProtocolMin), 10), - "serf_protocol_max": strconv.FormatUint(uint64(member.ProtocolMax), 10), - "version": parts.Build.String(), - } - - if parts.ExternalGRPCPort > 0 { - workloadMeta["grpc_port"] = strconv.Itoa(parts.ExternalGRPCPort) - } - if parts.ExternalGRPCTLSPort > 0 { - workloadMeta["grpc_tls_port"] = strconv.Itoa(parts.ExternalGRPCTLSPort) - } - - workload := &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: member.Addr.String(), Ports: []string{consulPortNameServer}}, - }, - // Don't include identity since Consul is not routable through the mesh. - // Don't include locality because these values are not passed along through serf, and they are probably - // different from the leader's values. - Ports: map[string]*pbcatalog.WorkloadPort{ - consulPortNameServer: { - Port: uint32(parts.Port), - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - // TODO: add other agent ports - }, - } - - workloadData, err := anypb.New(workload) - if err != nil { - return nil, fmt.Errorf("could not convert Workload to 'any' type: %w", err) - } - - workloadId := &pbresource.ID{ - Name: fmt.Sprintf("%s%s", consulWorkloadPrefix, types.NodeID(member.Tags["id"])), - Type: pbcatalog.WorkloadType, - Tenancy: resource.DefaultNamespacedTenancy(), - } - workloadId.Tenancy.Partition = nodeEntMeta.PartitionOrDefault() - - return &pbresource.Resource{ - Id: workloadId, - Data: workloadData, - Metadata: workloadMeta, - }, nil -} - -func (r V2ConsulRegistrator) createHealthStatusFromMember(member serf.Member, workloadId *pbresource.ID, passing bool, nodeEntMeta *acl.EnterpriseMeta) (*pbresource.Resource, error) { - hs := &pbcatalog.HealthStatus{ - Type: string(structs.SerfCheckID), - Description: structs.SerfCheckName, - } - - if passing { - hs.Status = pbcatalog.Health_HEALTH_PASSING - hs.Output = structs.SerfCheckAliveOutput - } else { - hs.Status = pbcatalog.Health_HEALTH_CRITICAL - hs.Output = structs.SerfCheckFailedOutput - } - - hsData, err := anypb.New(hs) - if err != nil { - return nil, fmt.Errorf("could not convert HealthStatus to 'any' type: %w", err) - } - - hsId := &pbresource.ID{ - Name: fmt.Sprintf("%s%s", consulWorkloadPrefix, types.NodeID(member.Tags["id"])), - Type: pbcatalog.HealthStatusType, - Tenancy: resource.DefaultNamespacedTenancy(), - } - hsId.Tenancy.Partition = nodeEntMeta.PartitionOrDefault() - - return &pbresource.Resource{ - Id: hsId, - Data: hsData, - Owner: workloadId, - }, nil -} - -// HandleFailedMember is used to mark the workload's associated HealthStatus. -func (r V2ConsulRegistrator) HandleFailedMember(member serf.Member, nodeEntMeta *acl.EnterpriseMeta) error { - if valid, _ := metadata.IsConsulServer(member); !valid { - return nil - } - - if nodeEntMeta == nil { - nodeEntMeta = structs.NodeEnterpriseMetaInDefaultPartition() - } - - r.Logger.Info("member failed", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - - // Validate that the associated workload exists - workloadId := &pbresource.ID{ - Name: fmt.Sprintf("%s%s", consulWorkloadPrefix, types.NodeID(member.Tags["id"])), - Type: pbcatalog.WorkloadType, - Tenancy: resource.DefaultNamespacedTenancy(), - } - workloadId.Tenancy.Partition = nodeEntMeta.PartitionOrDefault() - - res, err := r.Client.Read(context.TODO(), &pbresource.ReadRequest{Id: workloadId}) - if err != nil && !grpcNotFoundErr(err) { - return fmt.Errorf("error checking for existing Workload %s: %w", workloadId.Name, err) - } - if grpcNotFoundErr(err) { - r.Logger.Info("ignoring failed event for member because it does not exist in the catalog", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - return nil - } - // Overwrite the workload ID with the one that has UID populated. - existingWorkload := res.GetResource() - - hsResource, err := r.createHealthStatusFromMember(member, existingWorkload.Id, false, nodeEntMeta) - if err != nil { - return err - } - - res, err = r.Client.Read(context.TODO(), &pbresource.ReadRequest{Id: hsResource.Id}) - if err != nil && !grpcNotFoundErr(err) { - return fmt.Errorf("error checking for existing HealthStatus %s: %w", hsResource.Id.Name, err) - } - - if err == nil { - existingHS := res.GetResource() - r.Logger.Debug("existing HealthStatus matching the member found", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - - // If the HealthStatus is identical, we're done. - if cmp.Equal(hsResource, existingHS, resourceCmpOptions...) { - r.Logger.Debug("no updates to perform on member HealthStatus", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - return nil - } - - // If the existing HealthStatus is different, add the Version to the patch for CAS write. - hsResource.Id = existingHS.Id - hsResource.Version = existingHS.Version - } - - if _, err := r.Client.Write(context.TODO(), &pbresource.WriteRequest{Resource: hsResource}); err != nil { - return fmt.Errorf("failed to write HealthStatus %s: %w", hsResource.Id.Name, err) - } - r.Logger.Info("updated consul HealthStatus in catalog", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - return nil -} - -// HandleLeftMember is used to handle members that gracefully -// left. They are removed if necessary. -func (r V2ConsulRegistrator) HandleLeftMember(member serf.Member, nodeEntMeta *acl.EnterpriseMeta, removeServerFunc func(m serf.Member) error) error { - return r.handleDeregisterMember("left", member, nodeEntMeta, removeServerFunc) -} - -// HandleReapMember is used to handle members that have been -// reaped after a prolonged failure. They are removed from the catalog. -func (r V2ConsulRegistrator) HandleReapMember(member serf.Member, nodeEntMeta *acl.EnterpriseMeta, removeServerFunc func(m serf.Member) error) error { - return r.handleDeregisterMember("reaped", member, nodeEntMeta, removeServerFunc) -} - -// handleDeregisterMember is used to remove a member of a given reason -func (r V2ConsulRegistrator) handleDeregisterMember(reason string, member serf.Member, nodeEntMeta *acl.EnterpriseMeta, removeServerFunc func(m serf.Member) error) error { - if valid, _ := metadata.IsConsulServer(member); !valid { - return nil - } - - if nodeEntMeta == nil { - nodeEntMeta = structs.NodeEnterpriseMetaInDefaultPartition() - } - - r.Logger.Info("removing member", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - "reason", reason, - ) - - if err := removeServerFunc(member); err != nil { - return err - } - - // Do not remove our self. This can only happen if the current leader - // is leaving. Instead, we should allow a follower to take-over and - // remove us later. - if strings.EqualFold(member.Name, r.NodeName) && - strings.EqualFold(nodeEntMeta.PartitionOrDefault(), r.EntMeta.PartitionOrDefault()) { - r.Logger.Warn("removing self should be done by follower", - "name", r.NodeName, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - "reason", reason, - ) - return nil - } - - // Check if the workload exists - workloadID := &pbresource.ID{ - Name: fmt.Sprintf("%s%s", consulWorkloadPrefix, types.NodeID(member.Tags["id"])), - Type: pbcatalog.WorkloadType, - Tenancy: resource.DefaultNamespacedTenancy(), - } - workloadID.Tenancy.Partition = nodeEntMeta.PartitionOrDefault() - - res, err := r.Client.Read(context.TODO(), &pbresource.ReadRequest{Id: workloadID}) - if err != nil && !grpcNotFoundErr(err) { - return fmt.Errorf("error checking for existing Workload %s: %w", workloadID.Name, err) - } - if grpcNotFoundErr(err) { - r.Logger.Info("ignoring reap event for member because it does not exist in the catalog", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - return nil - } - existingWorkload := res.GetResource() - - // The HealthStatus should be reaped automatically - if _, err := r.Client.Delete(context.TODO(), &pbresource.DeleteRequest{Id: existingWorkload.Id}); err != nil { - return fmt.Errorf("failed to delete Workload %s: %w", existingWorkload.Id.Name, err) - } - r.Logger.Info("deleted consul Workload", - "member", member.Name, - "partition", getSerfMemberEnterpriseMeta(member).PartitionOrDefault(), - ) - return err -} - -func grpcNotFoundErr(err error) bool { - if err == nil { - return false - } - s, ok := status.FromError(err) - return ok && s.Code() == codes.NotFound -} diff --git a/agent/consul/leader_registrator_v2_test.go b/agent/consul/leader_registrator_v2_test.go deleted file mode 100644 index c2729c47ff..0000000000 --- a/agent/consul/leader_registrator_v2_test.go +++ /dev/null @@ -1,583 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package consul - -import ( - "fmt" - "net" - "testing" - - "github.com/google/go-cmp/cmp" - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/serf/serf" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - - "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/agent/metadata" - "github.com/hashicorp/consul/agent/structs" - mockpbresource "github.com/hashicorp/consul/grpcmocks/proto-public/pbresource" - "github.com/hashicorp/consul/internal/resource" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" -) - -var ( - fakeWrappedErr = fmt.Errorf("fake test error") -) - -type testCase struct { - name string - member serf.Member - nodeNameOverride string // This is used in the HandleLeftMember test to avoid deregistering ourself - - existingWorkload *pbresource.Resource - workloadReadErr bool - workloadWriteErr bool - workloadDeleteErr bool - - existingHealthStatus *pbresource.Resource - healthstatusReadErr bool - healthstatusWriteErr bool - - mutatedWorkload *pbresource.Resource // leaving one of these out means the mock expects not to have a write/delete called - mutatedHealthStatus *pbresource.Resource - expErr string -} - -func Test_HandleAliveMember(t *testing.T) { - t.Parallel() - - run := func(t *testing.T, tt testCase) { - client := mockpbresource.NewResourceServiceClient(t) - mockClient := client.EXPECT() - - // Build mock expectations based on the order of HandleAliveMember resource calls - setupReadExpectation(t, mockClient, getTestWorkloadId(), tt.existingWorkload, tt.workloadReadErr) - setupWriteExpectation(t, mockClient, tt.mutatedWorkload, tt.workloadWriteErr) - if !tt.workloadReadErr && !tt.workloadWriteErr { - // We expect to bail before this read if there is an error earlier in the function - setupReadExpectation(t, mockClient, getTestHealthstatusId(), tt.existingHealthStatus, tt.healthstatusReadErr) - } - setupWriteExpectation(t, mockClient, tt.mutatedHealthStatus, tt.healthstatusWriteErr) - - registrator := V2ConsulRegistrator{ - Logger: hclog.New(&hclog.LoggerOptions{}), - NodeName: "test-server-1", - Client: client, - } - - // Mock join function - var joinMockCalled bool - joinMock := func(_ serf.Member, _ *metadata.Server) error { - joinMockCalled = true - return nil - } - - err := registrator.HandleAliveMember(tt.member, acl.DefaultEnterpriseMeta(), joinMock) - if tt.expErr != "" { - require.Contains(t, err.Error(), tt.expErr) - } else { - require.NoError(t, err) - } - require.True(t, joinMockCalled, "the mock join function was not called") - } - - tests := []testCase{ - { - name: "New alive member", - member: getTestSerfMember(serf.StatusAlive), - mutatedWorkload: getTestWorkload(t), - mutatedHealthStatus: getTestHealthStatus(t, true), - }, - { - name: "No updates needed", - member: getTestSerfMember(serf.StatusAlive), - existingWorkload: getTestWorkload(t), - existingHealthStatus: getTestHealthStatus(t, true), - }, - { - name: "Existing Workload and HS need to be updated", - member: getTestSerfMember(serf.StatusAlive), - existingWorkload: getTestWorkloadWithPort(t, 8301), - existingHealthStatus: getTestHealthStatus(t, false), - mutatedWorkload: getTestWorkload(t), - mutatedHealthStatus: getTestHealthStatus(t, true), - }, - { - name: "Only the HS needs to be updated", - member: getTestSerfMember(serf.StatusAlive), - existingWorkload: getTestWorkload(t), - existingHealthStatus: getTestHealthStatus(t, false), - mutatedHealthStatus: getTestHealthStatus(t, true), - }, - { - name: "Error reading Workload", - member: getTestSerfMember(serf.StatusAlive), - workloadReadErr: true, - expErr: "error checking for existing Workload", - }, - { - name: "Error writing Workload", - member: getTestSerfMember(serf.StatusAlive), - workloadWriteErr: true, - mutatedWorkload: getTestWorkload(t), - expErr: "failed to write Workload", - }, - { - name: "Error reading HealthStatus", - member: getTestSerfMember(serf.StatusAlive), - healthstatusReadErr: true, - mutatedWorkload: getTestWorkload(t), - expErr: "error checking for existing HealthStatus", - }, - { - name: "Error writing HealthStatus", - member: getTestSerfMember(serf.StatusAlive), - healthstatusWriteErr: true, - mutatedWorkload: getTestWorkload(t), - mutatedHealthStatus: getTestHealthStatus(t, true), - expErr: "failed to write HealthStatus", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - run(t, tt) - }) - } -} - -func Test_HandleFailedMember(t *testing.T) { - t.Parallel() - - run := func(t *testing.T, tt testCase) { - client := mockpbresource.NewResourceServiceClient(t) - mockClient := client.EXPECT() - - // Build mock expectations based on the order of HandleFailed resource calls - setupReadExpectation(t, mockClient, getTestWorkloadId(), tt.existingWorkload, tt.workloadReadErr) - if !tt.workloadReadErr && tt.existingWorkload != nil { - // We expect to bail before this read if there is an error earlier in the function or there is no workload - setupReadExpectation(t, mockClient, getTestHealthstatusId(), tt.existingHealthStatus, tt.healthstatusReadErr) - } - setupWriteExpectation(t, mockClient, tt.mutatedHealthStatus, tt.healthstatusWriteErr) - - registrator := V2ConsulRegistrator{ - Logger: hclog.New(&hclog.LoggerOptions{}), - NodeName: "test-server-1", - Client: client, - } - - err := registrator.HandleFailedMember(tt.member, acl.DefaultEnterpriseMeta()) - if tt.expErr != "" { - require.Contains(t, err.Error(), tt.expErr) - } else { - require.NoError(t, err) - } - } - - tests := []testCase{ - { - name: "Update non-existent HealthStatus", - member: getTestSerfMember(serf.StatusFailed), - existingWorkload: getTestWorkload(t), - mutatedHealthStatus: getTestHealthStatus(t, false), - }, - { - name: "Underlying Workload does not exist", - member: getTestSerfMember(serf.StatusFailed), - }, - { - name: "Update an existing HealthStatus", - member: getTestSerfMember(serf.StatusFailed), - existingWorkload: getTestWorkload(t), - existingHealthStatus: getTestHealthStatus(t, true), - mutatedHealthStatus: getTestHealthStatus(t, false), - }, - { - name: "HealthStatus is already critical - no updates needed", - member: getTestSerfMember(serf.StatusFailed), - existingWorkload: getTestWorkload(t), - existingHealthStatus: getTestHealthStatus(t, false), - }, - { - name: "Error reading Workload", - member: getTestSerfMember(serf.StatusFailed), - workloadReadErr: true, - expErr: "error checking for existing Workload", - }, - { - name: "Error reading HealthStatus", - member: getTestSerfMember(serf.StatusFailed), - existingWorkload: getTestWorkload(t), - healthstatusReadErr: true, - expErr: "error checking for existing HealthStatus", - }, - { - name: "Error writing HealthStatus", - member: getTestSerfMember(serf.StatusFailed), - existingWorkload: getTestWorkload(t), - healthstatusWriteErr: true, - mutatedHealthStatus: getTestHealthStatus(t, false), - expErr: "failed to write HealthStatus", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - run(t, tt) - }) - } -} - -// Test_HandleLeftMember also tests HandleReapMembers, which are the same core logic with some different logs. -func Test_HandleLeftMember(t *testing.T) { - t.Parallel() - - run := func(t *testing.T, tt testCase) { - client := mockpbresource.NewResourceServiceClient(t) - mockClient := client.EXPECT() - - // Build mock expectations based on the order of HandleLeftMember resource calls - // We check for the override, which we use to skip self de-registration - if tt.nodeNameOverride == "" { - setupReadExpectation(t, mockClient, getTestWorkloadId(), tt.existingWorkload, tt.workloadReadErr) - if tt.existingWorkload != nil && !tt.workloadReadErr { - setupDeleteExpectation(t, mockClient, tt.mutatedWorkload, tt.workloadDeleteErr) - } - } - - nodeName := "test-server-2" // This is not the same as the serf node so we don't dergister ourself. - if tt.nodeNameOverride != "" { - nodeName = tt.nodeNameOverride - } - - registrator := V2ConsulRegistrator{ - Logger: hclog.New(&hclog.LoggerOptions{}), - NodeName: nodeName, // We change this so that we don't deregister ourself - Client: client, - } - - // Mock join function - var removeMockCalled bool - removeMock := func(_ serf.Member) error { - removeMockCalled = true - return nil - } - - err := registrator.HandleLeftMember(tt.member, acl.DefaultEnterpriseMeta(), removeMock) - if tt.expErr != "" { - require.Contains(t, err.Error(), tt.expErr) - } else { - require.NoError(t, err) - } - require.True(t, removeMockCalled, "the mock remove function was not called") - } - - tests := []testCase{ - { - name: "Remove member", - member: getTestSerfMember(serf.StatusAlive), - existingWorkload: getTestWorkload(t), - mutatedWorkload: getTestWorkload(t), - }, - { - name: "Don't deregister ourself", - member: getTestSerfMember(serf.StatusAlive), - nodeNameOverride: "test-server-1", - }, - { - name: "Don't do anything if the Workload is already gone", - member: getTestSerfMember(serf.StatusAlive), - }, - { - name: "Remove member regardless of Workload payload", - member: getTestSerfMember(serf.StatusAlive), - existingWorkload: getTestWorkloadWithPort(t, 8301), - mutatedWorkload: getTestWorkload(t), - }, - { - name: "Error reading Workload", - member: getTestSerfMember(serf.StatusAlive), - workloadReadErr: true, - expErr: "error checking for existing Workload", - }, - { - name: "Error deleting Workload", - member: getTestSerfMember(serf.StatusAlive), - workloadDeleteErr: true, - existingWorkload: getTestWorkloadWithPort(t, 8301), - mutatedWorkload: getTestWorkload(t), - expErr: "failed to delete Workload", - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - run(t, tt) - }) - } -} - -func setupReadExpectation( - t *testing.T, - mockClient *mockpbresource.ResourceServiceClient_Expecter, - expectedId *pbresource.ID, - existingResource *pbresource.Resource, - sendErr bool) { - - if sendErr { - mockClient.Read(mock.Anything, mock.Anything). - Return(nil, fakeWrappedErr). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.True(t, proto.Equal(expectedId, req.Id)) - }) - } else if existingResource != nil { - mockClient.Read(mock.Anything, mock.Anything). - Return(&pbresource.ReadResponse{ - Resource: existingResource, - }, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.True(t, proto.Equal(expectedId, req.Id)) - }) - } else { - mockClient.Read(mock.Anything, mock.Anything). - Return(nil, status.Error(codes.NotFound, "not found")). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.True(t, proto.Equal(expectedId, req.Id)) - }) - } -} - -func setupWriteExpectation( - t *testing.T, - mockClient *mockpbresource.ResourceServiceClient_Expecter, - expectedResource *pbresource.Resource, - sendErr bool) { - - // If there is no expected resource, we take that to mean we don't expect any client writes. - if expectedResource == nil { - return - } - - if sendErr { - mockClient.Write(mock.Anything, mock.Anything). - Return(nil, fakeWrappedErr). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.WriteRequest) - require.True(t, proto.Equal(expectedResource, req.Resource)) - }) - } else { - mockClient.Write(mock.Anything, mock.Anything). - Return(nil, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.WriteRequest) - require.True(t, proto.Equal(expectedResource, req.Resource)) - }) - } -} - -func setupDeleteExpectation( - t *testing.T, - mockClient *mockpbresource.ResourceServiceClient_Expecter, - expectedResource *pbresource.Resource, - sendErr bool) { - - expectedId := expectedResource.GetId() - - if sendErr { - mockClient.Delete(mock.Anything, mock.Anything). - Return(nil, fakeWrappedErr). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.DeleteRequest) - require.True(t, proto.Equal(expectedId, req.Id)) - }) - } else { - mockClient.Delete(mock.Anything, mock.Anything). - Return(nil, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.DeleteRequest) - require.True(t, proto.Equal(expectedId, req.Id)) - }) - } -} - -func getTestWorkload(t *testing.T) *pbresource.Resource { - return getTestWorkloadWithPort(t, 8300) -} - -func getTestWorkloadWithPort(t *testing.T, port int) *pbresource.Resource { - workload := &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "127.0.0.1", Ports: []string{consulPortNameServer}}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - consulPortNameServer: { - Port: uint32(port), - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - }, - } - data, err := anypb.New(workload) - require.NoError(t, err) - - return &pbresource.Resource{ - Id: getTestWorkloadId(), - Data: data, - Metadata: map[string]string{ - "read_replica": "false", - "raft_version": "3", - "serf_protocol_current": "2", - "serf_protocol_min": "1", - "serf_protocol_max": "5", - "version": "1.18.0", - "grpc_port": "8502", - }, - } -} - -func getTestWorkloadId() *pbresource.ID { - return &pbresource.ID{ - Tenancy: resource.DefaultNamespacedTenancy(), - Type: pbcatalog.WorkloadType, - Name: "consul-server-72af047d-1857-2493-969e-53614a70b25a", - } -} - -func getTestHealthStatus(t *testing.T, passing bool) *pbresource.Resource { - healthStatus := &pbcatalog.HealthStatus{ - Type: string(structs.SerfCheckID), - Description: structs.SerfCheckName, - } - - if passing { - healthStatus.Status = pbcatalog.Health_HEALTH_PASSING - healthStatus.Output = structs.SerfCheckAliveOutput - } else { - healthStatus.Status = pbcatalog.Health_HEALTH_CRITICAL - healthStatus.Output = structs.SerfCheckFailedOutput - } - - data, err := anypb.New(healthStatus) - require.NoError(t, err) - - return &pbresource.Resource{ - Id: getTestHealthstatusId(), - Data: data, - Owner: getTestWorkloadId(), - } -} - -func getTestHealthstatusId() *pbresource.ID { - return &pbresource.ID{ - Tenancy: resource.DefaultNamespacedTenancy(), - Type: pbcatalog.HealthStatusType, - Name: "consul-server-72af047d-1857-2493-969e-53614a70b25a", - } -} - -func getTestSerfMember(status serf.MemberStatus) serf.Member { - return serf.Member{ - Name: "test-server-1", - Addr: net.ParseIP("127.0.0.1"), - Port: 8300, - // representative tags from a local dev deployment of ENT - Tags: map[string]string{ - "vsn_min": "2", - "vsn": "2", - "acls": "1", - "ft_si": "1", - "raft_vsn": "3", - "grpc_port": "8502", - "wan_join_port": "8500", - "dc": "dc1", - "segment": "", - "id": "72af047d-1857-2493-969e-53614a70b25a", - "ft_admpart": "1", - "role": "consul", - "build": "1.18.0", - "ft_ns": "1", - "vsn_max": "3", - "bootstrap": "1", - "expect": "1", - "port": "8300", - }, - Status: status, - ProtocolMin: 1, - ProtocolMax: 5, - ProtocolCur: 2, - DelegateMin: 2, - DelegateMax: 5, - DelegateCur: 4, - } -} - -// Test_ResourceCmpOptions_GeneratedFieldInsensitive makes sure are protocmp options are working as expected. -func Test_ResourceCmpOptions_GeneratedFieldInsensitive(t *testing.T) { - t.Parallel() - - res1 := getTestWorkload(t) - res2 := getTestWorkload(t) - - // Modify the generated fields - res2.Id.Uid = "123456" - res2.Version = "789" - res2.Generation = "millenial" - res2.Status = map[string]*pbresource.Status{ - "foo": {ObservedGeneration: "124"}, - } - - require.True(t, cmp.Equal(res1, res2, resourceCmpOptions...)) - - res1.Metadata["foo"] = "bar" - - require.False(t, cmp.Equal(res1, res2, resourceCmpOptions...)) -} - -// Test gRPC Error Codes Conditions -func Test_grpcNotFoundErr(t *testing.T) { - t.Parallel() - tests := []struct { - name string - err error - expected bool - }{ - { - name: "Nil Error", - }, - { - name: "Nonsense Error", - err: fmt.Errorf("boooooo!"), - }, - { - name: "gRPC Permission Denied Error", - err: status.Error(codes.PermissionDenied, "permission denied is not NotFound"), - }, - { - name: "gRPC NotFound Error", - err: status.Error(codes.NotFound, "bingo: not found"), - expected: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - require.Equal(t, tt.expected, grpcNotFoundErr(tt.err)) - }) - } -} diff --git a/agent/consul/leader_test.go b/agent/consul/leader_test.go index 619d6ae6da..9709e391eb 100644 --- a/agent/consul/leader_test.go +++ b/agent/consul/leader_test.go @@ -14,84 +14,23 @@ import ( "testing" "time" - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-uuid" - "github.com/hashicorp/serf/serf" "github.com/stretchr/testify/require" "google.golang.org/grpc" msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/serf/serf" "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/agent/leafcert" "github.com/hashicorp/consul/agent/structs" tokenStore "github.com/hashicorp/consul/agent/token" "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/internal/resource" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/consul/testrpc" ) -func enableV2(t *testing.T) func(deps *Deps) { - return func(deps *Deps) { - deps.Experiments = []string{"resource-apis"} - m, _ := leafcert.NewTestManager(t, nil) - deps.LeafCertManager = m - } -} - -// Test that Consul service is created in V2. -// In V1, the service is implicitly created - this is covered in leader_registrator_v1_test.go -func Test_InitConsulService(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - t.Parallel() - - dir, s := testServerWithDepsAndConfig(t, enableV2(t), - func(c *Config) { - c.PrimaryDatacenter = "dc1" - c.ACLsEnabled = true - c.ACLInitialManagementToken = "root" - c.ACLResolverSettings.ACLDefaultPolicy = "deny" - }) - defer os.RemoveAll(dir) - defer s.Shutdown() - - testrpc.WaitForRaftLeader(t, s.RPC, "dc1", testrpc.WithToken("root")) - - client := pbresource.NewResourceServiceClient(s.insecureSafeGRPCChan) - - consulServiceID := &pbresource.ID{ - Name: structs.ConsulServiceName, - Type: pbcatalog.ServiceType, - Tenancy: resource.DefaultNamespacedTenancy(), - } - - retry.Run(t, func(r *retry.R) { - res, err := client.Read(context.Background(), &pbresource.ReadRequest{Id: consulServiceID}) - if err != nil { - r.Fatalf("err: %v", err) - } - data := res.GetResource().GetData() - require.NotNil(r, data) - - var service pbcatalog.Service - err = data.UnmarshalTo(&service) - require.NoError(r, err) - - // Spot check the Service - require.Equal(r, service.GetWorkloads().GetPrefixes(), []string{consulWorkloadPrefix}) - require.GreaterOrEqual(r, len(service.GetPorts()), 1) - - //Since we're not running a full agent w/ serf, we can't check for valid endpoints - }) -} - func TestLeader_TombstoneGC_Reset(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") @@ -834,7 +773,7 @@ func TestLeader_ConfigEntryBootstrap_Fail(t *testing.T) { deps := newDefaultDeps(t, config) deps.Logger = logger - srv, err := NewServer(config, deps, grpc.NewServer(), nil, logger, nil) + srv, err := NewServer(config, deps, grpc.NewServer(), nil, logger) require.NoError(t, err) defer srv.Shutdown() diff --git a/agent/consul/options.go b/agent/consul/options.go index 0909bed70a..8c9fe05f48 100644 --- a/agent/consul/options.go +++ b/agent/consul/options.go @@ -6,8 +6,6 @@ package consul import ( "google.golang.org/grpc" - "github.com/hashicorp/consul/lib/stringslice" - "github.com/hashicorp/consul-net-rpc/net/rpc" "github.com/hashicorp/go-hclog" @@ -50,42 +48,6 @@ type Deps struct { EnterpriseDeps } -// UseV1DNS returns true if "v1dns" is present in the Experiments -// array of the agent config. It is ignored if the v2 resource APIs are enabled. -func (d Deps) UseV1DNS() bool { - if stringslice.Contains(d.Experiments, V1DNSExperimentName) && !d.UseV2Resources() { - return true - } - return false -} - -// UseV2Resources returns true if "resource-apis" is present in the Experiments -// array of the agent config. -func (d Deps) UseV2Resources() bool { - if stringslice.Contains(d.Experiments, CatalogResourceExperimentName) { - return true - } - return false -} - -// UseV2Tenancy returns true if "v2tenancy" is present in the Experiments -// array of the agent config. -func (d Deps) UseV2Tenancy() bool { - if stringslice.Contains(d.Experiments, V2TenancyExperimentName) { - return true - } - return false -} - -// HCPAllowV2Resources returns true if "hcp-v2-resource-apis" is present in the Experiments -// array of the agent config. -func (d Deps) HCPAllowV2Resources() bool { - if stringslice.Contains(d.Experiments, HCPAllowV2ResourceAPIs) { - return true - } - return false -} - type GRPCClientConner interface { ClientConn(datacenter string) (*grpc.ClientConn, error) ClientConnLeader() (*grpc.ClientConn, error) diff --git a/agent/consul/prepared_query_endpoint.go b/agent/consul/prepared_query_endpoint.go index 139556bb1a..7c2bf1f960 100644 --- a/agent/consul/prepared_query_endpoint.go +++ b/agent/consul/prepared_query_endpoint.go @@ -562,8 +562,14 @@ func (p *PreparedQuery) execute(query *structs.PreparedQuery, } // Filter out any unhealthy nodes. - nodes = nodes.FilterIgnore(query.Service.OnlyPassing, - query.Service.IgnoreCheckIDs) + filterType := structs.HealthFilterExcludeCritical + if query.Service.OnlyPassing { + filterType = structs.HealthFilterIncludeOnlyPassing + + } + + nodes = nodes.Filter(structs.CheckServiceNodeFilterOptions{FilterType: filterType, + IgnoreCheckIDs: query.Service.IgnoreCheckIDs}) // Apply the node metadata filters, if any. if len(query.Service.NodeMeta) > 0 { diff --git a/agent/consul/replication.go b/agent/consul/replication.go index 08b8811129..ebed1377e1 100644 --- a/agent/consul/replication.go +++ b/agent/consul/replication.go @@ -153,6 +153,12 @@ func (r *Replicator) Run(ctx context.Context) error { // Perform a single round of replication index, exit, err := r.delegate.Replicate(ctx, atomic.LoadUint64(&r.lastRemoteIndex), r.logger) if exit { + metrics.SetGauge([]string{"leader", "replication", r.delegate.MetricName(), "status"}, + 0, + ) + metrics.SetGauge([]string{"leader", "replication", r.delegate.MetricName(), "index"}, + 0, + ) return nil } if err != nil { diff --git a/agent/consul/rpc_test.go b/agent/consul/rpc_test.go index 39351c98ca..11dbeef5db 100644 --- a/agent/consul/rpc_test.go +++ b/agent/consul/rpc_test.go @@ -24,7 +24,6 @@ import ( "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-memdb" "github.com/hashicorp/raft" - "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "google.golang.org/grpc" @@ -232,136 +231,12 @@ func (m *MockSink) Close() error { return nil } +// TestServer_blockingQuery tests authenticated and unauthenticated calls. The +// other blocking query tests reside in blockingquery_test.go in the blockingquery package. func TestServer_blockingQuery(t *testing.T) { t.Parallel() _, s := testServerWithConfig(t) - // Perform a non-blocking query. Note that it's significant that the meta has - // a zero index in response - the implied opts.MinQueryIndex is also zero but - // this should not block still. - t.Run("non-blocking query", func(t *testing.T) { - var opts structs.QueryOptions - var meta structs.QueryMeta - var calls int - fn := func(_ memdb.WatchSet, _ *state.Store) error { - calls++ - return nil - } - err := s.blockingQuery(&opts, &meta, fn) - require.NoError(t, err) - require.Equal(t, 1, calls) - }) - - // Perform a blocking query that gets woken up and loops around once. - t.Run("blocking query - single loop", func(t *testing.T) { - opts := structs.QueryOptions{ - MinQueryIndex: 3, - } - var meta structs.QueryMeta - var calls int - fn := func(ws memdb.WatchSet, _ *state.Store) error { - if calls == 0 { - meta.Index = 3 - - fakeCh := make(chan struct{}) - close(fakeCh) - ws.Add(fakeCh) - } else { - meta.Index = 4 - } - calls++ - return nil - } - err := s.blockingQuery(&opts, &meta, fn) - require.NoError(t, err) - require.Equal(t, 2, calls) - }) - - // Perform a blocking query that returns a zero index from blocking func (e.g. - // no state yet). This should still return an empty response immediately, but - // with index of 1 and then block on the next attempt. In one sense zero index - // is not really a valid response from a state method that is not an error but - // in practice a lot of state store operations do return it unless they - // explicitly special checks to turn 0 into 1. Often this is not caught or - // covered by tests but eventually when hit in the wild causes blocking - // clients to busy loop and burn CPU. This test ensure that blockingQuery - // systematically does the right thing to prevent future bugs like that. - t.Run("blocking query with 0 modifyIndex from state func", func(t *testing.T) { - opts := structs.QueryOptions{ - MinQueryIndex: 0, - } - var meta structs.QueryMeta - var calls int - fn := func(ws memdb.WatchSet, _ *state.Store) error { - if opts.MinQueryIndex > 0 { - // If client requested blocking, block forever. This is simulating - // waiting for the watched resource to be initialized/written to giving - // it a non-zero index. Note the timeout on the query options is relied - // on to stop the test taking forever. - fakeCh := make(chan struct{}) - ws.Add(fakeCh) - } - meta.Index = 0 - calls++ - return nil - } - require.NoError(t, s.blockingQuery(&opts, &meta, fn)) - assert.Equal(t, 1, calls) - assert.Equal(t, uint64(1), meta.Index, - "expect fake index of 1 to force client to block on next update") - - // Simulate client making next request - opts.MinQueryIndex = 1 - opts.MaxQueryTime = 20 * time.Millisecond // Don't wait too long - - // This time we should block even though the func returns index 0 still - t0 := time.Now() - require.NoError(t, s.blockingQuery(&opts, &meta, fn)) - t1 := time.Now() - assert.Equal(t, 2, calls) - assert.Equal(t, uint64(1), meta.Index, - "expect fake index of 1 to force client to block on next update") - assert.True(t, t1.Sub(t0) > 20*time.Millisecond, - "should have actually blocked waiting for timeout") - - }) - - // Perform a query that blocks and gets interrupted when the state store - // is abandoned. - t.Run("blocking query interrupted by abandonCh", func(t *testing.T) { - opts := structs.QueryOptions{ - MinQueryIndex: 3, - } - var meta structs.QueryMeta - var calls int - fn := func(_ memdb.WatchSet, _ *state.Store) error { - if calls == 0 { - meta.Index = 3 - - snap, err := s.fsm.Snapshot() - if err != nil { - t.Fatalf("err: %v", err) - } - defer snap.Release() - - buf := bytes.NewBuffer(nil) - sink := &MockSink{buf, false} - if err := snap.Persist(sink); err != nil { - t.Fatalf("err: %v", err) - } - - if err := s.fsm.Restore(sink); err != nil { - t.Fatalf("err: %v", err) - } - } - calls++ - return nil - } - err := s.blockingQuery(&opts, &meta, fn) - require.NoError(t, err) - require.Equal(t, 1, calls) - }) - t.Run("ResultsFilteredByACLs is reset for unauthenticated calls", func(t *testing.T) { opts := structs.QueryOptions{ Token: "", @@ -394,93 +269,6 @@ func TestServer_blockingQuery(t *testing.T) { require.NoError(t, err) require.True(t, meta.ResultsFilteredByACLs, "ResultsFilteredByACLs should be honored for authenticated calls") }) - - t.Run("non-blocking query for item that does not exist", func(t *testing.T) { - opts := structs.QueryOptions{} - meta := structs.QueryMeta{} - calls := 0 - fn := func(_ memdb.WatchSet, _ *state.Store) error { - calls++ - return errNotFound - } - - err := s.blockingQuery(&opts, &meta, fn) - require.NoError(t, err) - require.Equal(t, 1, calls) - }) - - t.Run("blocking query for item that does not exist", func(t *testing.T) { - opts := structs.QueryOptions{MinQueryIndex: 3, MaxQueryTime: 100 * time.Millisecond} - meta := structs.QueryMeta{} - calls := 0 - fn := func(ws memdb.WatchSet, _ *state.Store) error { - calls++ - if calls == 1 { - meta.Index = 3 - - ch := make(chan struct{}) - close(ch) - ws.Add(ch) - return errNotFound - } - meta.Index = 5 - return errNotFound - } - - err := s.blockingQuery(&opts, &meta, fn) - require.NoError(t, err) - require.Equal(t, 2, calls) - }) - - t.Run("blocking query for item that existed and is removed", func(t *testing.T) { - opts := structs.QueryOptions{MinQueryIndex: 3, MaxQueryTime: 100 * time.Millisecond} - meta := structs.QueryMeta{} - calls := 0 - fn := func(ws memdb.WatchSet, _ *state.Store) error { - calls++ - if calls == 1 { - meta.Index = 3 - - ch := make(chan struct{}) - close(ch) - ws.Add(ch) - return nil - } - meta.Index = 5 - return errNotFound - } - - start := time.Now() - err := s.blockingQuery(&opts, &meta, fn) - require.True(t, time.Since(start) < opts.MaxQueryTime, "query timed out") - require.NoError(t, err) - require.Equal(t, 2, calls) - }) - - t.Run("blocking query for non-existent item that is created", func(t *testing.T) { - opts := structs.QueryOptions{MinQueryIndex: 3, MaxQueryTime: 100 * time.Millisecond} - meta := structs.QueryMeta{} - calls := 0 - fn := func(ws memdb.WatchSet, _ *state.Store) error { - calls++ - if calls == 1 { - meta.Index = 3 - - ch := make(chan struct{}) - close(ch) - ws.Add(ch) - return errNotFound - } - meta.Index = 5 - return nil - } - - start := time.Now() - err := s.blockingQuery(&opts, &meta, fn) - require.True(t, time.Since(start) < opts.MaxQueryTime, "query timed out") - require.NoError(t, err) - require.Equal(t, 2, calls) - }) } func TestRPC_ReadyForConsistentReads(t *testing.T) { diff --git a/agent/consul/rtt.go b/agent/consul/rtt.go index 1599301e15..ca1ebc3610 100644 --- a/agent/consul/rtt.go +++ b/agent/consul/rtt.go @@ -8,7 +8,7 @@ import ( "sort" "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/internal/gossip/librtt" ) // nodeSorter takes a list of nodes and a parallel vector of distances and @@ -21,7 +21,7 @@ type nodeSorter struct { // newNodeSorter returns a new sorter for the given source coordinate and set of // nodes. -func (s *Server) newNodeSorter(cs lib.CoordinateSet, nodes structs.Nodes) (sort.Interface, error) { +func (s *Server) newNodeSorter(cs librtt.CoordinateSet, nodes structs.Nodes) (sort.Interface, error) { state := s.fsm.State() vec := make([]float64, len(nodes)) for i, node := range nodes { @@ -30,7 +30,7 @@ func (s *Server) newNodeSorter(cs lib.CoordinateSet, nodes structs.Nodes) (sort. return nil, err } c1, c2 := cs.Intersect(other) - vec[i] = lib.ComputeDistance(c1, c2) + vec[i] = librtt.ComputeDistance(c1, c2) } return &nodeSorter{nodes, vec}, nil } @@ -61,7 +61,7 @@ type serviceNodeSorter struct { // newServiceNodeSorter returns a new sorter for the given source coordinate and // set of service nodes. -func (s *Server) newServiceNodeSorter(cs lib.CoordinateSet, nodes structs.ServiceNodes) (sort.Interface, error) { +func (s *Server) newServiceNodeSorter(cs librtt.CoordinateSet, nodes structs.ServiceNodes) (sort.Interface, error) { state := s.fsm.State() vec := make([]float64, len(nodes)) for i, node := range nodes { @@ -70,7 +70,7 @@ func (s *Server) newServiceNodeSorter(cs lib.CoordinateSet, nodes structs.Servic return nil, err } c1, c2 := cs.Intersect(other) - vec[i] = lib.ComputeDistance(c1, c2) + vec[i] = librtt.ComputeDistance(c1, c2) } return &serviceNodeSorter{nodes, vec}, nil } @@ -101,7 +101,7 @@ type healthCheckSorter struct { // newHealthCheckSorter returns a new sorter for the given source coordinate and // set of health checks with nodes. -func (s *Server) newHealthCheckSorter(cs lib.CoordinateSet, checks structs.HealthChecks) (sort.Interface, error) { +func (s *Server) newHealthCheckSorter(cs librtt.CoordinateSet, checks structs.HealthChecks) (sort.Interface, error) { state := s.fsm.State() vec := make([]float64, len(checks)) for i, check := range checks { @@ -110,7 +110,7 @@ func (s *Server) newHealthCheckSorter(cs lib.CoordinateSet, checks structs.Healt return nil, err } c1, c2 := cs.Intersect(other) - vec[i] = lib.ComputeDistance(c1, c2) + vec[i] = librtt.ComputeDistance(c1, c2) } return &healthCheckSorter{checks, vec}, nil } @@ -141,7 +141,7 @@ type checkServiceNodeSorter struct { // newCheckServiceNodeSorter returns a new sorter for the given source coordinate // and set of nodes with health checks. -func (s *Server) newCheckServiceNodeSorter(cs lib.CoordinateSet, nodes structs.CheckServiceNodes) (sort.Interface, error) { +func (s *Server) newCheckServiceNodeSorter(cs librtt.CoordinateSet, nodes structs.CheckServiceNodes) (sort.Interface, error) { state := s.fsm.State() vec := make([]float64, len(nodes)) for i, node := range nodes { @@ -150,7 +150,7 @@ func (s *Server) newCheckServiceNodeSorter(cs lib.CoordinateSet, nodes structs.C return nil, err } c1, c2 := cs.Intersect(other) - vec[i] = lib.ComputeDistance(c1, c2) + vec[i] = librtt.ComputeDistance(c1, c2) } return &checkServiceNodeSorter{nodes, vec}, nil } @@ -172,7 +172,7 @@ func (n *checkServiceNodeSorter) Less(i, j int) bool { } // newSorterByDistanceFrom returns a sorter for the given type. -func (s *Server) newSorterByDistanceFrom(cs lib.CoordinateSet, subj interface{}) (sort.Interface, error) { +func (s *Server) newSorterByDistanceFrom(cs librtt.CoordinateSet, subj interface{}) (sort.Interface, error) { switch v := subj.(type) { case structs.Nodes: return s.newNodeSorter(cs, v) diff --git a/agent/consul/rtt_test.go b/agent/consul/rtt_test.go index aeed0b66f5..1f4a19ddfe 100644 --- a/agent/consul/rtt_test.go +++ b/agent/consul/rtt_test.go @@ -10,12 +10,12 @@ import ( "testing" "time" - "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/lib" - "github.com/hashicorp/consul/testrpc" - - "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" + msgpackrpc "github.com/hashicorp/consul-net-rpc/net-rpc-msgpackrpc" "github.com/hashicorp/consul-net-rpc/net/rpc" + + "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/internal/gossip/librtt" + "github.com/hashicorp/consul/testrpc" ) // verifyNodeSort makes sure the order of the nodes in the slice is the same as @@ -98,27 +98,27 @@ func seedCoordinates(t *testing.T, codec rpc.ClientCodec, server *Server) { { Datacenter: "dc1", Node: "node1", - Coord: lib.GenerateCoordinate(10 * time.Millisecond), + Coord: librtt.GenerateCoordinate(10 * time.Millisecond), }, { Datacenter: "dc1", Node: "node2", - Coord: lib.GenerateCoordinate(2 * time.Millisecond), + Coord: librtt.GenerateCoordinate(2 * time.Millisecond), }, { Datacenter: "dc1", Node: "node3", - Coord: lib.GenerateCoordinate(1 * time.Millisecond), + Coord: librtt.GenerateCoordinate(1 * time.Millisecond), }, { Datacenter: "dc1", Node: "node4", - Coord: lib.GenerateCoordinate(8 * time.Millisecond), + Coord: librtt.GenerateCoordinate(8 * time.Millisecond), }, { Datacenter: "dc1", Node: "node5", - Coord: lib.GenerateCoordinate(3 * time.Millisecond), + Coord: librtt.GenerateCoordinate(3 * time.Millisecond), }, } diff --git a/agent/consul/server.go b/agent/consul/server.go index cdd1904130..979d9e3cd4 100644 --- a/agent/consul/server.go +++ b/agent/consul/server.go @@ -63,24 +63,18 @@ import ( "github.com/hashicorp/consul/agent/rpc/peering" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/token" - "github.com/hashicorp/consul/internal/auth" - "github.com/hashicorp/consul/internal/catalog" "github.com/hashicorp/consul/internal/controller" + "github.com/hashicorp/consul/internal/gossip/librtt" hcpctl "github.com/hashicorp/consul/internal/hcp" - "github.com/hashicorp/consul/internal/mesh" - proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot" "github.com/hashicorp/consul/internal/multicluster" "github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/resource/demo" "github.com/hashicorp/consul/internal/resource/reaper" "github.com/hashicorp/consul/internal/storage" raftstorage "github.com/hashicorp/consul/internal/storage/raft" - "github.com/hashicorp/consul/internal/tenancy" "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/lib/routine" - "github.com/hashicorp/consul/lib/stringslice" "github.com/hashicorp/consul/logging" - "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1/pbproxystate" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/tlsutil" "github.com/hashicorp/consul/types" @@ -130,26 +124,9 @@ const ( // and wait for a periodic reconcile. reconcileChSize = 256 - LeaderTransferMinVersion = "1.6.0" - CatalogResourceExperimentName = "resource-apis" - V1DNSExperimentName = "v1dns" - V2TenancyExperimentName = "v2tenancy" - HCPAllowV2ResourceAPIs = "hcp-v2-resource-apis" + LeaderTransferMinVersion = "1.6.0" ) -// IsExperimentAllowedOnSecondaries returns true if an experiment is currently -// disallowed for wan federated secondary datacenters. -// -// Likely these will all be short lived exclusions. -func IsExperimentAllowedOnSecondaries(name string) bool { - switch name { - case CatalogResourceExperimentName, V2TenancyExperimentName: - return false - default: - return true - } -} - const ( aclPolicyReplicationRoutineName = "ACL policy replication" aclRoleReplicationRoutineName = "ACL role replication" @@ -474,15 +451,6 @@ type Server struct { reportingManager *reporting.ReportingManager registry resource.Registry - - useV2Resources bool - - // useV2Tenancy is tied to the "v2tenancy" feature flag. - useV2Tenancy bool - - // whether v2 resources are enabled for use with HCP - // TODO(CC-6389): Remove once resource-apis is no longer considered experimental and is supported by HCP - hcpAllowV2Resources bool } func (s *Server) DecrementBlockingQueries() uint64 { @@ -504,22 +472,10 @@ type connHandler interface { Shutdown() error } -// ProxyUpdater is an interface for ProxyTracker. -type ProxyUpdater interface { - // PushChange allows pushing a computed ProxyState to xds for xds resource generation to send to a proxy. - PushChange(id *pbresource.ID, snapshot proxysnapshot.ProxySnapshot) error - - // ProxyConnectedToServer returns whether this id is connected to this server. If it is connected, it also returns - // the token as the first argument. - ProxyConnectedToServer(id *pbresource.ID) (string, bool) - - EventChannel() chan controller.Event -} - // NewServer is used to construct a new Consul server from the configuration // and extra options, potentially returning an error. func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server, - incomingRPCLimiter rpcRate.RequestLimitsHandler, serverLogger hclog.InterceptLogger, proxyUpdater ProxyUpdater) (*Server, error) { + incomingRPCLimiter rpcRate.RequestLimitsHandler, serverLogger hclog.InterceptLogger) (*Server, error) { logger := flat.Logger if err := config.CheckProtocolVersion(); err != nil { return nil, err @@ -572,9 +528,6 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server, incomingRPCLimiter: incomingRPCLimiter, routineManager: routine.NewManager(logger.Named(logging.ConsulServer)), registry: flat.Registry, - useV2Resources: flat.UseV2Resources(), - useV2Tenancy: flat.UseV2Tenancy(), - hcpAllowV2Resources: flat.HCPAllowV2Resources(), } incomingRPCLimiter.Register(s) @@ -636,15 +589,7 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server, rpcServerOpts := []func(*rpc.Server){ rpc.WithPreBodyInterceptor( - middleware.ChainedRPCPreBodyInterceptor( - func(reqServiceMethod string, sourceAddr net.Addr) error { - if s.useV2Resources && isV1CatalogRequest(reqServiceMethod) { - return structs.ErrUsingV2CatalogExperiment - } - return nil - }, - middleware.GetNetRPCRateLimitingInterceptor(s.incomingRPCLimiter, middleware.NewPanicHandler(s.logger)), - ), + middleware.GetNetRPCRateLimitingInterceptor(s.incomingRPCLimiter, middleware.NewPanicHandler(s.logger)), ), } @@ -747,7 +692,7 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server, } // Initialize the Raft server. - if err := s.setupRaft(stringslice.Contains(flat.Experiments, CatalogResourceExperimentName)); err != nil { + if err := s.setupRaft(); err != nil { s.Shutdown() return nil, fmt.Errorf("Failed to start Raft: %v", err) } @@ -925,7 +870,7 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server, pbresource.NewResourceServiceClient(s.insecureUnsafeGRPCChan), s.loggers.Named(logging.ControllerRuntime), ) - if err := s.registerControllers(flat, proxyUpdater); err != nil { + if err := s.registerControllers(flat); err != nil { return nil, err } go s.controllerManager.Run(&lib.StopChannelContext{StopCh: shutdownCh}) @@ -943,22 +888,12 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server, // as establishing leadership could attempt to use autopilot and cause a panic. s.initAutopilot(config) - // Construct the registrator that makes sense for the catalog version - if s.useV2Resources { - s.registrator = V2ConsulRegistrator{ - Logger: serverLogger, - NodeName: s.config.NodeName, - EntMeta: s.config.AgentEnterpriseMeta(), - Client: pbresource.NewResourceServiceClient(s.insecureSafeGRPCChan), - } - } else { - s.registrator = V1ConsulRegistrator{ - Datacenter: s.config.Datacenter, - FSM: s.fsm, - Logger: serverLogger, - NodeName: s.config.NodeName, - RaftApplyFunc: s.raftApplyMsgpack, - } + s.registrator = V1ConsulRegistrator{ + Datacenter: s.config.Datacenter, + FSM: s.fsm, + Logger: serverLogger, + NodeName: s.config.NodeName, + RaftApplyFunc: s.raftApplyMsgpack, } // Start monitoring leadership. This must happen after Serf is set up @@ -993,86 +928,17 @@ func NewServer(config *Config, flat Deps, externalGRPCServer *grpc.Server, return s, nil } -func isV1CatalogRequest(rpcName string) bool { - switch { - case strings.HasPrefix(rpcName, "Catalog."), - strings.HasPrefix(rpcName, "Health."), - strings.HasPrefix(rpcName, "ConfigEntry."): - return true - } - - switch rpcName { - case "Internal.EventFire", "Internal.KeyringOperation", "Internal.OIDCAuthMethods": - return false - default: - if strings.HasPrefix(rpcName, "Internal.") { - return true - } - return false - } -} - -func (s *Server) registerControllers(deps Deps, proxyUpdater ProxyUpdater) error { +func (s *Server) registerControllers(deps Deps) error { if s.config.Cloud.IsConfigured() { hcpctl.RegisterControllers( s.controllerManager, hcpctl.ControllerDependencies{ - ResourceApisEnabled: s.useV2Resources, - HCPAllowV2ResourceApis: s.hcpAllowV2Resources, - CloudConfig: deps.HCP.Config, + CloudConfig: deps.HCP.Config, }, ) } - // When not enabled, the v1 tenancy bridge is used by default. - if s.useV2Tenancy { - tenancy.RegisterControllers( - s.controllerManager, - tenancy.Dependencies{Registry: deps.Registry}, - ) - } - - if s.useV2Resources { - catalog.RegisterControllers(s.controllerManager) - defaultAllow, err := s.config.ACLResolverSettings.IsDefaultAllow() - if err != nil { - return err - } - - mesh.RegisterControllers(s.controllerManager, mesh.ControllerDependencies{ - TrustBundleFetcher: func() (*pbproxystate.TrustBundle, error) { - var bundle pbproxystate.TrustBundle - roots, err := s.getCARoots(nil, s.GetState()) - if err != nil { - return nil, err - } - bundle.TrustDomain = roots.TrustDomain - for _, root := range roots.Roots { - bundle.Roots = append(bundle.Roots, root.RootCert) - } - return &bundle, nil - }, - // This function is adapted from server_connect.go:getCARoots. - TrustDomainFetcher: func() (string, error) { - _, caConfig, err := s.fsm.State().CAConfig(nil) - if err != nil { - return "", err - } - - return s.getTrustDomain(caConfig) - }, - - LeafCertManager: deps.LeafCertManager, - LocalDatacenter: s.config.Datacenter, - DefaultAllow: defaultAllow, - ProxyUpdater: proxyUpdater, - }) - - auth.RegisterControllers(s.controllerManager, auth.DefaultControllerDependencies()) - multicluster.RegisterControllers(s.controllerManager) - } else { - shim := NewExportedServicesShim(s) - multicluster.RegisterCompatControllers(s.controllerManager, multicluster.DefaultCompatControllerDependencies(shim)) - } + shim := NewExportedServicesShim(s) + multicluster.RegisterCompatControllers(s.controllerManager, multicluster.DefaultCompatControllerDependencies(shim)) reaper.RegisterControllers(s.controllerManager) @@ -1109,7 +975,7 @@ func (s *Server) connectCARootsMonitor(ctx context.Context) { } // setupRaft is used to setup and initialize Raft -func (s *Server) setupRaft(isCatalogResourceExperiment bool) error { +func (s *Server) setupRaft() error { // If we have an unclean exit then attempt to close the Raft store. defer func() { if s.raft == nil && s.raftStore != nil { @@ -1190,7 +1056,7 @@ func (s *Server) setupRaft(isCatalogResourceExperiment bool) error { return nil } // Only use WAL if there is no existing raft.db, even if it's enabled. - if s.config.LogStoreConfig.Backend == LogStoreBackendDefault && !boltFileExists && isCatalogResourceExperiment { + if s.config.LogStoreConfig.Backend == LogStoreBackendDefault && !boltFileExists { s.config.LogStoreConfig.Backend = LogStoreBackendWAL if !s.config.LogStoreConfig.Verification.Enabled { s.config.LogStoreConfig.Verification.Enabled = true @@ -1958,13 +1824,13 @@ func (s *Server) Stats() map[string]map[string]string { // are ancillary members of. // // NOTE: This assumes coordinates are enabled, so check that before calling. -func (s *Server) GetLANCoordinate() (lib.CoordinateSet, error) { +func (s *Server) GetLANCoordinate() (librtt.CoordinateSet, error) { lan, err := s.serfLAN.GetCoordinate() if err != nil { return nil, err } - cs := lib.CoordinateSet{"": lan} + cs := librtt.CoordinateSet{"": lan} if err := s.addEnterpriseLANCoordinates(cs); err != nil { return nil, err } diff --git a/agent/consul/server_ce.go b/agent/consul/server_ce.go index ac0df9dd73..dae8dc1516 100644 --- a/agent/consul/server_ce.go +++ b/agent/consul/server_ce.go @@ -20,8 +20,8 @@ import ( "github.com/hashicorp/consul/agent/consul/reporting" resourcegrpc "github.com/hashicorp/consul/agent/grpc-external/services/resource" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/internal/resource" - "github.com/hashicorp/consul/lib" "github.com/hashicorp/consul/logging" ) @@ -117,7 +117,7 @@ func (s *Server) GetMatchingLANCoordinate(_, _ string) (*coordinate.Coordinate, return s.serfLAN.GetCoordinate() } -func (s *Server) addEnterpriseLANCoordinates(cs lib.CoordinateSet) error { +func (s *Server) addEnterpriseLANCoordinates(cs librtt.CoordinateSet) error { return nil } @@ -205,6 +205,5 @@ func (s *Server) newResourceServiceConfig(typeRegistry resource.Registry, resolv ACLResolver: resolver, Logger: s.loggers.Named(logging.GRPCAPI).Named(logging.Resource), TenancyBridge: tenancyBridge, - UseV2Tenancy: s.useV2Tenancy, } } diff --git a/agent/consul/server_grpc.go b/agent/consul/server_grpc.go index a4ff866095..a190c44a05 100644 --- a/agent/consul/server_grpc.go +++ b/agent/consul/server_grpc.go @@ -29,8 +29,6 @@ import ( "github.com/hashicorp/consul/agent/rpc/peering" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/internal/resource" - "github.com/hashicorp/consul/internal/tenancy" - "github.com/hashicorp/consul/lib/stringslice" "github.com/hashicorp/consul/logging" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/proto/private/pbsubscribe" @@ -316,7 +314,6 @@ func (s *Server) setupGRPCServices(config *Config, deps Deps) error { // for anything internal in Consul to use the service. If that changes // we could register it on the in-process interfaces as well. err = s.registerDataplaneServer( - deps, s.externalGRPCServer, ) if err != nil { @@ -344,20 +341,7 @@ func (s *Server) registerResourceServiceServer(typeRegistry resource.Registry, r return fmt.Errorf("storage backend cannot be nil") } - var tenancyBridge resourcegrpc.TenancyBridge - if s.useV2Tenancy { - tenancyBridge = tenancy.NewV2TenancyBridge().WithClient( - // This assumes that the resource service will be registered with - // the insecureUnsafeGRPCChan. We are using the insecure and unsafe - // channel here because the V2 Tenancy bridge only reads data - // from the client and does not modify it. Therefore sharing memory - // with the resource services canonical immutable data is advantageous - // to prevent wasting CPU time for every resource op to clone things. - pbresource.NewResourceServiceClient(s.insecureUnsafeGRPCChan), - ) - } else { - tenancyBridge = NewV1TenancyBridge(s) - } + tenancyBridge := NewV1TenancyBridge(s) // Create the Resource Service Server srv := resourcegrpc.NewServer(s.newResourceServiceConfig(typeRegistry, resolver, tenancyBridge)) @@ -510,14 +494,12 @@ func (s *Server) registerConnectCAServer(registrars ...grpc.ServiceRegistrar) er return nil } -func (s *Server) registerDataplaneServer(deps Deps, registrars ...grpc.ServiceRegistrar) error { +func (s *Server) registerDataplaneServer(registrars ...grpc.ServiceRegistrar) error { srv := dataplane.NewServer(dataplane.Config{ - GetStore: func() dataplane.StateStore { return s.FSM().State() }, - Logger: s.loggers.Named(logging.GRPCAPI).Named(logging.Dataplane), - ACLResolver: s.ACLResolver, - Datacenter: s.config.Datacenter, - EnableV2: stringslice.Contains(deps.Experiments, CatalogResourceExperimentName), - ResourceAPIClient: pbresource.NewResourceServiceClient(s.insecureSafeGRPCChan), + GetStore: func() dataplane.StateStore { return s.FSM().State() }, + Logger: s.loggers.Named(logging.GRPCAPI).Named(logging.Dataplane), + ACLResolver: s.ACLResolver, + Datacenter: s.config.Datacenter, }) for _, reg := range registrars { diff --git a/agent/consul/server_serf.go b/agent/consul/server_serf.go index aea50aa6dd..ec2ad94688 100644 --- a/agent/consul/server_serf.go +++ b/agent/consul/server_serf.go @@ -12,6 +12,7 @@ import ( "time" "github.com/armon/go-metrics" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/memberlist" "github.com/hashicorp/raft" @@ -20,8 +21,8 @@ import ( "github.com/hashicorp/consul/agent/consul/wanfed" "github.com/hashicorp/consul/agent/metadata" "github.com/hashicorp/consul/agent/structs" + "github.com/hashicorp/consul/internal/gossip/libserf" "github.com/hashicorp/consul/lib" - libserf "github.com/hashicorp/consul/lib/serf" "github.com/hashicorp/consul/logging" "github.com/hashicorp/consul/types" ) diff --git a/agent/consul/server_test.go b/agent/consul/server_test.go index e685f25ca4..f157fa6dd5 100644 --- a/agent/consul/server_test.go +++ b/agent/consul/server_test.go @@ -19,10 +19,6 @@ import ( "github.com/armon/go-metrics" "github.com/google/tcpproxy" - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-uuid" - "github.com/hashicorp/memberlist" - "github.com/hashicorp/raft" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "golang.org/x/time/rate" @@ -30,6 +26,10 @@ import ( "google.golang.org/grpc/keepalive" "github.com/hashicorp/consul-net-rpc/net/rpc" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/memberlist" + "github.com/hashicorp/raft" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/consul/multilimiter" @@ -43,7 +43,6 @@ import ( "github.com/hashicorp/consul/agent/rpc/middleware" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/token" - proxytracker "github.com/hashicorp/consul/internal/mesh/proxy-tracker" "github.com/hashicorp/consul/ipaddr" "github.com/hashicorp/consul/sdk/freeport" "github.com/hashicorp/consul/sdk/testutil" @@ -352,8 +351,7 @@ func newServerWithDeps(t testutil.TestingTB, c *Config, deps Deps) (*Server, err } } grpcServer := external.NewServer(deps.Logger.Named("grpc.external"), nil, deps.TLSConfigurator, rpcRate.NullRequestLimitsHandler(), keepalive.ServerParameters{}, nil) - proxyUpdater := proxytracker.NewProxyTracker(proxytracker.ProxyTrackerConfig{}) - srv, err := NewServer(c, deps, grpcServer, nil, deps.Logger, proxyUpdater) + srv, err := NewServer(c, deps, grpcServer, nil, deps.Logger) if err != nil { return nil, err } @@ -1260,7 +1258,7 @@ func TestServer_RPC_MetricsIntercept_Off(t *testing.T) { } } - s1, err := NewServer(conf, deps, grpc.NewServer(), nil, deps.Logger, nil) + s1, err := NewServer(conf, deps, grpc.NewServer(), nil, deps.Logger) if err != nil { t.Fatalf("err: %v", err) } @@ -1298,7 +1296,7 @@ func TestServer_RPC_MetricsIntercept_Off(t *testing.T) { return nil } - s2, err := NewServer(conf, deps, grpc.NewServer(), nil, deps.Logger, nil) + s2, err := NewServer(conf, deps, grpc.NewServer(), nil, deps.Logger) if err != nil { t.Fatalf("err: %v", err) } @@ -1332,7 +1330,7 @@ func TestServer_RPC_RequestRecorder(t *testing.T) { deps := newDefaultDeps(t, conf) deps.NewRequestRecorderFunc = nil - s1, err := NewServer(conf, deps, grpc.NewServer(), nil, deps.Logger, nil) + s1, err := NewServer(conf, deps, grpc.NewServer(), nil, deps.Logger) require.Error(t, err, "need err when provider func is nil") require.Equal(t, err.Error(), "cannot initialize server without an RPC request recorder provider") @@ -1351,7 +1349,7 @@ func TestServer_RPC_RequestRecorder(t *testing.T) { return nil } - s2, err := NewServer(conf, deps, grpc.NewServer(), nil, deps.Logger, nil) + s2, err := NewServer(conf, deps, grpc.NewServer(), nil, deps.Logger) require.Error(t, err, "need err when RequestRecorder is nil") require.Equal(t, err.Error(), "cannot initialize server with a nil RPC request recorder") @@ -2315,7 +2313,7 @@ func TestServer_ControllerDependencies(t *testing.T) { _, conf := testServerConfig(t) deps := newDefaultDeps(t, conf) - deps.Experiments = []string{"resource-apis", "v2tenancy"} + deps.Experiments = []string{"resource-apis"} deps.LeafCertManager = &leafcert.Manager{} s1, err := newServerWithDeps(t, conf, deps) @@ -2325,6 +2323,10 @@ func TestServer_ControllerDependencies(t *testing.T) { // gotest.tools/v3 defines CLI flags which are incompatible wit the golden package // Once we eliminate gotest.tools/v3 from usage within Consul we could uncomment this // actual := fmt.Sprintf("```mermaid\n%s\n```", s1.controllerManager.CalculateDependencies(s1.registry.Types()).ToMermaid()) - // expected := golden.Get(t, actual, "v2-resource-dependencies") + // markdownFileName := "v2-resource-dependencies" + // if versiontest.IsEnterprise() { + // markdownFileName += "-enterprise" + // } + // expected := golden.Get(t, actual, markdownFileName) // require.Equal(t, expected, actual) } diff --git a/agent/consul/state/catalog.go b/agent/consul/state/catalog.go index ce40fda3e0..dcfe4ec91f 100644 --- a/agent/consul/state/catalog.go +++ b/agent/consul/state/catalog.go @@ -942,7 +942,7 @@ func ensureServiceTxn(tx WriteTxn, idx uint64, node string, preserveIndexes bool } if conf != nil { termGatewayConf := conf.(*structs.TerminatingGatewayConfigEntry) - addrs, err := getTermGatewayVirtualIPs(tx, idx, termGatewayConf.Services, &svc.EnterpriseMeta) + addrs, err := getTermGatewayVirtualIPs(tx, idx, termGatewayConf.Services) if err != nil { return err } @@ -3585,11 +3585,10 @@ func getTermGatewayVirtualIPs( tx WriteTxn, idx uint64, services []structs.LinkedService, - entMeta *acl.EnterpriseMeta, ) (map[string]structs.ServiceAddress, error) { addrs := make(map[string]structs.ServiceAddress, len(services)) for _, s := range services { - sn := structs.ServiceName{Name: s.Name, EnterpriseMeta: *entMeta} + sn := structs.ServiceName{Name: s.Name, EnterpriseMeta: s.EnterpriseMeta} // Terminating Gateways cannot route to services in peered clusters psn := structs.PeeredServiceName{ServiceName: sn, Peer: structs.DefaultPeerKeyword} vip, err := assignServiceVirtualIP(tx, idx, psn) @@ -3606,7 +3605,7 @@ func getTermGatewayVirtualIPs( func updateTerminatingGatewayVirtualIPs(tx WriteTxn, idx uint64, conf *structs.TerminatingGatewayConfigEntry, entMeta *acl.EnterpriseMeta) error { // Build the current map of services with virtual IPs for this gateway services := conf.Services - addrs, err := getTermGatewayVirtualIPs(tx, idx, services, entMeta) + addrs, err := getTermGatewayVirtualIPs(tx, idx, services) if err != nil { return err } diff --git a/agent/consul/state/catalog_schema.deepcopy.go b/agent/consul/state/catalog_schema.deepcopy.go index af4d430d2f..43937edd87 100644 --- a/agent/consul/state/catalog_schema.deepcopy.go +++ b/agent/consul/state/catalog_schema.deepcopy.go @@ -1,7 +1,4 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -// generated by deep-copy -pointer-receiver -o ./catalog_schema.deepcopy.go -type upstreamDownstream ./; DO NOT EDIT. +// Code generated by deep-copy -pointer-receiver -o ./catalog_schema.deepcopy.go -type upstreamDownstream ./; DO NOT EDIT. package state diff --git a/agent/consul/state/coordinate.go b/agent/consul/state/coordinate.go index bcd71e5a0f..90983708bf 100644 --- a/agent/consul/state/coordinate.go +++ b/agent/consul/state/coordinate.go @@ -11,7 +11,7 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/internal/gossip/librtt" ) const tableCoordinates = "coordinates" @@ -117,7 +117,7 @@ func (s *Restore) Coordinates(idx uint64, updates structs.Coordinates) error { // Coordinate returns a map of coordinates for the given node, indexed by // network segment. -func (s *Store) Coordinate(ws memdb.WatchSet, node string, entMeta *acl.EnterpriseMeta) (uint64, lib.CoordinateSet, error) { +func (s *Store) Coordinate(ws memdb.WatchSet, node string, entMeta *acl.EnterpriseMeta) (uint64, librtt.CoordinateSet, error) { tx := s.db.Txn(false) defer tx.Abort() @@ -137,7 +137,7 @@ func (s *Store) Coordinate(ws memdb.WatchSet, node string, entMeta *acl.Enterpri } ws.Add(iter.WatchCh()) - results := make(lib.CoordinateSet) + results := make(librtt.CoordinateSet) for raw := iter.Next(); raw != nil; raw = iter.Next() { coord := raw.(*structs.Coordinate) results[coord.Segment] = coord.Coord diff --git a/agent/consul/state/coordinate_test.go b/agent/consul/state/coordinate_test.go index dad0ce3e32..408b06e4c0 100644 --- a/agent/consul/state/coordinate_test.go +++ b/agent/consul/state/coordinate_test.go @@ -8,12 +8,13 @@ import ( "math/rand" "testing" - "github.com/hashicorp/go-memdb" - "github.com/hashicorp/serf/coordinate" "github.com/stretchr/testify/require" + "github.com/hashicorp/go-memdb" + "github.com/hashicorp/serf/coordinate" + "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/sdk/testutil" ) @@ -52,7 +53,7 @@ func TestStateStore_Coordinate_Updates(t *testing.T) { coordinateWs := memdb.NewWatchSet() _, coords, err := s.Coordinate(coordinateWs, "nope", nil) require.NoError(t, err) - require.Equal(t, lib.CoordinateSet{}, coords) + require.Equal(t, librtt.CoordinateSet{}, coords) // Make an update for nodes that don't exist and make sure they get // ignored. @@ -104,7 +105,7 @@ func TestStateStore_Coordinate_Updates(t *testing.T) { idx, coords, err := s.Coordinate(nodeWs[i], update.Node, nil) require.NoError(t, err) require.Equal(t, uint64(3), idx, "bad index") - expected := lib.CoordinateSet{ + expected := librtt.CoordinateSet{ "": update.Coord, } require.Equal(t, expected, coords) @@ -133,7 +134,7 @@ func TestStateStore_Coordinate_Updates(t *testing.T) { idx, coords, err := s.Coordinate(nil, update.Node, nil) require.NoError(t, err) require.Equal(t, uint64(4), idx, "bad index") - expected := lib.CoordinateSet{ + expected := librtt.CoordinateSet{ "": update.Coord, } require.Equal(t, expected, coords) @@ -178,7 +179,7 @@ func TestStateStore_Coordinate_Cleanup(t *testing.T) { // Make sure it's in there. _, coords, err := s.Coordinate(nil, "node1", nil) require.NoError(t, err) - expected := lib.CoordinateSet{ + expected := librtt.CoordinateSet{ "alpha": updates[0].Coord, "beta": updates[1].Coord, } @@ -190,7 +191,7 @@ func TestStateStore_Coordinate_Cleanup(t *testing.T) { // Make sure the coordinate is gone. _, coords, err = s.Coordinate(nil, "node1", nil) require.NoError(t, err) - require.Equal(t, lib.CoordinateSet{}, coords) + require.Equal(t, librtt.CoordinateSet{}, coords) // Make sure the index got updated. idx, all, err := s.Coordinates(nil, nil) diff --git a/agent/consul/state/txn.go b/agent/consul/state/txn.go index 30189fc1ed..66cc4bb33d 100644 --- a/agent/consul/state/txn.go +++ b/agent/consul/state/txn.go @@ -4,17 +4,31 @@ package state import ( + "errors" "fmt" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" ) +type UnsupportedFSMApplyPanicError struct { + Wrapped error +} + +func (e *UnsupportedFSMApplyPanicError) Unwrap() error { + return e.Wrapped +} + +func (e *UnsupportedFSMApplyPanicError) Error() string { + return e.Wrapped.Error() +} + // txnKVS handles all KV-related operations. func (s *Store) txnKVS(tx WriteTxn, idx uint64, op *structs.TxnKVOp) (structs.TxnResults, error) { var entry *structs.DirEntry var err error + // enumcover: api.KVOp switch op.Verb { case api.KVSet: entry = &op.DirEnt @@ -95,7 +109,7 @@ func (s *Store) txnKVS(tx WriteTxn, idx uint64, op *structs.TxnKVOp) (structs.Tx } default: - err = fmt.Errorf("unknown KV verb %q", op.Verb) + err = &UnsupportedFSMApplyPanicError{fmt.Errorf("unknown KV verb %q", op.Verb)} } if err != nil { return nil, err @@ -123,11 +137,12 @@ func (s *Store) txnKVS(tx WriteTxn, idx uint64, op *structs.TxnKVOp) (structs.Tx func txnSession(tx WriteTxn, idx uint64, op *structs.TxnSessionOp) error { var err error + // enumcover: api.SessionOp switch op.Verb { case api.SessionDelete: err = sessionDeleteWithSession(tx, &op.Session, idx) default: - err = fmt.Errorf("unknown Session verb %q", op.Verb) + return &UnsupportedFSMApplyPanicError{fmt.Errorf("unknown session verb %q", op.Verb)} } if err != nil { return fmt.Errorf("failed to delete session: %v", err) @@ -146,11 +161,17 @@ func txnLegacyIntention(tx WriteTxn, idx uint64, op *structs.TxnIntentionOp) err case structs.IntentionOpDelete: return legacyIntentionDeleteTxn(tx, idx, op.Intention.ID) case structs.IntentionOpDeleteAll: - fallthrough // deliberately not available via this api + // deliberately not available via this api + return fmt.Errorf("Intention op not supported %q", op.Op) case structs.IntentionOpUpsert: - fallthrough // deliberately not available via this api + // deliberately not available via this api + return fmt.Errorf("Intention op not supported %q", op.Op) default: - return fmt.Errorf("unknown Intention op %q", op.Op) + // If we've gotten to this point, the unknown verb has slipped by + // endpoint validation. This means it could be a mismatch in Server versions + // that are sending known verbs as part of Raft logs. We panic rather than silently + // swallowing the error during Raft Apply. + panic(fmt.Sprintf("unknown Intention op %q", op.Op)) } } @@ -202,7 +223,7 @@ func (s *Store) txnNode(tx WriteTxn, idx uint64, op *structs.TxnNodeOp) (structs } default: - err = fmt.Errorf("unknown Node verb %q", op.Verb) + err = &UnsupportedFSMApplyPanicError{fmt.Errorf("unknown Node verb %q", op.Verb)} } if err != nil { return nil, err @@ -271,7 +292,7 @@ func (s *Store) txnService(tx WriteTxn, idx uint64, op *structs.TxnServiceOp) (s return nil, err default: - return nil, fmt.Errorf("unknown Service verb %q", op.Verb) + return nil, &UnsupportedFSMApplyPanicError{fmt.Errorf("unknown Service verb %q", op.Verb)} } } @@ -326,7 +347,7 @@ func (s *Store) txnCheck(tx WriteTxn, idx uint64, op *structs.TxnCheckOp) (struc } default: - err = fmt.Errorf("unknown Check verb %q", op.Verb) + err = &UnsupportedFSMApplyPanicError{fmt.Errorf("unknown check verb %q", op.Verb)} } if err != nil { return nil, err @@ -352,7 +373,7 @@ func (s *Store) txnCheck(tx WriteTxn, idx uint64, op *structs.TxnCheckOp) (struc // txnDispatch runs the given operations inside the state store transaction. func (s *Store) txnDispatch(tx WriteTxn, idx uint64, ops structs.TxnOps) (structs.TxnResults, structs.TxnErrors) { results := make(structs.TxnResults, 0, len(ops)) - errors := make(structs.TxnErrors, 0, len(ops)) + errs := make(structs.TxnErrors, 0, len(ops)) for i, op := range ops { var ret structs.TxnResults var err error @@ -374,24 +395,33 @@ func (s *Store) txnDispatch(tx WriteTxn, idx uint64, ops structs.TxnOps) (struct // compatibility with pre-1.9.0 raft logs and during upgrades. err = txnLegacyIntention(tx, idx, op.Intention) default: - err = fmt.Errorf("no operation specified") + panic("no operation specified") } // Accumulate the results. results = append(results, ret...) + var panicErr *UnsupportedFSMApplyPanicError + if errors.As(err, &panicErr) { + // If we've gotten to this point, the unknown verb has slipped by + // endpoint validation. This means it could be a mismatch in Server versions + // that are sending known verbs as part of Raft logs. We panic rather than silently + // swallowing the error during Raft Apply. See NET-9016 for historical context. + panic(panicErr.Wrapped) + } + // Capture any error along with the index of the operation that // failed. if err != nil { - errors = append(errors, &structs.TxnError{ + errs = append(errs, &structs.TxnError{ OpIndex: i, What: err.Error(), }) } } - if len(errors) > 0 { - return nil, errors + if len(errs) > 0 { + return nil, errs } return results, nil diff --git a/agent/consul/state/txn_test.go b/agent/consul/state/txn_test.go index bda004a63a..c3d95a4efe 100644 --- a/agent/consul/state/txn_test.go +++ b/agent/consul/state/txn_test.go @@ -1058,14 +1058,6 @@ func TestStateStore_Txn_KVS_Rollback(t *testing.T) { }, }, }, - &structs.TxnOp{ - KV: &structs.TxnKVOp{ - Verb: "nope", - DirEnt: structs.DirEntry{ - Key: "foo/delete", - }, - }, - }, } results, errors := s.TxnRW(7, ops) if len(errors) != len(ops) { @@ -1086,7 +1078,6 @@ func TestStateStore_Txn_KVS_Rollback(t *testing.T) { `key "nope" doesn't exist`, "current modify index", `key "nope" doesn't exist`, - "unknown KV verb", } if len(errors) != len(expected) { t.Fatalf("bad len: %d != %d", len(errors), len(expected)) @@ -1415,3 +1406,64 @@ func TestStateStore_Txn_KVS_ModifyIndexes(t *testing.T) { } } } + +// TestStateStore_UnknownTxnOperationsPanic validates that unknown txn operations panic. +// If we error in this case this is from an FSM Apply, the state store of this agent could potentially be out of +// sync with other agents that applied the operation. In the case of responding to a local endpoint, we require +// that the operation type be validated prior to being sent to the state store. +// See NET-9016 for historical context. +func TestStateStore_UnknownTxnOperationsPanic(t *testing.T) { + s := testStateStore(t) + + testCases := []structs.TxnOps{ + { + &structs.TxnOp{ + KV: &structs.TxnKVOp{ + Verb: "sand-the-floor", + DirEnt: structs.DirEntry{ + Key: "foo/a", + }, + }, + }, + }, + { + &structs.TxnOp{ + Node: &structs.TxnNodeOp{ + Verb: "wax-the-car", + }, + }, + }, + { + &structs.TxnOp{ + Service: &structs.TxnServiceOp{ + Verb: "paint-the-house", + }, + }, + }, + { + &structs.TxnOp{ + Check: &structs.TxnCheckOp{ + Verb: "paint-the-fence", + }, + }, + }, + { + &structs.TxnOp{ + Session: &structs.TxnSessionOp{ + Verb: "sweep-the-knee", + }, + }, + }, + { + &structs.TxnOp{ + Intention: &structs.TxnIntentionOp{ // nolint:staticcheck // SA1019 intentional use of deprecated field + Op: "flying-crane-kick", + }, + }, + }, + } + + for _, tc := range testCases { + require.Panics(t, func() { s.TxnRW(3, tc) }) + } +} diff --git a/agent/consul/testdata/v2-resource-dependencies.md b/agent/consul/testdata/v2-resource-dependencies.md index e394247866..7bcb0d55c4 100644 --- a/agent/consul/testdata/v2-resource-dependencies.md +++ b/agent/consul/testdata/v2-resource-dependencies.md @@ -1,24 +1,5 @@ ```mermaid flowchart TD - auth/v2beta1/computedtrafficpermissions --> auth/v2beta1/namespacetrafficpermissions - auth/v2beta1/computedtrafficpermissions --> auth/v2beta1/partitiontrafficpermissions - auth/v2beta1/computedtrafficpermissions --> auth/v2beta1/trafficpermissions - auth/v2beta1/computedtrafficpermissions --> auth/v2beta1/workloadidentity - auth/v2beta1/namespacetrafficpermissions - auth/v2beta1/partitiontrafficpermissions - auth/v2beta1/trafficpermissions - auth/v2beta1/workloadidentity - catalog/v2beta1/computedfailoverpolicy --> catalog/v2beta1/failoverpolicy - catalog/v2beta1/computedfailoverpolicy --> catalog/v2beta1/service - catalog/v2beta1/failoverpolicy - catalog/v2beta1/healthstatus - catalog/v2beta1/node --> catalog/v2beta1/nodehealthstatus - catalog/v2beta1/nodehealthstatus - catalog/v2beta1/service - catalog/v2beta1/serviceendpoints --> catalog/v2beta1/service - catalog/v2beta1/serviceendpoints --> catalog/v2beta1/workload - catalog/v2beta1/workload --> catalog/v2beta1/healthstatus - catalog/v2beta1/workload --> catalog/v2beta1/node demo/v1/album demo/v1/artist demo/v1/concept @@ -27,42 +8,12 @@ flowchart TD demo/v2/album demo/v2/artist hcp/v2/link - hcp/v2/telemetrystate --> hcp/v2/link + hcp/v2/telemetrystate internal/v1/tombstone - mesh/v2beta1/computedexplicitdestinations --> catalog/v2beta1/service - mesh/v2beta1/computedexplicitdestinations --> catalog/v2beta1/workload - mesh/v2beta1/computedexplicitdestinations --> mesh/v2beta1/computedroutes - mesh/v2beta1/computedexplicitdestinations --> mesh/v2beta1/destinations - mesh/v2beta1/computedproxyconfiguration --> catalog/v2beta1/workload - mesh/v2beta1/computedproxyconfiguration --> mesh/v2beta1/proxyconfiguration - mesh/v2beta1/computedroutes --> catalog/v2beta1/computedfailoverpolicy - mesh/v2beta1/computedroutes --> catalog/v2beta1/service - mesh/v2beta1/computedroutes --> mesh/v2beta1/destinationpolicy - mesh/v2beta1/computedroutes --> mesh/v2beta1/grpcroute - mesh/v2beta1/computedroutes --> mesh/v2beta1/httproute - mesh/v2beta1/computedroutes --> mesh/v2beta1/tcproute - mesh/v2beta1/destinationpolicy - mesh/v2beta1/destinations - mesh/v2beta1/grpcroute - mesh/v2beta1/httproute - mesh/v2beta1/meshconfiguration - mesh/v2beta1/meshgateway - mesh/v2beta1/proxyconfiguration - mesh/v2beta1/proxystatetemplate --> auth/v2beta1/computedtrafficpermissions - mesh/v2beta1/proxystatetemplate --> catalog/v2beta1/service - mesh/v2beta1/proxystatetemplate --> catalog/v2beta1/serviceendpoints - mesh/v2beta1/proxystatetemplate --> catalog/v2beta1/workload - mesh/v2beta1/proxystatetemplate --> mesh/v2beta1/computedexplicitdestinations - mesh/v2beta1/proxystatetemplate --> mesh/v2beta1/computedproxyconfiguration - mesh/v2beta1/proxystatetemplate --> mesh/v2beta1/computedroutes - mesh/v2beta1/proxystatetemplate --> multicluster/v2/computedexportedservices - mesh/v2beta1/tcproute - multicluster/v2/computedexportedservices --> catalog/v2beta1/service multicluster/v2/computedexportedservices --> multicluster/v2/exportedservices multicluster/v2/computedexportedservices --> multicluster/v2/namespaceexportedservices multicluster/v2/computedexportedservices --> multicluster/v2/partitionexportedservices multicluster/v2/exportedservices multicluster/v2/namespaceexportedservices multicluster/v2/partitionexportedservices - tenancy/v2beta1/namespace ``` \ No newline at end of file diff --git a/agent/consul/txn_endpoint.go b/agent/consul/txn_endpoint.go index f39cd502cb..e704c9a2ed 100644 --- a/agent/consul/txn_endpoint.go +++ b/agent/consul/txn_endpoint.go @@ -57,8 +57,15 @@ func (t *Txn) preCheck(authorizer resolver.Result, ops structs.TxnOps) structs.T }) } case op.Node != nil: - // Skip the pre-apply checks if this is a GET. - if op.Node.Verb == api.NodeGet { + requiresPreApply, err := nodeVerbValidate(op.Node.Verb) + if err != nil { + errors = append(errors, &structs.TxnError{ + OpIndex: i, + What: err.Error(), + }) + break + } + if !requiresPreApply { break } @@ -79,8 +86,15 @@ func (t *Txn) preCheck(authorizer resolver.Result, ops structs.TxnOps) structs.T }) } case op.Service != nil: - // Skip the pre-apply checks if this is a GET. - if op.Service.Verb == api.ServiceGet { + requiresPreApply, err := serviceVerbValidate(op.Service.Verb) + if err != nil { + errors = append(errors, &structs.TxnError{ + OpIndex: i, + What: err.Error(), + }) + break + } + if !requiresPreApply { break } @@ -92,8 +106,15 @@ func (t *Txn) preCheck(authorizer resolver.Result, ops structs.TxnOps) structs.T }) } case op.Check != nil: - // Skip the pre-apply checks if this is a GET. - if op.Check.Verb == api.CheckGet { + requiresPreApply, err := checkVerbValidate(op.Check.Verb) + if err != nil { + errors = append(errors, &structs.TxnError{ + OpIndex: i, + What: err.Error(), + }) + break + } + if !requiresPreApply { break } @@ -106,6 +127,25 @@ func (t *Txn) preCheck(authorizer resolver.Result, ops structs.TxnOps) structs.T What: err.Error(), }) } + case op.Intention != nil: + if err := intentionVerbValidate(op.Intention.Op); err != nil { + errors = append(errors, &structs.TxnError{ + OpIndex: i, + What: err.Error(), + }) + } + case op.Session != nil: + if err := sessionVerbValidate(op.Session.Verb); err != nil { + errors = append(errors, &structs.TxnError{ + OpIndex: i, + What: err.Error(), + }) + } + default: + errors = append(errors, &structs.TxnError{ + OpIndex: i, + What: "unknown operation type", + }) } } @@ -224,3 +264,70 @@ func (t *Txn) Read(args *structs.TxnReadRequest, reply *structs.TxnReadResponse) return nil } + +// nodeVerbValidate checks for a known operation type. For certain operations, +// it also indicated if further "preApply" checks are required. +func nodeVerbValidate(op api.NodeOp) (bool, error) { + // enumcover: api.NodeOp + switch op { + // Skip the pre-apply checks if this is a GET. + case api.NodeGet: + return false, nil + case api.NodeSet, api.NodeCAS, api.NodeDelete, api.NodeDeleteCAS: + return true, nil + default: + return false, fmt.Errorf("unknown node operation: %s", op) + } +} + +// serviceVerbValidate checks for a known operation type. For certain operations, +// it also indicated if further "preApply" checks are required. +func serviceVerbValidate(op api.ServiceOp) (bool, error) { + // enumcover: api.ServiceOp + switch op { + // Skip the pre-apply checks if this is a GET. + case api.ServiceGet: + return false, nil + case api.ServiceSet, api.ServiceCAS, api.ServiceDelete, api.ServiceDeleteCAS: + return true, nil + default: + return false, fmt.Errorf("unknown service operation: %s", op) + } +} + +// checkVerbValidate checks for a known operation type. For certain operations, +// it also indicated if further "preApply" checks are required. +func checkVerbValidate(op api.CheckOp) (bool, error) { + // enumcover: api.CheckOp + switch op { + // Skip the pre-apply checks if this is a GET. + case api.CheckGet: + return false, nil + case api.CheckSet, api.CheckCAS, api.CheckDelete, api.CheckDeleteCAS: + return true, nil + default: + return false, fmt.Errorf("unknown check operation: %s", op) + } +} + +// intentionVerbValidate checks for a known operation type. +func intentionVerbValidate(op structs.IntentionOp) error { + // enumcover: structs.IntentionOp + switch op { + case structs.IntentionOpCreate, structs.IntentionOpDelete, structs.IntentionOpUpdate, structs.IntentionOpDeleteAll, structs.IntentionOpUpsert: + return nil + default: + return fmt.Errorf("unknown intention operation: %s", op) + } +} + +// sessionVerbValidate checks for a known operation type. +func sessionVerbValidate(op api.SessionOp) error { + // enumcover: api.SessionOp + switch op { + case api.SessionDelete: + return nil + default: + return fmt.Errorf("unknown session operation: %s", op) + } +} diff --git a/agent/consul/txn_endpoint_test.go b/agent/consul/txn_endpoint_test.go index ef2ecd13a3..03bccf95ff 100644 --- a/agent/consul/txn_endpoint_test.go +++ b/agent/consul/txn_endpoint_test.go @@ -946,3 +946,128 @@ func TestTxn_Read_ACLDeny(t *testing.T) { require.Empty(t, out.Results) }) } + +// TestTxn_Validation works across RW and RO Txn endpoints validating the "preCheck()" operation consistently +// validates operations provided in the request. +func TestTxn_Validation(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() + codec := rpcClient(t, s1) + defer codec.Close() + + testrpc.WaitForLeader(t, s1.RPC, "dc1") + + // Each one of these test cases should error as invalid. + testCases := []struct { + request structs.TxnReadRequest + expectedError string + }{ + { + request: structs.TxnReadRequest{ + Datacenter: "dc1", + Ops: structs.TxnOps{ + &structs.TxnOp{ + KV: &structs.TxnKVOp{ + Verb: "tick", + DirEnt: structs.DirEntry{ + Key: "nope", + }, + }, + }, + }, + }, + expectedError: "unknown KV operation", + }, + { + request: structs.TxnReadRequest{ + Datacenter: "dc1", + Ops: structs.TxnOps{ + &structs.TxnOp{ + Node: &structs.TxnNodeOp{ + Verb: "tick", + }, + }, + }, + }, + expectedError: "unknown node operation", + }, + { + request: structs.TxnReadRequest{ + Datacenter: "dc1", + Ops: structs.TxnOps{ + &structs.TxnOp{ + Service: &structs.TxnServiceOp{ + Verb: "tick", + }, + }, + }, + }, + expectedError: "unknown service operation", + }, + { + request: structs.TxnReadRequest{ + Datacenter: "dc1", + Ops: structs.TxnOps{ + &structs.TxnOp{ + Check: &structs.TxnCheckOp{ + Verb: "tick", + }, + }, + }, + }, + expectedError: "unknown check operation", + }, + { + request: structs.TxnReadRequest{ + Datacenter: "dc1", + Ops: structs.TxnOps{ + &structs.TxnOp{ + Session: &structs.TxnSessionOp{ + Verb: "tick", + }, + }, + }, + }, + expectedError: "unknown session operation", + }, + { + request: structs.TxnReadRequest{ + Datacenter: "dc1", + Ops: structs.TxnOps{ + &structs.TxnOp{ + Intention: &structs.TxnIntentionOp{ // nolint:staticcheck // SA1019 intentional use of deprecated field + Op: "BOOM!", + }, + }, + }, + }, + expectedError: "unknown intention operation", + }, + { + request: structs.TxnReadRequest{ + Datacenter: "dc1", + Ops: structs.TxnOps{ + &structs.TxnOp{ + // Intentionally Empty + }, + }, + }, + expectedError: "unknown operation type", + }, + } + + for _, tc := range testCases { + var out structs.TxnReadResponse + err := msgpackrpc.CallWithCodec(codec, "Txn.Read", &tc.request, &out) + require.NoError(t, err) + require.Greater(t, len(out.Errors), 0) + require.Contains(t, out.Errors[0].Error(), tc.expectedError) + } +} diff --git a/agent/consul/type_registry.go b/agent/consul/type_registry.go index 450cef7e05..cd2087e48f 100644 --- a/agent/consul/type_registry.go +++ b/agent/consul/type_registry.go @@ -4,14 +4,10 @@ package consul import ( - "github.com/hashicorp/consul/internal/auth" - "github.com/hashicorp/consul/internal/catalog" "github.com/hashicorp/consul/internal/hcp" - "github.com/hashicorp/consul/internal/mesh" "github.com/hashicorp/consul/internal/multicluster" "github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/resource/demo" - "github.com/hashicorp/consul/internal/tenancy" ) // NewTypeRegistry returns a registry populated with all supported resource @@ -25,10 +21,6 @@ func NewTypeRegistry() resource.Registry { registry := resource.NewRegistry() demo.RegisterTypes(registry) - mesh.RegisterTypes(registry) - catalog.RegisterTypes(registry) - auth.RegisterTypes(registry) - tenancy.RegisterTypes(registry) multicluster.RegisterTypes(registry) hcp.RegisterTypes(registry) diff --git a/agent/delegate_mock_test.go b/agent/delegate_mock_test.go index a75cf2d1e2..206aac9c7e 100644 --- a/agent/delegate_mock_test.go +++ b/agent/delegate_mock_test.go @@ -14,7 +14,7 @@ import ( "github.com/hashicorp/consul/acl/resolver" "github.com/hashicorp/consul/agent/consul" "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/proto-public/pbresource" ) @@ -22,9 +22,9 @@ type delegateMock struct { mock.Mock } -func (m *delegateMock) GetLANCoordinate() (lib.CoordinateSet, error) { +func (m *delegateMock) GetLANCoordinate() (librtt.CoordinateSet, error) { ret := m.Called() - return ret.Get(0).(lib.CoordinateSet), ret.Error(1) + return ret.Get(0).(librtt.CoordinateSet), ret.Error(1) } func (m *delegateMock) Leave() error { diff --git a/agent/discovery/discovery.go b/agent/discovery/discovery.go deleted file mode 100644 index ee2d742fe7..0000000000 --- a/agent/discovery/discovery.go +++ /dev/null @@ -1,250 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package discovery - -import ( - "fmt" - "net" - - "github.com/hashicorp/consul/agent/config" -) - -var ( - ErrECSNotGlobal = fmt.Errorf("ECS response is not global") - ErrNoData = fmt.Errorf("no data") - ErrNotFound = fmt.Errorf("not found") - ErrNotSupported = fmt.Errorf("not supported") - ErrNoPathToDatacenter = fmt.Errorf("no path to datacenter") -) - -// ECSNotGlobalError may be used to wrap an error or nil, to indicate that the -// EDNS client subnet source scope is not global. -type ECSNotGlobalError struct { - error -} - -func (e ECSNotGlobalError) Error() string { - if e.error == nil { - return "" - } - return e.error.Error() -} - -func (e ECSNotGlobalError) Is(other error) bool { - return other == ErrECSNotGlobal -} - -func (e ECSNotGlobalError) Unwrap() error { - return e.error -} - -// Query is used to request a name-based Service Discovery lookup. -type Query struct { - QueryType QueryType - QueryPayload QueryPayload -} - -// QueryType is used to filter service endpoints. -// This is needed by the V1 catalog because of the -// overlapping lookups through the service endpoint. -type QueryType string - -const ( - QueryTypeConnect QueryType = "CONNECT" // deprecated: use for V1 only - QueryTypeIngress QueryType = "INGRESS" // deprecated: use for V1 only - QueryTypeInvalid QueryType = "INVALID" - QueryTypeNode QueryType = "NODE" - QueryTypePreparedQuery QueryType = "PREPARED_QUERY" // deprecated: use for V1 only - QueryTypeService QueryType = "SERVICE" - QueryTypeVirtual QueryType = "VIRTUAL" - QueryTypeWorkload QueryType = "WORKLOAD" // V2-only -) - -// Context is used to pass information about the request. -type Context struct { - Token string -} - -// QueryTenancy is used to filter catalog data based on tenancy. -type QueryTenancy struct { - Namespace string - Partition string - SamenessGroup string - Peer string - Datacenter string -} - -// QueryPayload represents all information needed by the data backend -// to decide which records to include. -type QueryPayload struct { - Name string - PortName string // v1 - this could optionally be "connect" or "ingress"; v2 - this is the service port name - Tag string // deprecated: use for V1 only - SourceIP net.IP // deprecated: used for prepared queries - Tenancy QueryTenancy // tenancy includes any additional labels specified before the domain - Limit int // The maximum number of records to return - - // v2 fields only - EnableFailover bool -} - -// ResultType indicates the Consul resource that a discovery record represents. -// This is useful for things like adding TTLs for different objects in the DNS. -type ResultType string - -const ( - ResultTypeService ResultType = "SERVICE" - ResultTypeNode ResultType = "NODE" - ResultTypeVirtual ResultType = "VIRTUAL" - ResultTypeWorkload ResultType = "WORKLOAD" -) - -// Result is a generic format of targets that could be returned in a query. -// It is the responsibility of the DNS encoder to know what to do with -// each Result, based on the query type. -type Result struct { - Service *Location // The name and address of the service. - Node *Location // The name and address of the node. - Metadata map[string]string // Used to collect metadata into TXT Records - Type ResultType // Used to reconstruct the fqdn name of the resource - DNS DNSConfig // Used for DNS-specific configuration for this result - - // Ports include anything the node/service/workload implements. These are filtered if requested by the client. - // They are used in to generate the FQDN and SRV port numbers in V2 Catalog responses. - Ports []Port - - Tenancy ResultTenancy -} - -// TaggedAddress is used to represent a tagged address. -type TaggedAddress struct { - Name string - Address string - Port Port -} - -// Location is used to represent a service, node, or workload. -type Location struct { - Name string - Address string - TaggedAddresses map[string]*TaggedAddress // Used to collect tagged addresses into A/AAAA Records -} - -type DNSConfig struct { - TTL *uint32 // deprecated: use for V1 prepared queries only - Weight uint32 // SRV queries -} - -type Port struct { - Name string - Number uint32 -} - -// ResultTenancy is used to reconstruct the fqdn name of the resource. -type ResultTenancy struct { - Namespace string - Partition string - PeerName string - Datacenter string -} - -// LookupType is used by the CatalogDataFetcher to properly filter endpoints. -type LookupType string - -const ( - LookupTypeService LookupType = "SERVICE" - LookupTypeConnect LookupType = "CONNECT" - LookupTypeIngress LookupType = "INGRESS" -) - -// CatalogDataFetcher is an interface that abstracts data collection -// for Discovery queries. It is assumed that the instantiation also -// includes any agent configuration that influences catalog queries. -// -//go:generate mockery --name CatalogDataFetcher --inpackage -type CatalogDataFetcher interface { - // LoadConfig is used to hot-reload the data fetcher with new agent config. - LoadConfig(config *config.RuntimeConfig) - - // FetchNodes fetches A/AAAA/CNAME - FetchNodes(ctx Context, req *QueryPayload) ([]*Result, error) - - // FetchEndpoints fetches records for A/AAAA/CNAME or SRV requests for services - FetchEndpoints(ctx Context, req *QueryPayload, lookupType LookupType) ([]*Result, error) - - // FetchVirtualIP fetches A/AAAA records for virtual IPs - FetchVirtualIP(ctx Context, req *QueryPayload) (*Result, error) - - // FetchRecordsByIp is used for PTR requests - // to look up a service/node from an IP. - FetchRecordsByIp(ctx Context, ip net.IP) ([]*Result, error) - - // FetchWorkload fetches a single Result associated with - // V2 Workload. V2-only. - FetchWorkload(ctx Context, req *QueryPayload) (*Result, error) - - // FetchPreparedQuery evaluates the results of a prepared query. - // deprecated in V2 - FetchPreparedQuery(ctx Context, req *QueryPayload) ([]*Result, error) - - // NormalizeRequest mutates the original request based on data fetcher configuration, like - // defaulting tenancy to the agent's partition. - NormalizeRequest(req *QueryPayload) - - // ValidateRequest throws an error is any of the input fields are invalid for this data fetcher. - ValidateRequest(ctx Context, req *QueryPayload) error -} - -// QueryProcessor is used to process a Discovery Query and return the results. -type QueryProcessor struct { - dataFetcher CatalogDataFetcher -} - -// NewQueryProcessor creates a new QueryProcessor. -func NewQueryProcessor(dataFetcher CatalogDataFetcher) *QueryProcessor { - return &QueryProcessor{ - dataFetcher: dataFetcher, - } -} - -// QueryByName is used to look up a service, node, workload, or prepared query. -func (p *QueryProcessor) QueryByName(query *Query, ctx Context) ([]*Result, error) { - if err := p.dataFetcher.ValidateRequest(ctx, &query.QueryPayload); err != nil { - return nil, err - } - - p.dataFetcher.NormalizeRequest(&query.QueryPayload) - - switch query.QueryType { - case QueryTypeNode: - return p.dataFetcher.FetchNodes(ctx, &query.QueryPayload) - case QueryTypeService: - return p.dataFetcher.FetchEndpoints(ctx, &query.QueryPayload, LookupTypeService) - case QueryTypeConnect: - return p.dataFetcher.FetchEndpoints(ctx, &query.QueryPayload, LookupTypeConnect) - case QueryTypeIngress: - return p.dataFetcher.FetchEndpoints(ctx, &query.QueryPayload, LookupTypeIngress) - case QueryTypeVirtual: - result, err := p.dataFetcher.FetchVirtualIP(ctx, &query.QueryPayload) - if err != nil { - return nil, err - } - return []*Result{result}, nil - case QueryTypeWorkload: - result, err := p.dataFetcher.FetchWorkload(ctx, &query.QueryPayload) - if err != nil { - return nil, err - } - return []*Result{result}, nil - case QueryTypePreparedQuery: - return p.dataFetcher.FetchPreparedQuery(ctx, &query.QueryPayload) - default: - return nil, fmt.Errorf("unknown query type: %s", query.QueryType) - } -} - -// QueryByIP is used to look up a service or node from an IP address. -func (p *QueryProcessor) QueryByIP(ip net.IP, reqCtx Context) ([]*Result, error) { - return p.dataFetcher.FetchRecordsByIp(reqCtx, ip) -} diff --git a/agent/discovery/discovery_test.go b/agent/discovery/discovery_test.go deleted file mode 100644 index a53ec7b866..0000000000 --- a/agent/discovery/discovery_test.go +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package discovery - -import ( - "errors" - "net" - "testing" - - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" -) - -var ( - testContext = Context{ - Token: "bar", - } - - testErr = errors.New("test error") - - testIP = net.ParseIP("1.2.3.4") - - testPayload = QueryPayload{ - Name: "foo", - } - - testResult = &Result{ - Node: &Location{Address: "1.2.3.4"}, - Type: ResultTypeNode, // This isn't correct for some test cases, but we are only asserting the right data fetcher functions are called - Service: &Location{Name: "foo"}, - } -) - -func TestQueryByName(t *testing.T) { - - type testCase struct { - name string - reqType QueryType - configureDataFetcher func(*testing.T, *MockCatalogDataFetcher) - expectedResults []*Result - expectedError error - } - - run := func(t *testing.T, tc testCase) { - - fetcher := NewMockCatalogDataFetcher(t) - tc.configureDataFetcher(t, fetcher) - - qp := NewQueryProcessor(fetcher) - - q := Query{ - QueryType: tc.reqType, - QueryPayload: testPayload, - } - - results, err := qp.QueryByName(&q, testContext) - if tc.expectedError != nil { - require.Error(t, err) - require.True(t, errors.Is(err, tc.expectedError)) - require.Nil(t, results) - return - } - require.NoError(t, err) - require.Equal(t, tc.expectedResults, results) - } - - testCases := []testCase{ - { - name: "query node", - reqType: QueryTypeNode, - configureDataFetcher: func(t *testing.T, fetcher *MockCatalogDataFetcher) { - - fetcher.On("ValidateRequest", mock.Anything, mock.Anything).Return(nil) - fetcher.On("NormalizeRequest", mock.Anything) - fetcher.On("FetchNodes", mock.Anything, mock.Anything).Return([]*Result{testResult}, nil) - }, - expectedResults: []*Result{testResult}, - }, - { - name: "query service", - reqType: QueryTypeService, - configureDataFetcher: func(t *testing.T, fetcher *MockCatalogDataFetcher) { - - fetcher.On("ValidateRequest", mock.Anything, mock.Anything).Return(nil) - fetcher.On("NormalizeRequest", mock.Anything) - fetcher.On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything).Return([]*Result{testResult}, nil) - }, - expectedResults: []*Result{testResult}, - }, - { - name: "query connect", - reqType: QueryTypeConnect, - configureDataFetcher: func(t *testing.T, fetcher *MockCatalogDataFetcher) { - - fetcher.On("ValidateRequest", mock.Anything, mock.Anything).Return(nil) - fetcher.On("NormalizeRequest", mock.Anything) - fetcher.On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything).Return([]*Result{testResult}, nil) - }, - expectedResults: []*Result{testResult}, - }, - { - name: "query ingress", - reqType: QueryTypeIngress, - configureDataFetcher: func(t *testing.T, fetcher *MockCatalogDataFetcher) { - - fetcher.On("ValidateRequest", mock.Anything, mock.Anything).Return(nil) - fetcher.On("NormalizeRequest", mock.Anything) - fetcher.On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything).Return([]*Result{testResult}, nil) - }, - expectedResults: []*Result{testResult}, - }, - { - name: "query virtual ip", - reqType: QueryTypeVirtual, - configureDataFetcher: func(t *testing.T, fetcher *MockCatalogDataFetcher) { - - fetcher.On("ValidateRequest", mock.Anything, mock.Anything).Return(nil) - fetcher.On("NormalizeRequest", mock.Anything) - fetcher.On("FetchVirtualIP", mock.Anything, mock.Anything).Return(testResult, nil) - }, - expectedResults: []*Result{testResult}, - }, - { - name: "query workload", - reqType: QueryTypeWorkload, - configureDataFetcher: func(t *testing.T, fetcher *MockCatalogDataFetcher) { - - fetcher.On("ValidateRequest", mock.Anything, mock.Anything).Return(nil) - fetcher.On("NormalizeRequest", mock.Anything) - fetcher.On("FetchWorkload", mock.Anything, mock.Anything).Return(testResult, nil) - }, - expectedResults: []*Result{testResult}, - }, - { - name: "query prepared query", - reqType: QueryTypePreparedQuery, - configureDataFetcher: func(t *testing.T, fetcher *MockCatalogDataFetcher) { - - fetcher.On("ValidateRequest", mock.Anything, mock.Anything).Return(nil) - fetcher.On("NormalizeRequest", mock.Anything) - fetcher.On("FetchPreparedQuery", mock.Anything, mock.Anything).Return([]*Result{testResult}, nil) - }, - expectedResults: []*Result{testResult}, - }, - { - name: "returns error from validation", - reqType: QueryTypePreparedQuery, - configureDataFetcher: func(t *testing.T, fetcher *MockCatalogDataFetcher) { - fetcher.On("ValidateRequest", mock.Anything, mock.Anything).Return(testErr) - }, - expectedError: testErr, - }, - { - name: "returns error from fetcher", - reqType: QueryTypePreparedQuery, - configureDataFetcher: func(t *testing.T, fetcher *MockCatalogDataFetcher) { - fetcher.On("ValidateRequest", mock.Anything, mock.Anything).Return(nil) - fetcher.On("NormalizeRequest", mock.Anything) - fetcher.On("FetchPreparedQuery", mock.Anything, mock.Anything).Return(nil, testErr) - }, - expectedError: testErr, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - -func TestQueryByIP(t *testing.T) { - type testCase struct { - name string - configureDataFetcher func(*testing.T, *MockCatalogDataFetcher) - expectedResults []*Result - expectedError error - } - - run := func(t *testing.T, tc testCase) { - - fetcher := NewMockCatalogDataFetcher(t) - tc.configureDataFetcher(t, fetcher) - - qp := NewQueryProcessor(fetcher) - - results, err := qp.QueryByIP(testIP, testContext) - if tc.expectedError != nil { - require.Error(t, err) - require.True(t, errors.Is(err, tc.expectedError)) - require.Nil(t, results) - return - } - require.NoError(t, err) - require.Equal(t, tc.expectedResults, results) - } - - testCases := []testCase{ - { - name: "query by IP", - configureDataFetcher: func(t *testing.T, fetcher *MockCatalogDataFetcher) { - fetcher.On("FetchRecordsByIp", mock.Anything, mock.Anything).Return([]*Result{testResult}, nil) - }, - expectedResults: []*Result{testResult}, - }, - { - name: "returns error from fetcher", - configureDataFetcher: func(t *testing.T, fetcher *MockCatalogDataFetcher) { - fetcher.On("FetchRecordsByIp", mock.Anything, mock.Anything).Return(nil, testErr) - }, - expectedError: testErr, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} diff --git a/agent/discovery/mock_CatalogDataFetcher.go b/agent/discovery/mock_CatalogDataFetcher.go deleted file mode 100644 index f80a6010d2..0000000000 --- a/agent/discovery/mock_CatalogDataFetcher.go +++ /dev/null @@ -1,209 +0,0 @@ -// Code generated by mockery v2.37.1. DO NOT EDIT. - -package discovery - -import ( - config "github.com/hashicorp/consul/agent/config" - mock "github.com/stretchr/testify/mock" - - net "net" -) - -// MockCatalogDataFetcher is an autogenerated mock type for the CatalogDataFetcher type -type MockCatalogDataFetcher struct { - mock.Mock -} - -// FetchEndpoints provides a mock function with given fields: ctx, req, lookupType -func (_m *MockCatalogDataFetcher) FetchEndpoints(ctx Context, req *QueryPayload, lookupType LookupType) ([]*Result, error) { - ret := _m.Called(ctx, req, lookupType) - - var r0 []*Result - var r1 error - if rf, ok := ret.Get(0).(func(Context, *QueryPayload, LookupType) ([]*Result, error)); ok { - return rf(ctx, req, lookupType) - } - if rf, ok := ret.Get(0).(func(Context, *QueryPayload, LookupType) []*Result); ok { - r0 = rf(ctx, req, lookupType) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*Result) - } - } - - if rf, ok := ret.Get(1).(func(Context, *QueryPayload, LookupType) error); ok { - r1 = rf(ctx, req, lookupType) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// FetchNodes provides a mock function with given fields: ctx, req -func (_m *MockCatalogDataFetcher) FetchNodes(ctx Context, req *QueryPayload) ([]*Result, error) { - ret := _m.Called(ctx, req) - - var r0 []*Result - var r1 error - if rf, ok := ret.Get(0).(func(Context, *QueryPayload) ([]*Result, error)); ok { - return rf(ctx, req) - } - if rf, ok := ret.Get(0).(func(Context, *QueryPayload) []*Result); ok { - r0 = rf(ctx, req) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*Result) - } - } - - if rf, ok := ret.Get(1).(func(Context, *QueryPayload) error); ok { - r1 = rf(ctx, req) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// FetchPreparedQuery provides a mock function with given fields: ctx, req -func (_m *MockCatalogDataFetcher) FetchPreparedQuery(ctx Context, req *QueryPayload) ([]*Result, error) { - ret := _m.Called(ctx, req) - - var r0 []*Result - var r1 error - if rf, ok := ret.Get(0).(func(Context, *QueryPayload) ([]*Result, error)); ok { - return rf(ctx, req) - } - if rf, ok := ret.Get(0).(func(Context, *QueryPayload) []*Result); ok { - r0 = rf(ctx, req) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*Result) - } - } - - if rf, ok := ret.Get(1).(func(Context, *QueryPayload) error); ok { - r1 = rf(ctx, req) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// FetchRecordsByIp provides a mock function with given fields: ctx, ip -func (_m *MockCatalogDataFetcher) FetchRecordsByIp(ctx Context, ip net.IP) ([]*Result, error) { - ret := _m.Called(ctx, ip) - - var r0 []*Result - var r1 error - if rf, ok := ret.Get(0).(func(Context, net.IP) ([]*Result, error)); ok { - return rf(ctx, ip) - } - if rf, ok := ret.Get(0).(func(Context, net.IP) []*Result); ok { - r0 = rf(ctx, ip) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]*Result) - } - } - - if rf, ok := ret.Get(1).(func(Context, net.IP) error); ok { - r1 = rf(ctx, ip) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// FetchVirtualIP provides a mock function with given fields: ctx, req -func (_m *MockCatalogDataFetcher) FetchVirtualIP(ctx Context, req *QueryPayload) (*Result, error) { - ret := _m.Called(ctx, req) - - var r0 *Result - var r1 error - if rf, ok := ret.Get(0).(func(Context, *QueryPayload) (*Result, error)); ok { - return rf(ctx, req) - } - if rf, ok := ret.Get(0).(func(Context, *QueryPayload) *Result); ok { - r0 = rf(ctx, req) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*Result) - } - } - - if rf, ok := ret.Get(1).(func(Context, *QueryPayload) error); ok { - r1 = rf(ctx, req) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// FetchWorkload provides a mock function with given fields: ctx, req -func (_m *MockCatalogDataFetcher) FetchWorkload(ctx Context, req *QueryPayload) (*Result, error) { - ret := _m.Called(ctx, req) - - var r0 *Result - var r1 error - if rf, ok := ret.Get(0).(func(Context, *QueryPayload) (*Result, error)); ok { - return rf(ctx, req) - } - if rf, ok := ret.Get(0).(func(Context, *QueryPayload) *Result); ok { - r0 = rf(ctx, req) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*Result) - } - } - - if rf, ok := ret.Get(1).(func(Context, *QueryPayload) error); ok { - r1 = rf(ctx, req) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// LoadConfig provides a mock function with given fields: _a0 -func (_m *MockCatalogDataFetcher) LoadConfig(_a0 *config.RuntimeConfig) { - _m.Called(_a0) -} - -// NormalizeRequest provides a mock function with given fields: req -func (_m *MockCatalogDataFetcher) NormalizeRequest(req *QueryPayload) { - _m.Called(req) -} - -// ValidateRequest provides a mock function with given fields: ctx, req -func (_m *MockCatalogDataFetcher) ValidateRequest(ctx Context, req *QueryPayload) error { - ret := _m.Called(ctx, req) - - var r0 error - if rf, ok := ret.Get(0).(func(Context, *QueryPayload) error); ok { - r0 = rf(ctx, req) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// NewMockCatalogDataFetcher creates a new instance of MockCatalogDataFetcher. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockCatalogDataFetcher(t interface { - mock.TestingT - Cleanup(func()) -}) *MockCatalogDataFetcher { - mock := &MockCatalogDataFetcher{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/agent/discovery/query_fetcher_v1.go b/agent/discovery/query_fetcher_v1.go deleted file mode 100644 index dc897f7728..0000000000 --- a/agent/discovery/query_fetcher_v1.go +++ /dev/null @@ -1,670 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package discovery - -import ( - "context" - "errors" - "fmt" - "net" - "strings" - "sync/atomic" - "time" - - "github.com/armon/go-metrics" - "github.com/armon/go-metrics/prometheus" - - "github.com/hashicorp/go-hclog" - - "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/agent/cache" - cachetype "github.com/hashicorp/consul/agent/cache-types" - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/api" -) - -const ( - // Increment a counter when requests staler than this are served - staleCounterThreshold = 5 * time.Second -) - -// DNSCounters pre-registers the staleness metric. -// This value is used by both the V1 and V2 DNS (V1 Catalog-only) servers. -var DNSCounters = []prometheus.CounterDefinition{ - { - Name: []string{"dns", "stale_queries"}, - Help: "Increments when an agent serves a query within the allowed stale threshold.", - }, -} - -// V1DataFetcherDynamicConfig is used to store the dynamic configuration of the V1 data fetcher. -type V1DataFetcherDynamicConfig struct { - // Default request tenancy - Datacenter string - - SegmentName string - NodeName string - NodePartition string - - // Catalog configuration - AllowStale bool - MaxStale time.Duration - UseCache bool - CacheMaxAge time.Duration - OnlyPassing bool -} - -// V1DataFetcher is used to fetch data from the V1 catalog. -type V1DataFetcher struct { - defaultEnterpriseMeta acl.EnterpriseMeta - dynamicConfig atomic.Value - logger hclog.Logger - - getFromCacheFunc func(ctx context.Context, t string, r cache.Request) (interface{}, cache.ResultMeta, error) - rpcFunc func(ctx context.Context, method string, args interface{}, reply interface{}) error - rpcFuncForServiceNodes func(ctx context.Context, req structs.ServiceSpecificRequest) (structs.IndexedCheckServiceNodes, cache.ResultMeta, error) - rpcFuncForSamenessGroup func(ctx context.Context, req *structs.ConfigEntryQuery) (structs.SamenessGroupConfigEntry, cache.ResultMeta, error) - translateServicePortFunc func(dc string, port int, taggedAddresses map[string]structs.ServiceAddress) int -} - -// NewV1DataFetcher creates a new V1 data fetcher. -func NewV1DataFetcher(config *config.RuntimeConfig, - entMeta *acl.EnterpriseMeta, - getFromCacheFunc func(ctx context.Context, t string, r cache.Request) (interface{}, cache.ResultMeta, error), - rpcFunc func(ctx context.Context, method string, args interface{}, reply interface{}) error, - rpcFuncForServiceNodes func(ctx context.Context, req structs.ServiceSpecificRequest) (structs.IndexedCheckServiceNodes, cache.ResultMeta, error), - rpcFuncForSamenessGroup func(ctx context.Context, req *structs.ConfigEntryQuery) (structs.SamenessGroupConfigEntry, cache.ResultMeta, error), - translateServicePortFunc func(dc string, port int, taggedAddresses map[string]structs.ServiceAddress) int, - logger hclog.Logger) *V1DataFetcher { - f := &V1DataFetcher{ - defaultEnterpriseMeta: *entMeta, - getFromCacheFunc: getFromCacheFunc, - rpcFunc: rpcFunc, - rpcFuncForServiceNodes: rpcFuncForServiceNodes, - rpcFuncForSamenessGroup: rpcFuncForSamenessGroup, - translateServicePortFunc: translateServicePortFunc, - logger: logger, - } - f.LoadConfig(config) - return f -} - -// LoadConfig loads the configuration for the V1 data fetcher. -func (f *V1DataFetcher) LoadConfig(config *config.RuntimeConfig) { - dynamicConfig := &V1DataFetcherDynamicConfig{ - AllowStale: config.DNSAllowStale, - MaxStale: config.DNSMaxStale, - UseCache: config.DNSUseCache, - CacheMaxAge: config.DNSCacheMaxAge, - OnlyPassing: config.DNSOnlyPassing, - Datacenter: config.Datacenter, - SegmentName: config.SegmentName, - NodeName: config.NodeName, - } - f.dynamicConfig.Store(dynamicConfig) -} - -func (f *V1DataFetcher) GetConfig() *V1DataFetcherDynamicConfig { - return f.dynamicConfig.Load().(*V1DataFetcherDynamicConfig) -} - -// FetchNodes fetches A/AAAA/CNAME -func (f *V1DataFetcher) FetchNodes(ctx Context, req *QueryPayload) ([]*Result, error) { - if req.Tenancy.Namespace != "" && req.Tenancy.Namespace != acl.DefaultNamespaceName { - // Nodes are not namespaced, so this is a name error - return nil, ErrNotFound - } - cfg := f.dynamicConfig.Load().(*V1DataFetcherDynamicConfig) - - // If no datacenter is passed, default to our own - datacenter := cfg.Datacenter - if req.Tenancy.Datacenter != "" { - datacenter = req.Tenancy.Datacenter - } - - // Make an RPC request - args := &structs.NodeSpecificRequest{ - Datacenter: datacenter, - PeerName: req.Tenancy.Peer, - Node: req.Name, - QueryOptions: structs.QueryOptions{ - Token: ctx.Token, - AllowStale: cfg.AllowStale, - }, - EnterpriseMeta: queryTenancyToEntMeta(req.Tenancy), - } - out, err := f.fetchNode(cfg, args) - if err != nil { - return nil, fmt.Errorf("failed rpc request: %w", err) - } - - // If we have no out.NodeServices.Nodeaddress, return not found! - if out.NodeServices == nil { - return nil, ErrNotFound - } - - results := make([]*Result, 0, 1) - n := out.NodeServices.Node - - results = append(results, &Result{ - Node: &Location{ - Name: n.Node, - Address: n.Address, - TaggedAddresses: makeTaggedAddressesFromStrings(n.TaggedAddresses), - }, - Type: ResultTypeNode, - Metadata: n.Meta, - - Tenancy: ResultTenancy{ - // Namespace is not required because nodes are not namespaced - Partition: n.GetEnterpriseMeta().PartitionOrDefault(), - Datacenter: n.Datacenter, - }, - }) - - return results, nil -} - -// FetchEndpoints fetches records for A/AAAA/CNAME or SRV requests for services -func (f *V1DataFetcher) FetchEndpoints(ctx Context, req *QueryPayload, lookupType LookupType) ([]*Result, error) { - f.logger.Trace(fmt.Sprintf("FetchEndpoints - req: %+v / lookupType: %+v", req, lookupType)) - cfg := f.dynamicConfig.Load().(*V1DataFetcherDynamicConfig) - return f.fetchService(ctx, req, cfg, lookupType) -} - -// FetchVirtualIP fetches A/AAAA records for virtual IPs -func (f *V1DataFetcher) FetchVirtualIP(ctx Context, req *QueryPayload) (*Result, error) { - args := structs.ServiceSpecificRequest{ - // The Datacenter of the request is not specified because cross-Datacenter virtual IP - // queries are not supported. This guard rail is in place because virtual IPs are allocated - // within a DC, therefore their uniqueness is not guaranteed globally. - PeerName: req.Tenancy.Peer, - ServiceName: req.Name, - EnterpriseMeta: queryTenancyToEntMeta(req.Tenancy), - QueryOptions: structs.QueryOptions{ - Token: ctx.Token, - }, - } - - var out string - if err := f.rpcFunc(context.Background(), "Catalog.VirtualIPForService", &args, &out); err != nil { - return nil, err - } - - result := &Result{ - Service: &Location{ - Name: req.Name, - Address: out, - }, - Type: ResultTypeVirtual, - } - return result, nil -} - -// FetchRecordsByIp is used for PTR requests to look up a service/node from an IP. -// The search is performed in the agent's partition and over all namespaces (or those allowed by the ACL token). -func (f *V1DataFetcher) FetchRecordsByIp(reqCtx Context, ip net.IP) ([]*Result, error) { - if ip == nil { - return nil, ErrNotSupported - } - - configCtx := f.dynamicConfig.Load().(*V1DataFetcherDynamicConfig) - targetIP := ip.String() - - var results []*Result - - args := structs.DCSpecificRequest{ - Datacenter: configCtx.Datacenter, - QueryOptions: structs.QueryOptions{ - Token: reqCtx.Token, - AllowStale: configCtx.AllowStale, - }, - } - var out structs.IndexedNodes - - // TODO: Replace ListNodes with an internal RPC that can do the filter - // server side to avoid transferring the entire node list. - if err := f.rpcFunc(context.Background(), "Catalog.ListNodes", &args, &out); err == nil { - for _, n := range out.Nodes { - if targetIP == n.Address { - results = append(results, &Result{ - Node: &Location{ - Name: n.Node, - Address: n.Address, - TaggedAddresses: makeTaggedAddressesFromStrings(n.TaggedAddresses), - }, - Type: ResultTypeNode, - Tenancy: ResultTenancy{ - Namespace: f.defaultEnterpriseMeta.NamespaceOrDefault(), - Partition: f.defaultEnterpriseMeta.PartitionOrDefault(), - Datacenter: configCtx.Datacenter, - }, - }) - return results, nil - } - } - } - - // only look into the services if we didn't find a node - sargs := structs.ServiceSpecificRequest{ - Datacenter: configCtx.Datacenter, - QueryOptions: structs.QueryOptions{ - Token: reqCtx.Token, - AllowStale: configCtx.AllowStale, - }, - ServiceAddress: targetIP, - EnterpriseMeta: *f.defaultEnterpriseMeta.WithWildcardNamespace(), - } - - var sout structs.IndexedServiceNodes - if err := f.rpcFunc(context.Background(), "Catalog.ServiceNodes", &sargs, &sout); err == nil { - if len(sout.ServiceNodes) == 0 { - return nil, ErrNotFound - } - - for _, n := range sout.ServiceNodes { - if n.ServiceAddress == targetIP { - results = append(results, &Result{ - Service: &Location{ - Name: n.ServiceName, - Address: n.ServiceAddress, - }, - Type: ResultTypeService, - Node: &Location{ - Name: n.Node, - Address: n.Address, - }, - Tenancy: ResultTenancy{ - Namespace: n.NamespaceOrEmpty(), - Partition: n.PartitionOrEmpty(), - Datacenter: n.Datacenter, - }, - }) - return results, nil - } - } - } - - // nothing found locally, recurse - // TODO: (v2-dns) implement recursion (NET-7883) - //d.handleRecurse(resp, req) - - return nil, fmt.Errorf("unhandled error in FetchRecordsByIp") -} - -// FetchWorkload fetches a single Result associated with -// V2 Workload. V2-only. -func (f *V1DataFetcher) FetchWorkload(ctx Context, req *QueryPayload) (*Result, error) { - return nil, ErrNotSupported -} - -// FetchPreparedQuery evaluates the results of a prepared query. -// deprecated in V2 -func (f *V1DataFetcher) FetchPreparedQuery(ctx Context, req *QueryPayload) ([]*Result, error) { - cfg := f.dynamicConfig.Load().(*V1DataFetcherDynamicConfig) - - // If no datacenter is passed, default to our own - datacenter := cfg.Datacenter - if req.Tenancy.Datacenter != "" { - datacenter = req.Tenancy.Datacenter - } - - // Execute the prepared query. - args := structs.PreparedQueryExecuteRequest{ - Datacenter: datacenter, - QueryIDOrName: req.Name, - QueryOptions: structs.QueryOptions{ - Token: ctx.Token, - AllowStale: cfg.AllowStale, - MaxAge: cfg.CacheMaxAge, - }, - - // Always pass the local agent through. In the DNS interface, there - // is no provision for passing additional query parameters, so we - // send the local agent's data through to allow distance sorting - // relative to ourself on the server side. - Agent: structs.QuerySource{ - Datacenter: cfg.Datacenter, - Segment: cfg.SegmentName, - Node: cfg.NodeName, - NodePartition: cfg.NodePartition, - }, - Source: structs.QuerySource{ - Ip: req.SourceIP.String(), - }, - } - - out, err := f.executePreparedQuery(cfg, args) - if err != nil { - // errors.Is() doesn't work with errors.New() so we need to check the error message. - if err.Error() == structs.ErrQueryNotFound.Error() { - err = ErrNotFound - } - return nil, ECSNotGlobalError{err} - } - - // TODO (slackpad) - What's a safe limit we can set here? It seems like - // with dup filtering done at this level we need to get everything to - // match the previous behavior. We can optimize by pushing more filtering - // into the query execution, but for now I think we need to get the full - // response. We could also choose a large arbitrary number that will - // likely work in practice, like 10*maxUDPAnswerLimit which should help - // reduce bandwidth if there are thousands of nodes available. - // Determine the TTL. The parse should never fail since we vet it when - // the query is created, but we check anyway. If the query didn't - // specify a TTL then we will try to use the agent's service-specific - // TTL configs. - - // Check is there is a TTL provided as part of the prepared query - var ttlOverride *uint32 - if out.DNS.TTL != "" { - ttl, err := time.ParseDuration(out.DNS.TTL) - if err == nil { - ttlSec := uint32(ttl / time.Second) - ttlOverride = &ttlSec - } - f.logger.Warn("Failed to parse TTL for prepared query , ignoring", - "ttl", out.DNS.TTL, - "prepared_query", req.Name, - ) - } - - // If we have no nodes, return not found! - if len(out.Nodes) == 0 { - return nil, ECSNotGlobalError{ErrNotFound} - } - - // Perform a random shuffle - out.Nodes.Shuffle() - return f.buildResultsFromServiceNodes(out.Nodes, req, ttlOverride), ECSNotGlobalError{} -} - -// executePreparedQuery is used to execute a PreparedQuery against the Consul catalog. -// If the config is set to UseCache, it will use agent cache. -func (f *V1DataFetcher) executePreparedQuery(cfg *V1DataFetcherDynamicConfig, args structs.PreparedQueryExecuteRequest) (*structs.PreparedQueryExecuteResponse, error) { - var out structs.PreparedQueryExecuteResponse - -RPC: - if cfg.UseCache { - raw, m, err := f.getFromCacheFunc(context.TODO(), cachetype.PreparedQueryName, &args) - if err != nil { - return nil, err - } - reply, ok := raw.(*structs.PreparedQueryExecuteResponse) - if !ok { - // This should never happen, but we want to protect against panics - return nil, err - } - - f.logger.Trace("cache results for prepared query", - "cache_hit", m.Hit, - "prepared_query", args.QueryIDOrName, - ) - - out = *reply - } else { - if err := f.rpcFunc(context.Background(), "PreparedQuery.Execute", &args, &out); err != nil { - return nil, err - } - } - - // Verify that request is not too stale, redo the request. - if args.AllowStale { - if out.LastContact > cfg.MaxStale { - args.AllowStale = false - f.logger.Warn("Query results too stale, re-requesting") - goto RPC - } else if out.LastContact > staleCounterThreshold { - metrics.IncrCounter([]string{"dns", "stale_queries"}, 1) - } - } - - return &out, nil -} - -func (f *V1DataFetcher) ValidateRequest(_ Context, req *QueryPayload) error { - if req.EnableFailover { - return ErrNotSupported - } - if req.PortName != "" { - return ErrNotSupported - } - return validateEnterpriseTenancy(req.Tenancy) -} - -// buildResultsFromServiceNodes builds a list of results from a list of nodes. -func (f *V1DataFetcher) buildResultsFromServiceNodes(nodes []structs.CheckServiceNode, req *QueryPayload, ttlOverride *uint32) []*Result { - // Convert the service endpoints to results up to the limit - limit := req.Limit - if len(nodes) < limit || limit == 0 { - limit = len(nodes) - } - - results := make([]*Result, 0, limit) - for idx := 0; idx < limit; idx++ { - n := nodes[idx] - results = append(results, &Result{ - Service: &Location{ - Name: n.Service.Service, - Address: n.Service.Address, - TaggedAddresses: makeTaggedAddressesFromServiceAddresses(n.Service.TaggedAddresses), - }, - Node: &Location{ - Name: n.Node.Node, - Address: n.Node.Address, - TaggedAddresses: makeTaggedAddressesFromStrings(n.Node.TaggedAddresses), - }, - Type: ResultTypeService, - DNS: DNSConfig{ - TTL: ttlOverride, - Weight: uint32(findWeight(n)), - }, - Ports: []Port{ - {Number: uint32(f.translateServicePortFunc(n.Node.Datacenter, n.Service.Port, n.Service.TaggedAddresses))}, - }, - Metadata: n.Node.Meta, - Tenancy: ResultTenancy{ - Namespace: n.Service.NamespaceOrEmpty(), - Partition: n.Service.PartitionOrEmpty(), - Datacenter: n.Node.Datacenter, - PeerName: req.Tenancy.Peer, - }, - }) - } - return results -} - -// makeTaggedAddressesFromServiceAddresses is used to convert a map of service addresses to a map of Locations. -func makeTaggedAddressesFromServiceAddresses(tagged map[string]structs.ServiceAddress) map[string]*TaggedAddress { - taggedAddresses := make(map[string]*TaggedAddress) - for k, v := range tagged { - taggedAddresses[k] = &TaggedAddress{ - Name: k, - Address: v.Address, - Port: Port{ - Number: uint32(v.Port), - }, - } - } - return taggedAddresses -} - -// makeTaggedAddressesFromStrings is used to convert a map of strings to a map of Locations. -func makeTaggedAddressesFromStrings(tagged map[string]string) map[string]*TaggedAddress { - taggedAddresses := make(map[string]*TaggedAddress) - for k, v := range tagged { - taggedAddresses[k] = &TaggedAddress{ - Name: k, - Address: v, - } - } - return taggedAddresses -} - -// fetchNode is used to look up a node in the Consul catalog within NodeServices. -// If the config is set to UseCache, it will get the record from the agent cache. -func (f *V1DataFetcher) fetchNode(cfg *V1DataFetcherDynamicConfig, args *structs.NodeSpecificRequest) (*structs.IndexedNodeServices, error) { - var out structs.IndexedNodeServices - - useCache := cfg.UseCache -RPC: - if useCache { - raw, _, err := f.getFromCacheFunc(context.TODO(), cachetype.NodeServicesName, args) - if err != nil { - return nil, err - } - reply, ok := raw.(*structs.IndexedNodeServices) - if !ok { - // This should never happen, but we want to protect against panics - return nil, fmt.Errorf("internal error: response type not correct") - } - out = *reply - } else { - if err := f.rpcFunc(context.Background(), "Catalog.NodeServices", &args, &out); err != nil { - return nil, err - } - } - - // Verify that request is not too stale, redo the request - if args.AllowStale { - if out.LastContact > cfg.MaxStale { - args.AllowStale = false - useCache = false - f.logger.Warn("Query results too stale, re-requesting") - goto RPC - } else if out.LastContact > staleCounterThreshold { - metrics.IncrCounter([]string{"dns", "stale_queries"}, 1) - } - } - - return &out, nil -} - -func (f *V1DataFetcher) fetchService(ctx Context, req *QueryPayload, - cfg *V1DataFetcherDynamicConfig, lookupType LookupType) ([]*Result, error) { - f.logger.Trace("fetchService", "req", req) - if req.Tenancy.SamenessGroup == "" { - return f.fetchServiceBasedOnTenancy(ctx, req, cfg, lookupType) - } - - return f.fetchServiceFromSamenessGroup(ctx, req, cfg, lookupType) -} - -// fetchServiceBasedOnTenancy is used to look up a service in the Consul catalog based on its tenancy or default tenancy. -func (f *V1DataFetcher) fetchServiceBasedOnTenancy(ctx Context, req *QueryPayload, - cfg *V1DataFetcherDynamicConfig, lookupType LookupType) ([]*Result, error) { - f.logger.Trace(fmt.Sprintf("fetchServiceBasedOnTenancy - req: %+v", req)) - if req.Tenancy.SamenessGroup != "" { - return nil, errors.New("sameness groups are not allowed for service lookups based on tenancy") - } - - // If no datacenter is passed, default to our own - datacenter := cfg.Datacenter - if req.Tenancy.Datacenter != "" { - datacenter = req.Tenancy.Datacenter - } - if req.Tenancy.Peer != "" { - datacenter = "" - } - - serviceTags := []string{} - if req.Tag != "" { - serviceTags = []string{req.Tag} - } - args := structs.ServiceSpecificRequest{ - PeerName: req.Tenancy.Peer, - Connect: lookupType == LookupTypeConnect, - Ingress: lookupType == LookupTypeIngress, - Datacenter: datacenter, - ServiceName: req.Name, - ServiceTags: serviceTags, - TagFilter: req.Tag != "", - QueryOptions: structs.QueryOptions{ - Token: ctx.Token, - AllowStale: cfg.AllowStale, - MaxAge: cfg.CacheMaxAge, - UseCache: cfg.UseCache, - MaxStaleDuration: cfg.MaxStale, - }, - EnterpriseMeta: queryTenancyToEntMeta(req.Tenancy), - } - - out, _, err := f.rpcFuncForServiceNodes(context.TODO(), args) - if err != nil { - if strings.Contains(err.Error(), structs.ErrNoDCPath.Error()) { - return nil, ErrNoPathToDatacenter - } - return nil, fmt.Errorf("rpc request failed: %w", err) - } - - // If we have no nodes, return not found! - if len(out.Nodes) == 0 { - return nil, ErrNotFound - } - - // Filter out any service nodes due to health checks - // We copy the slice to avoid modifying the result if it comes from the cache - nodes := make(structs.CheckServiceNodes, len(out.Nodes)) - copy(nodes, out.Nodes) - out.Nodes = nodes.Filter(cfg.OnlyPassing) - if err != nil { - return nil, fmt.Errorf("rpc request failed: %w", err) - } - - // If we have no nodes, return not found! - if len(out.Nodes) == 0 { - return nil, ErrNotFound - } - - // Perform a random shuffle - out.Nodes.Shuffle() - return f.buildResultsFromServiceNodes(out.Nodes, req, nil), nil -} - -// findWeight returns the weight of a service node. -func findWeight(node structs.CheckServiceNode) int { - // By default, when only_passing is false, warning and passing nodes are returned - // Those values will be used if using a client with support while server has no - // support for weights - weightPassing := 1 - weightWarning := 1 - if node.Service.Weights != nil { - weightPassing = node.Service.Weights.Passing - weightWarning = node.Service.Weights.Warning - } - serviceChecks := make(api.HealthChecks, 0, len(node.Checks)) - for _, c := range node.Checks { - if c.ServiceName == node.Service.Service || c.ServiceName == "" { - healthCheck := &api.HealthCheck{ - Node: c.Node, - CheckID: string(c.CheckID), - Name: c.Name, - Status: c.Status, - Notes: c.Notes, - Output: c.Output, - ServiceID: c.ServiceID, - ServiceName: c.ServiceName, - ServiceTags: c.ServiceTags, - } - serviceChecks = append(serviceChecks, healthCheck) - } - } - status := serviceChecks.AggregatedStatus() - switch status { - case api.HealthWarning: - return weightWarning - case api.HealthPassing: - return weightPassing - case api.HealthMaint: - // Not used in theory - return 0 - case api.HealthCritical: - // Should not happen since already filtered - return 0 - default: - // When non-standard status, return 1 - return 1 - } -} diff --git a/agent/discovery/query_fetcher_v1_ce.go b/agent/discovery/query_fetcher_v1_ce.go deleted file mode 100644 index 59d32e91e2..0000000000 --- a/agent/discovery/query_fetcher_v1_ce.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -//go:build !consulent - -package discovery - -import ( - "errors" - "fmt" - - "github.com/hashicorp/consul/acl" -) - -func (f *V1DataFetcher) NormalizeRequest(req *QueryPayload) { - // Nothing to do for CE - return -} - -func validateEnterpriseTenancy(req QueryTenancy) error { - if req.Namespace != "" || req.Partition != acl.DefaultPartitionName { - return ErrNotSupported - } - return nil -} - -func queryTenancyToEntMeta(_ QueryTenancy) acl.EnterpriseMeta { - return acl.EnterpriseMeta{} -} - -// fetchServiceFromSamenessGroup fetches a service from a sameness group. -func (f *V1DataFetcher) fetchServiceFromSamenessGroup(ctx Context, req *QueryPayload, cfg *V1DataFetcherDynamicConfig, lookupType LookupType) ([]*Result, error) { - f.logger.Trace(fmt.Sprintf("fetchServiceFromSamenessGroup - req: %+v", req)) - if req.Tenancy.SamenessGroup == "" { - return nil, errors.New("sameness groups must be provided for service lookups") - } - return f.fetchServiceBasedOnTenancy(ctx, req, cfg, lookupType) -} diff --git a/agent/discovery/query_fetcher_v1_ce_test.go b/agent/discovery/query_fetcher_v1_ce_test.go deleted file mode 100644 index 717475c9dc..0000000000 --- a/agent/discovery/query_fetcher_v1_ce_test.go +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -//go:build !consulent - -package discovery - -const ( - defaultTestNamespace = "" - defaultTestPartition = "" -) diff --git a/agent/discovery/query_fetcher_v1_test.go b/agent/discovery/query_fetcher_v1_test.go deleted file mode 100644 index 450b0cb13a..0000000000 --- a/agent/discovery/query_fetcher_v1_test.go +++ /dev/null @@ -1,206 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package discovery - -import ( - "context" - "errors" - "testing" - "time" - - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/agent/cache" - cachetype "github.com/hashicorp/consul/agent/cache-types" - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/sdk/testutil" -) - -// Test_FetchVirtualIP tests the FetchVirtualIP method in scenarios where the RPC -// call succeeds and fails. -func Test_FetchVirtualIP(t *testing.T) { - // set these to confirm that RPC call does not use them for this particular RPC - rc := &config.RuntimeConfig{ - DNSAllowStale: true, - DNSMaxStale: 100, - DNSUseCache: true, - DNSCacheMaxAge: 100, - } - tests := []struct { - name string - queryPayload *QueryPayload - context Context - expectedResult *Result - expectedErr error - }{ - { - name: "FetchVirtualIP returns result", - queryPayload: &QueryPayload{ - Name: "db", - Tenancy: QueryTenancy{ - Peer: "test-peer", - Namespace: defaultTestNamespace, - Partition: defaultTestPartition, - }, - }, - context: Context{ - Token: "test-token", - }, - expectedResult: &Result{ - Service: &Location{ - Name: "db", - Address: "192.168.10.10", - }, - Type: ResultTypeVirtual, - }, - expectedErr: nil, - }, - { - name: "FetchVirtualIP returns error", - queryPayload: &QueryPayload{ - Name: "db", - Tenancy: QueryTenancy{ - Peer: "test-peer", - Namespace: defaultTestNamespace, - Partition: defaultTestPartition}, - }, - context: Context{ - Token: "test-token", - }, - expectedResult: nil, - expectedErr: errors.New("test-error"), - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - logger := testutil.Logger(t) - mockRPC := cachetype.NewMockRPC(t) - mockRPC.On("RPC", mock.Anything, "Catalog.VirtualIPForService", mock.Anything, mock.Anything). - Return(tc.expectedErr). - Run(func(args mock.Arguments) { - req := args.Get(2).(*structs.ServiceSpecificRequest) - - // validate RPC options are not set from config for the VirtuaLIPForService RPC - require.False(t, req.AllowStale) - require.Equal(t, time.Duration(0), req.MaxStaleDuration) - require.False(t, req.UseCache) - require.Equal(t, time.Duration(0), req.MaxAge) - - // validate RPC options are set correctly from the queryPayload and context - require.Equal(t, tc.queryPayload.Tenancy.Peer, req.PeerName) - require.Equal(t, tc.queryPayload.Tenancy.Namespace, req.EnterpriseMeta.NamespaceOrEmpty()) - require.Equal(t, tc.queryPayload.Tenancy.Partition, req.EnterpriseMeta.PartitionOrEmpty()) - require.Equal(t, tc.context.Token, req.QueryOptions.Token) - - if tc.expectedErr == nil { - // set the out parameter to ensure that it is used to formulate the result.Address - reply := args.Get(3).(*string) - *reply = tc.expectedResult.Service.Address - } - }) - translateServicePortFunc := func(dc string, port int, taggedAddresses map[string]structs.ServiceAddress) int { return 0 } - rpcFuncForServiceNodes := func(ctx context.Context, req structs.ServiceSpecificRequest) (structs.IndexedCheckServiceNodes, cache.ResultMeta, error) { - return structs.IndexedCheckServiceNodes{}, cache.ResultMeta{}, nil - } - rpcFuncForSamenessGroup := func(ctx context.Context, req *structs.ConfigEntryQuery) (structs.SamenessGroupConfigEntry, cache.ResultMeta, error) { - return structs.SamenessGroupConfigEntry{}, cache.ResultMeta{}, nil - } - getFromCacheFunc := func(ctx context.Context, t string, r cache.Request) (interface{}, cache.ResultMeta, error) { - return nil, cache.ResultMeta{}, nil - } - - df := NewV1DataFetcher(rc, acl.DefaultEnterpriseMeta(), getFromCacheFunc, mockRPC.RPC, rpcFuncForServiceNodes, rpcFuncForSamenessGroup, translateServicePortFunc, logger) - - result, err := df.FetchVirtualIP(tc.context, tc.queryPayload) - require.Equal(t, tc.expectedErr, err) - require.Equal(t, tc.expectedResult, result) - }) - } -} - -// Test_FetchEndpoints tests the FetchEndpoints method in scenarios where the RPC -// call succeeds and fails. -func Test_FetchEndpoints(t *testing.T) { - // set these to confirm that RPC call does not use them for this particular RPC - rc := &config.RuntimeConfig{ - Datacenter: "dc2", - DNSAllowStale: true, - DNSMaxStale: 100, - DNSUseCache: true, - DNSCacheMaxAge: 100, - } - ctx := Context{ - Token: "test-token", - } - expectedResults := []*Result{ - { - Node: &Location{ - Name: "node-name", - Address: "node-address", - TaggedAddresses: map[string]*TaggedAddress{}, - }, - Service: &Location{ - Name: "service-name", - Address: "service-address", - TaggedAddresses: map[string]*TaggedAddress{}, - }, - Type: ResultTypeService, - DNS: DNSConfig{ - Weight: 1, - }, - Ports: []Port{ - { - Number: 0, - }, - }, - Tenancy: ResultTenancy{ - PeerName: "test-peer", - }, - }, - } - - logger := testutil.Logger(t) - mockRPC := cachetype.NewMockRPC(t) - translateServicePortFunc := func(dc string, port int, taggedAddresses map[string]structs.ServiceAddress) int { return 0 } - rpcFuncForSamenessGroup := func(ctx context.Context, req *structs.ConfigEntryQuery) (structs.SamenessGroupConfigEntry, cache.ResultMeta, error) { - return structs.SamenessGroupConfigEntry{}, cache.ResultMeta{}, nil - } - getFromCacheFunc := func(ctx context.Context, t string, r cache.Request) (interface{}, cache.ResultMeta, error) { - return nil, cache.ResultMeta{}, nil - } - rpcFuncForServiceNodes := func(ctx context.Context, req structs.ServiceSpecificRequest) (structs.IndexedCheckServiceNodes, cache.ResultMeta, error) { - return structs.IndexedCheckServiceNodes{ - Nodes: []structs.CheckServiceNode{ - { - Node: &structs.Node{ - Address: "node-address", - Node: "node-name", - }, - Service: &structs.NodeService{ - Address: "service-address", - Service: "service-name", - }, - }, - }, - }, cache.ResultMeta{}, nil - } - queryPayload := &QueryPayload{ - Name: "service-name", - Tenancy: QueryTenancy{ - Peer: "test-peer", - Namespace: defaultTestNamespace, - Partition: defaultTestPartition, - }, - } - - df := NewV1DataFetcher(rc, acl.DefaultEnterpriseMeta(), getFromCacheFunc, mockRPC.RPC, rpcFuncForServiceNodes, rpcFuncForSamenessGroup, translateServicePortFunc, logger) - - results, err := df.FetchEndpoints(ctx, queryPayload, LookupTypeService) - require.NoError(t, err) - require.Equal(t, expectedResults, results) -} diff --git a/agent/discovery/query_fetcher_v2.go b/agent/discovery/query_fetcher_v2.go deleted file mode 100644 index dc870e76ad..0000000000 --- a/agent/discovery/query_fetcher_v2.go +++ /dev/null @@ -1,365 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package discovery - -import ( - "context" - "fmt" - "math/rand" - "net" - "strings" - "sync/atomic" - - "golang.org/x/exp/slices" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/metadata" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/proto" - - "github.com/hashicorp/go-hclog" - - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/internal/resource" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" -) - -// V2DataFetcherDynamicConfig is used to store the dynamic configuration of the V2 data fetcher. -type V2DataFetcherDynamicConfig struct { - OnlyPassing bool -} - -// V2DataFetcher is used to fetch data from the V2 catalog. -type V2DataFetcher struct { - client pbresource.ResourceServiceClient - logger hclog.Logger - - // Requests inherit the partition of the agent unless otherwise specified. - defaultPartition string - - dynamicConfig atomic.Value -} - -// NewV2DataFetcher creates a new V2 data fetcher. -func NewV2DataFetcher(config *config.RuntimeConfig, client pbresource.ResourceServiceClient, logger hclog.Logger) *V2DataFetcher { - f := &V2DataFetcher{ - client: client, - logger: logger, - defaultPartition: config.PartitionOrDefault(), - } - f.LoadConfig(config) - return f -} - -// LoadConfig loads the configuration for the V2 data fetcher. -func (f *V2DataFetcher) LoadConfig(config *config.RuntimeConfig) { - dynamicConfig := &V2DataFetcherDynamicConfig{ - OnlyPassing: config.DNSOnlyPassing, - } - f.dynamicConfig.Store(dynamicConfig) -} - -// GetConfig loads the configuration for the V2 data fetcher. -func (f *V2DataFetcher) GetConfig() *V2DataFetcherDynamicConfig { - return f.dynamicConfig.Load().(*V2DataFetcherDynamicConfig) -} - -// FetchNodes fetches A/AAAA/CNAME -func (f *V2DataFetcher) FetchNodes(ctx Context, req *QueryPayload) ([]*Result, error) { - // TODO (v2-dns): NET-6623 - Implement FetchNodes - // Make sure that we validate that namespace is not provided here - return nil, fmt.Errorf("not implemented") -} - -// FetchEndpoints fetches records for A/AAAA/CNAME or SRV requests for services -func (f *V2DataFetcher) FetchEndpoints(reqContext Context, req *QueryPayload, lookupType LookupType) ([]*Result, error) { - if lookupType != LookupTypeService { - return nil, ErrNotSupported - } - - configCtx := f.dynamicConfig.Load().(*V2DataFetcherDynamicConfig) - - serviceEndpoints := pbcatalog.ServiceEndpoints{} - serviceEndpointsResource, err := f.fetchResource(reqContext, *req, pbcatalog.ServiceEndpointsType, &serviceEndpoints) - if err != nil { - return nil, err - } - - f.logger.Trace("shuffling endpoints", "name", req.Name, "endpoints", len(serviceEndpoints.Endpoints)) - - // Shuffle the endpoints slice - shuffleFunc := func(i, j int) { - serviceEndpoints.Endpoints[i], serviceEndpoints.Endpoints[j] = serviceEndpoints.Endpoints[j], serviceEndpoints.Endpoints[i] - } - rand.Shuffle(len(serviceEndpoints.Endpoints), shuffleFunc) - - // Convert the service endpoints to results up to the limit - limit := req.Limit - if len(serviceEndpoints.Endpoints) < limit || limit == 0 { - limit = len(serviceEndpoints.Endpoints) - } - - results := make([]*Result, 0, limit) - for _, endpoint := range serviceEndpoints.Endpoints[:limit] { - - // First we check the endpoint first to make sure that the requested port is matched from the service. - // We error here because we expect all endpoints to have the same ports as the service. - ports := getResultPorts(req, endpoint.Ports) //assuming the logic changed in getResultPorts - if len(ports) == 0 { - f.logger.Debug("could not find matching port in endpoint", "name", req.Name, "port", req.PortName) - return nil, ErrNotFound - } - - address, err := f.addressFromWorkloadAddresses(endpoint.Addresses, req.Name) - if err != nil { - return nil, err - } - - weight, ok := getEndpointWeight(endpoint, configCtx) - if !ok { - f.logger.Debug("endpoint filtered out because of health status", "name", req.Name, "endpoint", endpoint.GetTargetRef().GetName()) - continue - } - - result := &Result{ - Node: &Location{ - Address: address, - Name: endpoint.GetTargetRef().GetName(), - }, - Type: ResultTypeWorkload, - Tenancy: ResultTenancy{ - Namespace: serviceEndpointsResource.GetId().GetTenancy().GetNamespace(), - Partition: serviceEndpointsResource.GetId().GetTenancy().GetPartition(), - }, - DNS: DNSConfig{ - Weight: weight, - }, - Ports: ports, - } - results = append(results, result) - } - return results, nil -} - -// FetchVirtualIP fetches A/AAAA records for virtual IPs -func (f *V2DataFetcher) FetchVirtualIP(ctx Context, req *QueryPayload) (*Result, error) { - // TODO (v2-dns): NET-6624 - Implement FetchVirtualIP - return nil, fmt.Errorf("not implemented") -} - -// FetchRecordsByIp is used for PTR requests to look up a service/node from an IP. -func (f *V2DataFetcher) FetchRecordsByIp(ctx Context, ip net.IP) ([]*Result, error) { - // TODO (v2-dns): NET-6795 - Implement FetchRecordsByIp - // Validate non-nil IP - return nil, fmt.Errorf("not implemented") -} - -// FetchWorkload is used to fetch a single workload from the V2 catalog. -// V2-only. -func (f *V2DataFetcher) FetchWorkload(reqContext Context, req *QueryPayload) (*Result, error) { - workload := pbcatalog.Workload{} - resourceObj, err := f.fetchResource(reqContext, *req, pbcatalog.WorkloadType, &workload) - if err != nil { - return nil, err - } - - // First we check the endpoint first to make sure that the requested port is matched from the service. - // We error here because we expect all endpoints to have the same ports as the service. - ports := getResultPorts(req, workload.Ports) //assuming the logic changed in getResultPorts - if ports == nil || len(ports) == 0 { - f.logger.Debug("could not find matching port in endpoint", "name", req.Name, "port", req.PortName) - return nil, ErrNotFound - } - - address, err := f.addressFromWorkloadAddresses(workload.Addresses, req.Name) - if err != nil { - return nil, err - } - - tenancy := resourceObj.GetId().GetTenancy() - result := &Result{ - Node: &Location{ - Address: address, - Name: resourceObj.GetId().GetName(), - }, - Type: ResultTypeWorkload, - Tenancy: ResultTenancy{ - Namespace: tenancy.GetNamespace(), - Partition: tenancy.GetPartition(), - }, - Ports: ports, - } - - return result, nil -} - -// FetchPreparedQuery is used to fetch a prepared query from the V2 catalog. -// Deprecated in V2. -func (f *V2DataFetcher) FetchPreparedQuery(ctx Context, req *QueryPayload) ([]*Result, error) { - return nil, ErrNotSupported -} - -func (f *V2DataFetcher) NormalizeRequest(req *QueryPayload) { - // If we do not have an explicit partition in the request, we use the agent's - if req.Tenancy.Partition == "" { - req.Tenancy.Partition = f.defaultPartition - } -} - -// ValidateRequest throws an error is any of the deprecated V1 input fields are used in a QueryByName for this data fetcher. -func (f *V2DataFetcher) ValidateRequest(_ Context, req *QueryPayload) error { - if req.Tag != "" { - return ErrNotSupported - } - if req.SourceIP != nil { - return ErrNotSupported - } - return nil -} - -// fetchResource is used to read a single resource from the V2 catalog and cast into a concrete type. -func (f *V2DataFetcher) fetchResource(reqContext Context, req QueryPayload, kind *pbresource.Type, payload proto.Message) (*pbresource.Resource, error) { - // Query the resource service for the ServiceEndpoints by name and tenancy - resourceReq := pbresource.ReadRequest{ - Id: &pbresource.ID{ - Name: req.Name, - Type: kind, - Tenancy: queryTenancyToResourceTenancy(req.Tenancy), - }, - } - - f.logger.Trace("fetching "+kind.String(), "name", req.Name) - resourceCtx := metadata.AppendToOutgoingContext(context.Background(), "x-consul-token", reqContext.Token) - - // If the service is not found, return nil and an error equivalent to NXDOMAIN - response, err := f.client.Read(resourceCtx, &resourceReq) - switch { - case grpcNotFoundErr(err): - f.logger.Debug(kind.String()+" not found", "name", req.Name) - return nil, ErrNotFound - case err != nil: - f.logger.Error("error fetching "+kind.String(), "name", req.Name) - return nil, fmt.Errorf("error fetching %s: %w", kind.String(), err) - // default: fallthrough - } - - data := response.GetResource().GetData() - if err := data.UnmarshalTo(payload); err != nil { - f.logger.Error("error unmarshalling "+kind.String(), "name", req.Name) - return nil, fmt.Errorf("error unmarshalling %s: %w", kind.String(), err) - } - return response.GetResource(), nil -} - -// addressFromWorkloadAddresses returns one address from the workload addresses. -func (f *V2DataFetcher) addressFromWorkloadAddresses(addresses []*pbcatalog.WorkloadAddress, name string) (string, error) { - // TODO: (v2-dns): we will need to intelligently return the right workload address based on either the translate - // address setting or the locality of the requester. Workloads must have at least one. - // We also need to make sure that we filter out unix sockets here. - address := addresses[0].GetHost() - if strings.HasPrefix(address, "unix://") { - f.logger.Error("unix sockets are currently unsupported in workload results", "name", name) - return "", ErrNotFound - } - return address, nil -} - -// getEndpointWeight returns the weight of the endpoint and a boolean indicating if the endpoint should be included -// based on it's health status. -func getEndpointWeight(endpoint *pbcatalog.Endpoint, configCtx *V2DataFetcherDynamicConfig) (uint32, bool) { - health := endpoint.GetHealthStatus().Enum() - if health == nil { - return 0, false - } - - // Filter based on health status and agent config - // This is also a good opportunity to see if SRV weights are set - var weight uint32 - switch *health { - case pbcatalog.Health_HEALTH_PASSING: - weight = endpoint.GetDns().GetWeights().GetPassing() - case pbcatalog.Health_HEALTH_CRITICAL: - return 0, false // always filtered out - case pbcatalog.Health_HEALTH_WARNING: - if configCtx.OnlyPassing { - return 0, false // filtered out - } - weight = endpoint.GetDns().GetWeights().GetWarning() - default: - // Everything else can be filtered out - return 0, false - } - - // Important! double-check the weight in the case DNS weights are not set - if weight == 0 { - weight = 1 - } - return weight, true -} - -// getResultPorts conditionally returns ports from a map based on a query. The results are sorted by name. -func getResultPorts(req *QueryPayload, workloadPorts map[string]*pbcatalog.WorkloadPort) []Port { - if len(workloadPorts) == 0 { - return nil - } - - var ports []Port - if req.PortName != "" { - // Make sure the workload implements that port name. - if _, ok := workloadPorts[req.PortName]; !ok { - return nil - } - // In the case that the query asked for a specific port, we only return that port. - ports = []Port{ - { - Name: req.PortName, - Number: workloadPorts[req.PortName].Port, - }, - } - } else { - // If the client didn't specify a particular port, return all the workload ports. - for name, port := range workloadPorts { - ports = append(ports, Port{ - Name: name, - Number: port.Port, - }) - } - // Stable Sort - slices.SortStableFunc(ports, func(i, j Port) int { - if i.Name < j.Name { - return -1 - } else if i.Name > j.Name { - return 1 - } - return 0 - }) - } - return ports -} - -// queryTenancyToResourceTenancy converts a QueryTenancy to a pbresource.Tenancy. -func queryTenancyToResourceTenancy(qTenancy QueryTenancy) *pbresource.Tenancy { - rTenancy := resource.DefaultNamespacedTenancy() - - // If the request has any tenancy specified, it overrides the defaults. - if qTenancy.Namespace != "" { - rTenancy.Namespace = qTenancy.Namespace - } - // In the case of partition, we have the agent's partition as the fallback. - // That is handled in NormalizeRequest. - if qTenancy.Partition != "" { - rTenancy.Partition = qTenancy.Partition - } - - return rTenancy -} - -// grpcNotFoundErr returns true if the error is a gRPC status error with a code of NotFound. -func grpcNotFoundErr(err error) bool { - if err == nil { - return false - } - s, ok := status.FromError(err) - return ok && s.Code() == codes.NotFound -} diff --git a/agent/discovery/query_fetcher_v2_test.go b/agent/discovery/query_fetcher_v2_test.go deleted file mode 100644 index 5519ad13c7..0000000000 --- a/agent/discovery/query_fetcher_v2_test.go +++ /dev/null @@ -1,859 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package discovery - -import ( - "errors" - "fmt" - "testing" - - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/anypb" - - "github.com/hashicorp/consul/agent/config" - mockpbresource "github.com/hashicorp/consul/grpcmocks/proto-public/pbresource" - "github.com/hashicorp/consul/internal/resource" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil" -) - -var ( - unknownErr = errors.New("I don't feel so good") -) - -// Test_FetchService tests the FetchService method in scenarios where the RPC -// call succeeds and fails. -func Test_FetchWorkload(t *testing.T) { - - rc := &config.RuntimeConfig{ - DNSOnlyPassing: false, - } - - tests := []struct { - name string - queryPayload *QueryPayload - context Context - configureMockClient func(mockClient *mockpbresource.ResourceServiceClient_Expecter) - expectedResult *Result - expectedErr error - }{ - { - name: "FetchWorkload returns result", - queryPayload: &QueryPayload{ - Name: "foo-1234", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - result := getTestWorkloadResponse(t, "foo-1234", "", "") - mockClient.Read(mock.Anything, mock.Anything). - Return(result, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, result.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedResult: &Result{ - Node: &Location{Name: "foo-1234", Address: "1.2.3.4"}, - Type: ResultTypeWorkload, - Ports: []Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "mesh", - Number: 21000, - }, - }, - Tenancy: ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - }, - expectedErr: nil, - }, - { - name: "FetchWorkload for non-existent workload", - queryPayload: &QueryPayload{ - Name: "foo-1234", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - input := getTestWorkloadResponse(t, "foo-1234", "", "") - mockClient.Read(mock.Anything, mock.Anything). - Return(nil, status.Error(codes.NotFound, "not found")). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, input.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedResult: nil, - expectedErr: ErrNotFound, - }, - { - name: "FetchWorkload encounters a resource client error", - queryPayload: &QueryPayload{ - Name: "foo-1234", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - input := getTestWorkloadResponse(t, "foo-1234", "", "") - mockClient.Read(mock.Anything, mock.Anything). - Return(nil, unknownErr). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, input.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedResult: nil, - expectedErr: unknownErr, - }, - { - name: "FetchWorkload with a matching port", - queryPayload: &QueryPayload{ - Name: "foo-1234", - PortName: "api", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - result := getTestWorkloadResponse(t, "foo-1234", "", "") - mockClient.Read(mock.Anything, mock.Anything). - Return(result, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, result.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedResult: &Result{ - Node: &Location{Name: "foo-1234", Address: "1.2.3.4"}, - Type: ResultTypeWorkload, - Ports: []Port{ - { - Name: "api", - Number: 5678, - }, - }, - Tenancy: ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - }, - expectedErr: nil, - }, - { - name: "FetchWorkload with a matching port", - queryPayload: &QueryPayload{ - Name: "foo-1234", - PortName: "not-api", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - result := getTestWorkloadResponse(t, "foo-1234", "", "") - mockClient.Read(mock.Anything, mock.Anything). - Return(result, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, result.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedResult: nil, - expectedErr: ErrNotFound, - }, - { - name: "FetchWorkload returns result for non-default tenancy", - queryPayload: &QueryPayload{ - Name: "foo-1234", - Tenancy: QueryTenancy{ - Namespace: "test-namespace", - Partition: "test-partition", - }, - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - result := getTestWorkloadResponse(t, "foo-1234", "test-namespace", "test-partition") - mockClient.Read(mock.Anything, mock.Anything). - Return(result, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, result.GetResource().GetId().GetName(), req.Id.Name) - require.Equal(t, result.GetResource().GetId().GetTenancy().GetNamespace(), req.Id.Tenancy.Namespace) - require.Equal(t, result.GetResource().GetId().GetTenancy().GetPartition(), req.Id.Tenancy.Partition) - }) - }, - expectedResult: &Result{ - Node: &Location{Name: "foo-1234", Address: "1.2.3.4"}, - Type: ResultTypeWorkload, - Ports: []Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "mesh", - Number: 21000, - }, - }, - Tenancy: ResultTenancy{ - Namespace: "test-namespace", - Partition: "test-partition", - }, - }, - expectedErr: nil, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - logger := testutil.Logger(t) - - client := mockpbresource.NewResourceServiceClient(t) - mockClient := client.EXPECT() - tc.configureMockClient(mockClient) - - df := NewV2DataFetcher(rc, client, logger) - - result, err := df.FetchWorkload(tc.context, tc.queryPayload) - require.True(t, errors.Is(err, tc.expectedErr)) - require.Equal(t, tc.expectedResult, result) - }) - } -} - -// Test_V2FetchEndpoints the FetchService method in scenarios where the RPC -// call succeeds and fails. -func Test_V2FetchEndpoints(t *testing.T) { - - tests := []struct { - name string - queryPayload *QueryPayload - context Context - configureMockClient func(mockClient *mockpbresource.ResourceServiceClient_Expecter) - rc *config.RuntimeConfig - expectedResult []*Result - expectedErr error - verifyShuffle bool - }{ - { - name: "FetchEndpoints returns result", - queryPayload: &QueryPayload{ - Name: "consul", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - endpoints := []*pbcatalog.Endpoint{ - makeEndpoint("consul-1", "1.2.3.4", pbcatalog.Health_HEALTH_PASSING, 0, 0), - } - - serviceEndpoints := getTestEndpointsResponse(t, "", "", endpoints...) - mockClient.Read(mock.Anything, mock.Anything). - Return(serviceEndpoints, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, serviceEndpoints.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedResult: []*Result{ - { - Node: &Location{Name: "consul-1", Address: "1.2.3.4"}, - Type: ResultTypeWorkload, - Ports: []Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "mesh", - Number: 21000, - }, - }, - Tenancy: ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - DNS: DNSConfig{ - Weight: 1, - }, - }, - }, - }, - { - name: "FetchEndpoints returns empty result with no endpoints", - queryPayload: &QueryPayload{ - Name: "consul", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - - result := getTestEndpointsResponse(t, "", "") - mockClient.Read(mock.Anything, mock.Anything). - Return(result, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, result.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedResult: []*Result{}, - }, - { - name: "FetchEndpoints returns a name error when the ServiceEndpoint does not exist", - queryPayload: &QueryPayload{ - Name: "consul", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - - result := getTestEndpointsResponse(t, "", "") - mockClient.Read(mock.Anything, mock.Anything). - Return(nil, status.Error(codes.NotFound, "not found")). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, result.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedErr: ErrNotFound, - }, - { - name: "FetchEndpoints encounters a resource client error", - queryPayload: &QueryPayload{ - Name: "consul", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - - result := getTestEndpointsResponse(t, "", "") - mockClient.Read(mock.Anything, mock.Anything). - Return(nil, unknownErr). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, result.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedErr: unknownErr, - }, - { - name: "FetchEndpoints always filters out critical endpoints; DNS weights applied correctly", - queryPayload: &QueryPayload{ - Name: "consul", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - results := []*pbcatalog.Endpoint{ - makeEndpoint("consul-1", "1.2.3.4", pbcatalog.Health_HEALTH_PASSING, 2, 3), - makeEndpoint("consul-2", "2.3.4.5", pbcatalog.Health_HEALTH_WARNING, 2, 3), - makeEndpoint("consul-3", "3.4.5.6", pbcatalog.Health_HEALTH_CRITICAL, 2, 3), - } - - result := getTestEndpointsResponse(t, "", "", results...) - mockClient.Read(mock.Anything, mock.Anything). - Return(result, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, result.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedResult: []*Result{ - { - Node: &Location{Name: "consul-1", Address: "1.2.3.4"}, - Type: ResultTypeWorkload, - Tenancy: ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - DNS: DNSConfig{ - Weight: 2, - }, - Ports: []Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "mesh", - Number: 21000, - }, - }, - }, - { - Node: &Location{Name: "consul-2", Address: "2.3.4.5"}, - Type: ResultTypeWorkload, - Tenancy: ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - DNS: DNSConfig{ - Weight: 3, - }, - Ports: []Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "mesh", - Number: 21000, - }, - }, - }, - }, - }, - { - name: "FetchEndpoints filters out warning endpoints when DNSOnlyPassing is true", - queryPayload: &QueryPayload{ - Name: "consul", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - results := []*pbcatalog.Endpoint{ - makeEndpoint("consul-1", "1.2.3.4", pbcatalog.Health_HEALTH_PASSING, 2, 3), - makeEndpoint("consul-2", "2.3.4.5", pbcatalog.Health_HEALTH_WARNING, 2, 3), - makeEndpoint("consul-3", "3.4.5.6", pbcatalog.Health_HEALTH_CRITICAL, 2, 3), - } - - result := getTestEndpointsResponse(t, "", "", results...) - mockClient.Read(mock.Anything, mock.Anything). - Return(result, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, result.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - rc: &config.RuntimeConfig{ - DNSOnlyPassing: true, - }, - expectedResult: []*Result{ - { - Node: &Location{Name: "consul-1", Address: "1.2.3.4"}, - Type: ResultTypeWorkload, - Tenancy: ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - DNS: DNSConfig{ - Weight: 2, - }, - Ports: []Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "mesh", - Number: 21000, - }, - }, - }, - }, - }, - { - name: "FetchEndpoints shuffles the results", - queryPayload: &QueryPayload{ - Name: "consul", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - results := []*pbcatalog.Endpoint{ - // use a set of 10 elements, the odds of getting the same result are 1 in 3628800 - makeEndpoint("consul-1", "10.0.0.1", pbcatalog.Health_HEALTH_PASSING, 0, 0), - makeEndpoint("consul-2", "10.0.0.2", pbcatalog.Health_HEALTH_PASSING, 0, 0), - makeEndpoint("consul-3", "10.0.0.3", pbcatalog.Health_HEALTH_PASSING, 0, 0), - makeEndpoint("consul-4", "10.0.0.4", pbcatalog.Health_HEALTH_PASSING, 0, 0), - makeEndpoint("consul-5", "10.0.0.5", pbcatalog.Health_HEALTH_PASSING, 0, 0), - makeEndpoint("consul-6", "10.0.0.6", pbcatalog.Health_HEALTH_PASSING, 0, 0), - makeEndpoint("consul-7", "10.0.0.7", pbcatalog.Health_HEALTH_PASSING, 0, 0), - makeEndpoint("consul-8", "10.0.0.8", pbcatalog.Health_HEALTH_PASSING, 0, 0), - makeEndpoint("consul-9", "10.0.0.9", pbcatalog.Health_HEALTH_PASSING, 0, 0), - makeEndpoint("consul-10", "10.0.0.10", pbcatalog.Health_HEALTH_PASSING, 0, 0), - } - - result := getTestEndpointsResponse(t, "", "", results...) - mockClient.Read(mock.Anything, mock.Anything). - Return(result, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, result.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedResult: func() []*Result { - results := make([]*Result, 0, 10) - - for i := 0; i < 10; i++ { - name := fmt.Sprintf("consul-%d", i+1) - address := fmt.Sprintf("10.0.0.%d", i+1) - result := &Result{ - Node: &Location{Name: name, Address: address}, - Type: ResultTypeWorkload, - Tenancy: ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - Ports: []Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "mesh", - Number: 21000, - }, - }, - DNS: DNSConfig{ - Weight: 1, - }, - } - results = append(results, result) - } - return results - }(), - verifyShuffle: true, - }, - { - name: "FetchEndpoints returns only the specified limit", - queryPayload: &QueryPayload{ - Name: "consul", - Limit: 1, - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - results := []*pbcatalog.Endpoint{ - // intentionally all the same to make this easier to verify - makeEndpoint("consul-1", "10.0.0.1", pbcatalog.Health_HEALTH_PASSING, 0, 0), - makeEndpoint("consul-1", "10.0.0.1", pbcatalog.Health_HEALTH_PASSING, 0, 0), - makeEndpoint("consul-1", "10.0.0.1", pbcatalog.Health_HEALTH_PASSING, 0, 0), - } - - result := getTestEndpointsResponse(t, "", "", results...) - mockClient.Read(mock.Anything, mock.Anything). - Return(result, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, result.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedResult: []*Result{ - { - Node: &Location{Name: "consul-1", Address: "10.0.0.1"}, - Type: ResultTypeWorkload, - Tenancy: ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - DNS: DNSConfig{ - Weight: 1, - }, - Ports: []Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "mesh", - Number: 21000, - }, - }, - }, - }, - }, - { - name: "FetchEndpoints returns results with non-default tenancy", - queryPayload: &QueryPayload{ - Name: "consul", - Tenancy: QueryTenancy{ - Namespace: "test-namespace", - Partition: "test-partition", - }, - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - results := []*pbcatalog.Endpoint{ - // intentionally all the same to make this easier to verify - makeEndpoint("consul-1", "10.0.0.1", pbcatalog.Health_HEALTH_PASSING, 0, 0), - } - - result := getTestEndpointsResponse(t, "test-namespace", "test-partition", results...) - mockClient.Read(mock.Anything, mock.Anything). - Return(result, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, result.GetResource().GetId().GetName(), req.Id.Name) - require.Equal(t, result.GetResource().GetId().GetTenancy().GetNamespace(), req.Id.Tenancy.Namespace) - require.Equal(t, result.GetResource().GetId().GetTenancy().GetPartition(), req.Id.Tenancy.Partition) - }) - }, - expectedResult: []*Result{ - { - Node: &Location{Name: "consul-1", Address: "10.0.0.1"}, - Type: ResultTypeWorkload, - Tenancy: ResultTenancy{ - Namespace: "test-namespace", - Partition: "test-partition", - }, - DNS: DNSConfig{ - Weight: 1, - }, - Ports: []Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "mesh", - Number: 21000, - }, - }, - }, - }, - }, - { - name: "FetchEndpoints returns only a specific port if is one requested", - queryPayload: &QueryPayload{ - Name: "consul", - PortName: "api", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - endpoints := []*pbcatalog.Endpoint{ - makeEndpoint("consul-1", "10.0.0.1", pbcatalog.Health_HEALTH_PASSING, 0, 0), - } - - serviceEndpoints := getTestEndpointsResponse(t, "", "", endpoints...) - mockClient.Read(mock.Anything, mock.Anything). - Return(serviceEndpoints, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, serviceEndpoints.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedResult: []*Result{ - { - Node: &Location{Name: "consul-1", Address: "10.0.0.1"}, - Type: ResultTypeWorkload, - Ports: []Port{ - { - Name: "api", - Number: 5678, - }, - // No mesh port this time - }, - Tenancy: ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - DNS: DNSConfig{ - Weight: 1, - }, - }, - }, - }, - { - name: "FetchEndpoints returns a name error when a service doesn't implement the requested port", - queryPayload: &QueryPayload{ - Name: "consul", - PortName: "banana", - }, - context: Context{ - Token: "test-token", - }, - configureMockClient: func(mockClient *mockpbresource.ResourceServiceClient_Expecter) { - endpoints := []*pbcatalog.Endpoint{ - makeEndpoint("consul-1", "10.0.0.1", pbcatalog.Health_HEALTH_PASSING, 0, 0), - } - - serviceEndpoints := getTestEndpointsResponse(t, "", "", endpoints...) - mockClient.Read(mock.Anything, mock.Anything). - Return(serviceEndpoints, nil). - Once(). - Run(func(args mock.Arguments) { - req := args.Get(1).(*pbresource.ReadRequest) - require.Equal(t, serviceEndpoints.GetResource().GetId().GetName(), req.Id.Name) - }) - }, - expectedErr: ErrNotFound, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - logger := testutil.Logger(t) - - client := mockpbresource.NewResourceServiceClient(t) - mockClient := client.EXPECT() - tc.configureMockClient(mockClient) - - if tc.rc == nil { - tc.rc = &config.RuntimeConfig{ - DNSOnlyPassing: false, - } - } - - df := NewV2DataFetcher(tc.rc, client, logger) - - result, err := df.FetchEndpoints(tc.context, tc.queryPayload, LookupTypeService) - require.True(t, errors.Is(err, tc.expectedErr)) - - if tc.verifyShuffle { - require.NotEqualf(t, tc.expectedResult, result, "expected result to be shuffled. There is a small probability that it shuffled back to the original order. In that case, you may want to play the lottery.") - } - - require.ElementsMatchf(t, tc.expectedResult, result, "elements of results should match") - }) - } -} - -func getTestWorkloadResponse(t *testing.T, name string, nsOverride string, partitionOverride string) *pbresource.ReadResponse { - workload := &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - { - Host: "1.2.3.4", - Ports: []string{"api", "mesh"}, - }, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "api": { - Port: 5678, - }, - "mesh": { - Port: 21000, - }, - }, - Identity: "test-identity", - } - - data, err := anypb.New(workload) - require.NoError(t, err) - - resp := &pbresource.ReadResponse{ - Resource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: name, - Type: pbcatalog.WorkloadType, - Tenancy: resource.DefaultNamespacedTenancy(), - }, - Data: data, - }, - } - - if nsOverride != "" { - resp.Resource.Id.Tenancy.Namespace = nsOverride - } - if partitionOverride != "" { - resp.Resource.Id.Tenancy.Partition = partitionOverride - } - - return resp -} - -func makeEndpoint(name string, address string, health pbcatalog.Health, weightPassing, weightWarning uint32) *pbcatalog.Endpoint { - endpoint := &pbcatalog.Endpoint{ - Addresses: []*pbcatalog.WorkloadAddress{ - { - Host: address, - Ports: []string{"api"}, - }, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "api": { - Port: 5678, - }, - "mesh": { - Port: 21000, - }, - }, - HealthStatus: health, - TargetRef: &pbresource.ID{ - Name: name, - }, - } - - if weightPassing > 0 || weightWarning > 0 { - endpoint.Dns = &pbcatalog.DNSPolicy{ - Weights: &pbcatalog.Weights{ - Passing: weightPassing, - Warning: weightWarning, - }, - } - } - - return endpoint -} - -func getTestEndpointsResponse(t *testing.T, nsOverride string, partitionOverride string, endpoints ...*pbcatalog.Endpoint) *pbresource.ReadResponse { - serviceEndpoints := &pbcatalog.ServiceEndpoints{ - Endpoints: endpoints, - } - - data, err := anypb.New(serviceEndpoints) - require.NoError(t, err) - - resp := &pbresource.ReadResponse{ - Resource: &pbresource.Resource{ - Id: &pbresource.ID{ - Name: "consul", - Type: pbcatalog.ServiceType, - Tenancy: resource.DefaultNamespacedTenancy(), - }, - Data: data, - }, - } - - if nsOverride != "" { - resp.Resource.Id.Tenancy.Namespace = nsOverride - } - if partitionOverride != "" { - resp.Resource.Id.Tenancy.Partition = partitionOverride - } - - return resp -} diff --git a/agent/dns.go b/agent/dns.go index ebcafc1d61..dd34b3b8bd 100644 --- a/agent/dns.go +++ b/agent/dns.go @@ -8,6 +8,7 @@ import ( "encoding/hex" "errors" "fmt" + agentdns "github.com/hashicorp/consul/agent/dns" "math" "net" "regexp" @@ -61,7 +62,17 @@ type dnsSOAConfig struct { Minttl uint32 // 0 } -type dnsConfig struct { +// dnsRequestConfig returns the DNS request configuration that encapsulates: +// - the DNS server configuration. +// - the token from the request, if available. +// - the enterprise meta from the request, if available. +type dnsRequestConfig struct { + *dnsServerConfig + token string + defaultEnterpriseMeta acl.EnterpriseMeta +} + +type dnsServerConfig struct { AllowStale bool Datacenter string EnableTruncate bool @@ -92,6 +103,7 @@ type serviceLookup struct { PeerName string Datacenter string Service string + SamenessGroup string Tag string MaxRecursionLevel int Connect bool @@ -118,7 +130,7 @@ type DNSServer struct { altDomain string logger hclog.Logger - // config stores the config as an atomic value (for hot-reloading). It is always of type *dnsConfig + // config stores the config as an atomic value (for hot-reloading). It is always of type *dnsServerConfig config atomic.Value // recursorEnabled stores whever the recursor handler is enabled as an atomic flag. @@ -140,7 +152,7 @@ func NewDNSServer(a *Agent) (*DNSServer, error) { defaultEnterpriseMeta: *a.AgentEnterpriseMeta(), mux: dns.NewServeMux(), } - cfg, err := GetDNSConfig(a.config) + cfg, err := getDNSServerConfig(a.config) if err != nil { return nil, err } @@ -162,9 +174,9 @@ func NewDNSServer(a *Agent) (*DNSServer, error) { return srv, nil } -// GetDNSConfig takes global config and creates the config used by DNS server -func GetDNSConfig(conf *config.RuntimeConfig) (*dnsConfig, error) { - cfg := &dnsConfig{ +// getDNSServerConfig takes global config and creates the config used by DNS server +func getDNSServerConfig(conf *config.RuntimeConfig) (*dnsServerConfig, error) { + cfg := &dnsServerConfig{ AllowStale: conf.DNSAllowStale, ARecordLimit: conf.DNSARecordLimit, Datacenter: conf.Datacenter, @@ -216,7 +228,7 @@ func GetDNSConfig(conf *config.RuntimeConfig) (*dnsConfig, error) { // GetTTLForService Find the TTL for a given service. // return ttl, true if found, 0, false otherwise -func (cfg *dnsConfig) GetTTLForService(service string) (time.Duration, bool) { +func (cfg *dnsServerConfig) GetTTLForService(service string) (time.Duration, bool) { if cfg.TTLStrict != nil { ttl, ok := cfg.TTLStrict[service] if ok { @@ -268,7 +280,7 @@ func (d *DNSServer) GetAddr() string { } // toggleRecursorHandlerFromConfig enables or disables the recursor handler based on config idempotently -func (d *DNSServer) toggleRecursorHandlerFromConfig(cfg *dnsConfig) { +func (d *DNSServer) toggleRecursorHandlerFromConfig(cfg *dnsServerConfig) { shouldEnable := len(cfg.Recursors) > 0 if shouldEnable && atomic.CompareAndSwapUint32(&d.recursorEnabled, 0, 1) { @@ -286,7 +298,7 @@ func (d *DNSServer) toggleRecursorHandlerFromConfig(cfg *dnsConfig) { // ReloadConfig hot-reloads the server config with new parameters under config.RuntimeConfig.DNS* func (d *DNSServer) ReloadConfig(newCfg *config.RuntimeConfig) error { - cfg, err := GetDNSConfig(newCfg) + cfg, err := getDNSServerConfig(newCfg) if err != nil { return err } @@ -406,7 +418,7 @@ func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) { ) }(time.Now()) - cfg := d.config.Load().(*dnsConfig) + cfg := d.getRequestConfig(resp) // Setup the message response m := new(dns.Msg) @@ -429,7 +441,7 @@ func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) { args := structs.DCSpecificRequest{ Datacenter: datacenter, QueryOptions: structs.QueryOptions{ - Token: d.coalesceDNSToken(), + Token: d.coalesceDNSToken(cfg.token), AllowStale: cfg.AllowStale, }, } @@ -439,18 +451,11 @@ func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) { // server side to avoid transferring the entire node list. if err := d.agent.RPC(context.Background(), "Catalog.ListNodes", &args, &out); err == nil { for _, n := range out.Nodes { - lookup := serviceLookup{ - // Peering PTR lookups are currently not supported, so we don't - // need to populate that field for creating the node FQDN. - // PeerName: n.PeerName, - Datacenter: n.Datacenter, - EnterpriseMeta: *n.GetEnterpriseMeta(), - } arpa, _ := dns.ReverseAddr(n.Address) if arpa == qName { ptr := &dns.PTR{ Hdr: dns.RR_Header{Name: q.Name, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, - Ptr: nodeCanonicalDNSName(lookup, n.Node, d.domain), + Ptr: nodeCanonicalDNSName(n, d.domain), } m.Answer = append(m.Answer, ptr) break @@ -469,11 +474,11 @@ func (d *DNSServer) handlePtr(resp dns.ResponseWriter, req *dns.Msg) { sargs := structs.ServiceSpecificRequest{ Datacenter: datacenter, QueryOptions: structs.QueryOptions{ - Token: d.coalesceDNSToken(), + Token: d.coalesceDNSToken(cfg.token), AllowStale: cfg.AllowStale, }, ServiceAddress: serviceAddress, - EnterpriseMeta: *d.defaultEnterpriseMeta.WithWildcardNamespace(), + EnterpriseMeta: *cfg.defaultEnterpriseMeta.WithWildcardNamespace(), } var sout structs.IndexedServiceNodes @@ -542,7 +547,7 @@ func (d *DNSServer) handleQuery(resp dns.ResponseWriter, req *dns.Msg) { network = "tcp" } - cfg := d.config.Load().(*dnsConfig) + cfg := d.getRequestConfig(resp) // Set up the message response m := new(dns.Msg) @@ -571,7 +576,7 @@ func (d *DNSServer) handleQuery(resp dns.ResponseWriter, req *dns.Msg) { m.SetRcode(req, dns.RcodeNotImplemented) default: - err = d.dispatch(resp.RemoteAddr(), req, m, maxRecursionLevelDefault) + err = d.dispatch(resp.RemoteAddr(), req, m, cfg, maxRecursionLevelDefault) rCode := rCodeFromError(err) if rCode == dns.RcodeNameError || errors.Is(err, errNoData) { d.addSOAToMessage(cfg, m, q.Name) @@ -589,7 +594,7 @@ func (d *DNSServer) handleQuery(resp dns.ResponseWriter, req *dns.Msg) { } // Craft dns records for an SOA -func (d *DNSServer) makeSOARecord(cfg *dnsConfig, questionName string) *dns.SOA { +func (d *DNSServer) makeSOARecord(cfg *dnsRequestConfig, questionName string) *dns.SOA { domain := d.domain if d.altDomain != "" && strings.HasSuffix(questionName, "."+d.altDomain) { domain = d.altDomain @@ -614,19 +619,19 @@ func (d *DNSServer) makeSOARecord(cfg *dnsConfig, questionName string) *dns.SOA } // addSOA is used to add an SOA record to a message for the given domain -func (d *DNSServer) addSOAToMessage(cfg *dnsConfig, msg *dns.Msg, questionName string) { +func (d *DNSServer) addSOAToMessage(cfg *dnsRequestConfig, msg *dns.Msg, questionName string) { msg.Ns = append(msg.Ns, d.makeSOARecord(cfg, questionName)) } // getNameserversAndNodeRecord returns the names and ip addresses of up to three random servers // in the current cluster which serve as authoritative name servers for zone. -func (d *DNSServer) getNameserversAndNodeRecord(questionName string, cfg *dnsConfig, maxRecursionLevel int) (ns []dns.RR, extra []dns.RR) { +func (d *DNSServer) getNameserversAndNodeRecord(questionName string, cfg *dnsRequestConfig, maxRecursionLevel int) (ns []dns.RR, extra []dns.RR) { out, err := d.lookupServiceNodes(cfg, serviceLookup{ Datacenter: d.agent.config.Datacenter, Service: structs.ConsulServiceName, Connect: false, Ingress: false, - EnterpriseMeta: d.defaultEnterpriseMeta, + EnterpriseMeta: cfg.defaultEnterpriseMeta, }) if err != nil { d.logger.Warn("Unable to get list of servers", "error", err) @@ -664,7 +669,7 @@ func (d *DNSServer) getNameserversAndNodeRecord(questionName string, cfg *dnsCon } ns = append(ns, nsrr) - extra = append(extra, d.makeRecordFromNode(o.Node, dns.TypeANY, fqdn, cfg.NodeTTL, maxRecursionLevel)...) + extra = append(extra, d.makeRecordFromNode(o.Node, dns.TypeANY, fqdn, cfg, maxRecursionLevel)...) // don't provide more than 3 servers if len(ns) >= 3 { @@ -738,6 +743,10 @@ type queryLocality struct { // not be shared between datacenters. In all other cases, it should be considered a DC. peerOrDatacenter string + // samenessGroup is the samenessGroup name parsed from a label that has explicit parts. + // Example query: .service..sg.consul + samenessGroup string + acl.EnterpriseMeta } @@ -756,7 +765,7 @@ func (l queryLocality) effectiveDatacenter(defaultDC string) string { // dispatch is used to parse a request and invoke the correct handler. // parameter maxRecursionLevel will handle whether recursive call can be performed -func (d *DNSServer) dispatch(remoteAddr net.Addr, req, resp *dns.Msg, maxRecursionLevel int) error { +func (d *DNSServer) dispatch(remoteAddr net.Addr, req, resp *dns.Msg, cfg *dnsRequestConfig, maxRecursionLevel int) error { // Choose correct response domain respDomain := d.getResponseDomain(req.Question[0].Name) @@ -767,8 +776,6 @@ func (d *DNSServer) dispatch(remoteAddr net.Addr, req, resp *dns.Msg, maxRecursi // Split into the label parts labels := dns.SplitDomainName(qName) - cfg := d.config.Load().(*dnsConfig) - var queryKind string var queryParts []string var querySuffixes []string @@ -805,59 +812,56 @@ func (d *DNSServer) dispatch(remoteAddr net.Addr, req, resp *dns.Msg, maxRecursi return invalid() } - localities, err := d.parseSamenessGroupLocality(cfg, querySuffixes, invalid) + locality, err := d.parseSamenessGroupLocality(cfg, querySuffixes, invalid) if err != nil { return err } - // Loop over the localities and return as soon as a lookup is successful - for _, locality := range localities { - d.logger.Debug("labels", "querySuffixes", querySuffixes) + lookup := serviceLookup{ + Datacenter: locality.effectiveDatacenter(d.agent.config.Datacenter), + PeerName: locality.peer, + SamenessGroup: locality.samenessGroup, + Connect: false, + Ingress: false, + MaxRecursionLevel: maxRecursionLevel, + EnterpriseMeta: locality.EnterpriseMeta, + } - lookup := serviceLookup{ - Datacenter: locality.effectiveDatacenter(d.agent.config.Datacenter), - PeerName: locality.peer, - Connect: false, - Ingress: false, - MaxRecursionLevel: maxRecursionLevel, - EnterpriseMeta: locality.EnterpriseMeta, - } - // Only one of dc or peer can be used. - if lookup.PeerName != "" { - lookup.Datacenter = "" + // Only one of dc or peer can be used. + if lookup.PeerName != "" { + lookup.Datacenter = "" + } + + // Support RFC 2782 style syntax + if n == 2 && strings.HasPrefix(queryParts[1], "_") && strings.HasPrefix(queryParts[0], "_") { + // Grab the tag since we make nuke it if it's tcp + tag := queryParts[1][1:] + + // Treat _name._tcp.service.consul as a default, no need to filter on that tag + if tag == "tcp" { + tag = "" } - // Support RFC 2782 style syntax - if n == 2 && strings.HasPrefix(queryParts[1], "_") && strings.HasPrefix(queryParts[0], "_") { - // Grab the tag since we make nuke it if it's tcp - tag := queryParts[1][1:] - - // Treat _name._tcp.service.consul as a default, no need to filter on that tag - if tag == "tcp" { - tag = "" - } - - lookup.Tag = tag - lookup.Service = queryParts[0][1:] - // _name._tag.service.consul - } else { - // Consul 0.3 and prior format for SRV queries - // Support "." in the label, re-join all the parts - tag := "" - if n >= 2 { - tag = strings.Join(queryParts[:n-1], ".") - } - - lookup.Tag = tag - lookup.Service = queryParts[n-1] - // tag[.tag].name.service.consul + lookup.Tag = tag + lookup.Service = queryParts[0][1:] + // _name._tag.service.consul + } else { + // Consul 0.3 and prior format for SRV queries + // Support "." in the label, re-join all the parts + tag := "" + if n >= 2 { + tag = strings.Join(queryParts[:n-1], ".") } - err = d.handleServiceQuery(cfg, lookup, req, resp) - // Return if we are error free right away, otherwise loop again if we can - if err == nil { - return nil - } + lookup.Tag = tag + lookup.Service = queryParts[n-1] + // tag[.tag].name.service.consul + } + + err = d.handleServiceQuery(cfg, lookup, req, resp) + // Return if we are error free right away, otherwise loop again if we can + if err == nil { + return nil } // We've exhausted all DNS possibilities so return here @@ -904,7 +908,7 @@ func (d *DNSServer) dispatch(remoteAddr net.Addr, req, resp *dns.Msg, maxRecursi ServiceName: queryParts[len(queryParts)-1], EnterpriseMeta: locality.EnterpriseMeta, QueryOptions: structs.QueryOptions{ - Token: d.coalesceDNSToken(), + Token: d.coalesceDNSToken(cfg.token), }, } if args.PeerName == "" { @@ -1101,9 +1105,11 @@ func rCodeFromError(err error) int { return dns.RcodeSuccess case errors.Is(err, errECSNotGlobal): return rCodeFromError(errors.Unwrap(err)) - case errors.Is(err, errNameNotFound): - return dns.RcodeNameError - case structs.IsErrNoDCPath(err) || structs.IsErrQueryNotFound(err): + case errors.Is(err, errNameNotFound), + structs.IsErrNoDCPath(err), + structs.IsErrQueryNotFound(err), + structs.IsErrSamenessGroupMustBeDefaultForFailover(err), + structs.IsErrSamenessGroupNotFound(err): return dns.RcodeNameError default: return dns.RcodeServerFailure @@ -1111,7 +1117,7 @@ func rCodeFromError(err error) int { } // handleNodeQuery is used to handle a node query -func (d *DNSServer) handleNodeQuery(cfg *dnsConfig, lookup nodeLookup, req, resp *dns.Msg) error { +func (d *DNSServer) handleNodeQuery(cfg *dnsRequestConfig, lookup nodeLookup, req, resp *dns.Msg) error { // Only handle ANY, A, AAAA, and TXT type requests qType := req.Question[0].Qtype if qType != dns.TypeANY && qType != dns.TypeA && qType != dns.TypeAAAA && qType != dns.TypeTXT { @@ -1124,7 +1130,7 @@ func (d *DNSServer) handleNodeQuery(cfg *dnsConfig, lookup nodeLookup, req, resp PeerName: lookup.PeerName, Node: lookup.Node, QueryOptions: structs.QueryOptions{ - Token: d.coalesceDNSToken(), + Token: d.coalesceDNSToken(cfg.token), AllowStale: cfg.AllowStale, }, EnterpriseMeta: lookup.EnterpriseMeta, @@ -1150,7 +1156,7 @@ func (d *DNSServer) handleNodeQuery(cfg *dnsConfig, lookup nodeLookup, req, resp q := req.Question[0] // Only compute A and CNAME record if query is not TXT type if qType != dns.TypeTXT { - records := d.makeRecordFromNode(n, q.Qtype, q.Name, cfg.NodeTTL, lookup.MaxRecursionLevel) + records := d.makeRecordFromNode(n, q.Qtype, q.Name, cfg, lookup.MaxRecursionLevel) resp.Answer = append(resp.Answer, records...) } @@ -1163,7 +1169,7 @@ func (d *DNSServer) handleNodeQuery(cfg *dnsConfig, lookup nodeLookup, req, resp // lookupNode is used to look up a node in the Consul catalog within NodeServices. // If the config is set to UseCache, it will get the record from the agent cache. -func (d *DNSServer) lookupNode(cfg *dnsConfig, args *structs.NodeSpecificRequest) (*structs.IndexedNodeServices, error) { +func (d *DNSServer) lookupNode(cfg *dnsRequestConfig, args *structs.NodeSpecificRequest) (*structs.IndexedNodeServices, error) { var out structs.IndexedNodeServices useCache := cfg.UseCache @@ -1420,7 +1426,7 @@ func trimUDPResponse(req, resp *dns.Msg, udpAnswerLimit int) (trimmed bool) { } // trimDNSResponse will trim the response for UDP and TCP -func (d *DNSServer) trimDNSResponse(cfg *dnsConfig, network string, req, resp *dns.Msg) { +func (d *DNSServer) trimDNSResponse(cfg *dnsRequestConfig, network string, req, resp *dns.Msg) { var trimmed bool originalSize := resp.Len() originalNumRecords := len(resp.Answer) @@ -1445,21 +1451,27 @@ func (d *DNSServer) trimDNSResponse(cfg *dnsConfig, network string, req, resp *d // lookupServiceNodes is used to look up a node in the Consul health catalog within ServiceNodes. // If the config is set to UseCache, it will get the record from the agent cache. -func (d *DNSServer) lookupServiceNodes(cfg *dnsConfig, lookup serviceLookup) (structs.IndexedCheckServiceNodes, error) { +func (d *DNSServer) lookupServiceNodes(cfg *dnsRequestConfig, lookup serviceLookup) (structs.IndexedCheckServiceNodes, error) { serviceTags := []string{} if lookup.Tag != "" { serviceTags = []string{lookup.Tag} } + healthFilterType := structs.HealthFilterExcludeCritical + if cfg.OnlyPassing { + healthFilterType = structs.HealthFilterIncludeOnlyPassing + } args := structs.ServiceSpecificRequest{ - PeerName: lookup.PeerName, - Connect: lookup.Connect, - Ingress: lookup.Ingress, - Datacenter: lookup.Datacenter, - ServiceName: lookup.Service, - ServiceTags: serviceTags, - TagFilter: lookup.Tag != "", + PeerName: lookup.PeerName, + SamenessGroup: lookup.SamenessGroup, + Connect: lookup.Connect, + Ingress: lookup.Ingress, + Datacenter: lookup.Datacenter, + ServiceName: lookup.Service, + ServiceTags: serviceTags, + TagFilter: lookup.Tag != "", + HealthFilterType: healthFilterType, QueryOptions: structs.QueryOptions{ - Token: d.coalesceDNSToken(), + Token: d.coalesceDNSToken(cfg.token), AllowStale: cfg.AllowStale, MaxAge: cfg.CacheMaxAge, UseCache: cfg.UseCache, @@ -1473,16 +1485,11 @@ func (d *DNSServer) lookupServiceNodes(cfg *dnsConfig, lookup serviceLookup) (st return out, err } - // Filter out any service nodes due to health checks - // We copy the slice to avoid modifying the result if it comes from the cache - nodes := make(structs.CheckServiceNodes, len(out.Nodes)) - copy(nodes, out.Nodes) - out.Nodes = nodes.Filter(cfg.OnlyPassing) return out, nil } // handleServiceQuery is used to handle a service query -func (d *DNSServer) handleServiceQuery(cfg *dnsConfig, lookup serviceLookup, req, resp *dns.Msg) error { +func (d *DNSServer) handleServiceQuery(cfg *dnsRequestConfig, lookup serviceLookup, req, resp *dns.Msg) error { out, err := d.lookupServiceNodes(cfg, lookup) if err != nil { return fmt.Errorf("rpc request failed: %w", err) @@ -1531,13 +1538,13 @@ func ednsSubnetForRequest(req *dns.Msg) *dns.EDNS0_SUBNET { } // handlePreparedQuery is used to handle a prepared query. -func (d *DNSServer) handlePreparedQuery(cfg *dnsConfig, datacenter, query string, remoteAddr net.Addr, req, resp *dns.Msg, maxRecursionLevel int) error { +func (d *DNSServer) handlePreparedQuery(cfg *dnsRequestConfig, datacenter, query string, remoteAddr net.Addr, req, resp *dns.Msg, maxRecursionLevel int) error { // Execute the prepared query. args := structs.PreparedQueryExecuteRequest{ Datacenter: datacenter, QueryIDOrName: query, QueryOptions: structs.QueryOptions{ - Token: d.coalesceDNSToken(), + Token: d.coalesceDNSToken(cfg.token), AllowStale: cfg.AllowStale, MaxAge: cfg.CacheMaxAge, }, @@ -1625,7 +1632,7 @@ func (d *DNSServer) handlePreparedQuery(cfg *dnsConfig, datacenter, query string // lookupPreparedQuery is used to execute a PreparedQuery against the Consul catalog. // If the config is set to UseCache, it will use agent cache. -func (d *DNSServer) lookupPreparedQuery(cfg *dnsConfig, args structs.PreparedQueryExecuteRequest) (*structs.PreparedQueryExecuteResponse, error) { +func (d *DNSServer) lookupPreparedQuery(cfg *dnsRequestConfig, args structs.PreparedQueryExecuteRequest) (*structs.PreparedQueryExecuteResponse, error) { var out structs.PreparedQueryExecuteResponse RPC: @@ -1667,7 +1674,7 @@ RPC: } // addServiceNodeRecordsToMessage is used to add the node records for a service lookup -func (d *DNSServer) addServiceNodeRecordsToMessage(cfg *dnsConfig, lookup serviceLookup, nodes structs.CheckServiceNodes, req, resp *dns.Msg, ttl time.Duration, maxRecursionLevel int) { +func (d *DNSServer) addServiceNodeRecordsToMessage(cfg *dnsRequestConfig, lookup serviceLookup, nodes structs.CheckServiceNodes, req, resp *dns.Msg, ttl time.Duration, maxRecursionLevel int) { handled := make(map[string]struct{}) var answerCNAME []dns.RR = nil @@ -1758,20 +1765,20 @@ func findWeight(node structs.CheckServiceNode) int { } } -func (d *DNSServer) encodeIPAsFqdn(questionName string, lookup serviceLookup, ip net.IP) string { +func (d *DNSServer) encodeIPAsFqdn(questionName string, serviceNode structs.CheckServiceNode, ip net.IP) string { ipv4 := ip.To4() respDomain := d.getResponseDomain(questionName) ipStr := hex.EncodeToString(ip) if ipv4 != nil { ipStr = ipStr[len(ipStr)-(net.IPv4len*2):] } - if lookup.PeerName != "" { + if serviceNode.Service.PeerName != "" { // Exclude the datacenter from the FQDN on the addr for peers. // This technically makes no difference, since the addr endpoint ignores the DC // component of the request, but do it anyway for a less confusing experience. return fmt.Sprintf("%s.addr.%s", ipStr, respDomain) } - return fmt.Sprintf("%s.addr.%s.%s", ipStr, lookup.Datacenter, respDomain) + return fmt.Sprintf("%s.addr.%s.%s", ipStr, serviceNode.Node.Datacenter, respDomain) } // Craft dns records for a an A record for an IP address @@ -1806,7 +1813,8 @@ func makeARecord(qType uint16, ip net.IP, ttl time.Duration) dns.RR { // Craft dns records for a node // In case of an SRV query the answer will be a IN SRV and additional data will store an IN A to the node IP // Otherwise it will return a IN A record -func (d *DNSServer) makeRecordFromNode(node *structs.Node, qType uint16, qName string, ttl time.Duration, maxRecursionLevel int) []dns.RR { +func (d *DNSServer) makeRecordFromNode(node *structs.Node, qType uint16, qName string, cfg *dnsRequestConfig, maxRecursionLevel int) []dns.RR { + ttl := cfg.NodeTTL addrTranslate := dnsutil.TranslateAddressAcceptDomain if qType == dns.TypeA { addrTranslate |= dnsutil.TranslateAddressAcceptIPv4 @@ -1833,7 +1841,7 @@ func (d *DNSServer) makeRecordFromNode(node *structs.Node, qType uint16, qName s }) res = append(res, - d.resolveCNAME(d.config.Load().(*dnsConfig), dns.Fqdn(node.Address), maxRecursionLevel)..., + d.resolveCNAME(cfg, dns.Fqdn(node.Address), maxRecursionLevel)..., ) return res @@ -1860,7 +1868,7 @@ func (d *DNSServer) makeRecordFromServiceNode(lookup serviceLookup, serviceNode if q.Qtype == dns.TypeSRV { respDomain := d.getResponseDomain(q.Name) - nodeFQDN := nodeCanonicalDNSName(lookup, serviceNode.Node.Node, respDomain) + nodeFQDN := nodeCanonicalDNSName(serviceNode.Node, respDomain) answers := []dns.RR{ &dns.SRV{ Hdr: dns.RR_Header{ @@ -1895,7 +1903,7 @@ func (d *DNSServer) makeRecordFromIP(lookup serviceLookup, addr net.IP, serviceN } if q.Qtype == dns.TypeSRV { - ipFQDN := d.encodeIPAsFqdn(q.Name, lookup, addr) + ipFQDN := d.encodeIPAsFqdn(q.Name, serviceNode, addr) answers := []dns.RR{ &dns.SRV{ Hdr: dns.RR_Header{ @@ -1922,7 +1930,7 @@ func (d *DNSServer) makeRecordFromIP(lookup serviceLookup, addr net.IP, serviceN // Craft dns records for an FQDN // In case of an SRV query the answer will be a IN SRV and additional data will store an IN A to the IP // Otherwise it will return a CNAME and a IN A record -func (d *DNSServer) makeRecordFromFQDN(lookup serviceLookup, fqdn string, serviceNode structs.CheckServiceNode, req *dns.Msg, ttl time.Duration, cfg *dnsConfig, maxRecursionLevel int) ([]dns.RR, []dns.RR) { +func (d *DNSServer) makeRecordFromFQDN(lookup serviceLookup, fqdn string, serviceNode structs.CheckServiceNode, req *dns.Msg, ttl time.Duration, cfg *dnsRequestConfig, maxRecursionLevel int) ([]dns.RR, []dns.RR) { edns := req.IsEdns0() != nil q := req.Question[0] @@ -1978,7 +1986,7 @@ MORE_REC: } // Craft dns records from a CheckServiceNode struct -func (d *DNSServer) makeNodeServiceRecords(lookup serviceLookup, node structs.CheckServiceNode, req *dns.Msg, ttl time.Duration, cfg *dnsConfig, maxRecursionLevel int) ([]dns.RR, []dns.RR) { +func (d *DNSServer) makeNodeServiceRecords(lookup serviceLookup, node structs.CheckServiceNode, req *dns.Msg, ttl time.Duration, cfg *dnsRequestConfig, maxRecursionLevel int) ([]dns.RR, []dns.RR) { addrTranslate := dnsutil.TranslateAddressAcceptDomain if req.Question[0].Qtype == dns.TypeA { addrTranslate |= dnsutil.TranslateAddressAcceptIPv4 @@ -2052,7 +2060,7 @@ func (d *DNSServer) makeTXTRecordFromNodeMeta(qName string, node *structs.Node, } // addServiceSRVRecordsToMessage is used to add the SRV records for a service lookup -func (d *DNSServer) addServiceSRVRecordsToMessage(cfg *dnsConfig, lookup serviceLookup, nodes structs.CheckServiceNodes, req, resp *dns.Msg, ttl time.Duration, maxRecursionLevel int) { +func (d *DNSServer) addServiceSRVRecordsToMessage(cfg *dnsRequestConfig, lookup serviceLookup, nodes structs.CheckServiceNodes, req, resp *dns.Msg, ttl time.Duration, maxRecursionLevel int) { handled := make(map[string]struct{}) for _, node := range nodes { @@ -2076,14 +2084,14 @@ func (d *DNSServer) addServiceSRVRecordsToMessage(cfg *dnsConfig, lookup service resp.Extra = append(resp.Extra, extra...) if cfg.NodeMetaTXT { - resp.Extra = append(resp.Extra, d.makeTXTRecordFromNodeMeta(nodeCanonicalDNSName(lookup, node.Node.Node, respDomain), node.Node, ttl)...) + resp.Extra = append(resp.Extra, d.makeTXTRecordFromNodeMeta(nodeCanonicalDNSName(node.Node, respDomain), node.Node, ttl)...) } } } // handleRecurse is used to handle recursive DNS queries func (d *DNSServer) handleRecurse(resp dns.ResponseWriter, req *dns.Msg) { - cfg := d.config.Load().(*dnsConfig) + cfg := d.getRequestConfig(resp) q := req.Question[0] network := "udp" @@ -2159,7 +2167,7 @@ func (d *DNSServer) handleRecurse(resp dns.ResponseWriter, req *dns.Msg) { } // resolveCNAME is used to recursively resolve CNAME records -func (d *DNSServer) resolveCNAME(cfg *dnsConfig, name string, maxRecursionLevel int) []dns.RR { +func (d *DNSServer) resolveCNAME(cfg *dnsRequestConfig, name string, maxRecursionLevel int) []dns.RR { // If the CNAME record points to a Consul address, resolve it internally // Convert query to lowercase because DNS is case insensitive; d.domain and // d.altDomain are already converted @@ -2174,7 +2182,7 @@ func (d *DNSServer) resolveCNAME(cfg *dnsConfig, name string, maxRecursionLevel req.SetQuestion(name, dns.TypeANY) // TODO: handle error response - d.dispatch(nil, req, resp, maxRecursionLevel-1) + d.dispatch(nil, req, resp, cfg, maxRecursionLevel-1) return resp.Answer } @@ -2212,10 +2220,46 @@ func (d *DNSServer) resolveCNAME(cfg *dnsConfig, name string, maxRecursionLevel return nil } -func (d *DNSServer) coalesceDNSToken() string { +// coalesceDNSToken returns the ACL token to use for DNS queries. +// It returns the first token found in the following order: +// 1. The token from the request, if available. +// 2. The DNSToken from the agent. +// 3. The UserToken from the agent. +func (d *DNSServer) coalesceDNSToken(requestToken string) string { + // if the request token is set, which occurs when consul-dataplane forwards requests over gRPC, use it + if requestToken != "" { + return requestToken + } if d.agent.tokens.DNSToken() != "" { return d.agent.tokens.DNSToken() - } else { - return d.agent.tokens.UserToken() } + return d.agent.tokens.UserToken() +} + +// getRequestConfig returns the DNS request configuration that encapsulates: +// - the DNS server configuration. +// - the token from the request, if available. +// - the enterprise meta from the request, if available. +func (d *DNSServer) getRequestConfig(resp dns.ResponseWriter) *dnsRequestConfig { + dnsServerConfig := d.config.Load().(*dnsServerConfig) + requestDnsConfig := &dnsRequestConfig{ + dnsServerConfig: dnsServerConfig, + defaultEnterpriseMeta: d.defaultEnterpriseMeta, + } + + // DNS uses *dns.ServeMux, which takes a ResponseWriter interface and a DNS message both + // from the github.com/miekg/dns module, so we are limited in what we can pass as arguments. + // We can't pass a context.Context, so we have to add the RequestContext field to our + // implementation of dns.ResponseWriter to pass the context from the request. + if rw, ok := resp.(*agentdns.BufferResponseWriter); ok { + // use the ACL token from the request if available. Regular DNS hitting the + // agent will not carry a token, but gRPC requests from consul-dataplane will. + if rw.RequestContext.Token != "" { + requestDnsConfig.token = rw.RequestContext.Token + } + + d.setEnterpriseMetaFromRequestContext(rw.RequestContext, requestDnsConfig) + } + + return requestDnsConfig } diff --git a/agent/dns/buffer_response_writer.go b/agent/dns/buffer_response_writer.go new file mode 100644 index 0000000000..ed240758be --- /dev/null +++ b/agent/dns/buffer_response_writer.go @@ -0,0 +1,78 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +package dns + +import ( + "github.com/hashicorp/go-hclog" + "github.com/miekg/dns" + "net" +) + +// BufferResponseWriter writes a DNS response to a byte buffer. +type BufferResponseWriter struct { + // responseBuffer is the buffer that the response is written to. + responseBuffer []byte + // RequestContext is the context of the request that carries the ACL token and tenancy of the request. + RequestContext Context + // LocalAddress is the address of the server. + LocalAddress net.Addr + // RemoteAddress is the address of the client that sent the request. + RemoteAddress net.Addr + // Logger is the logger for the response writer. + Logger hclog.Logger +} + +var _ dns.ResponseWriter = (*BufferResponseWriter)(nil) + +// ResponseBuffer returns the buffer containing the response. +func (b *BufferResponseWriter) ResponseBuffer() []byte { + return b.responseBuffer +} + +// LocalAddr returns the net.Addr of the server +func (b *BufferResponseWriter) LocalAddr() net.Addr { + return b.LocalAddress +} + +// RemoteAddr returns the net.Addr of the client that sent the current request. +func (b *BufferResponseWriter) RemoteAddr() net.Addr { + return b.RemoteAddress +} + +// WriteMsg writes a reply back to the client. +func (b *BufferResponseWriter) WriteMsg(m *dns.Msg) error { + // Pack message to bytes first. + msgBytes, err := m.Pack() + if err != nil { + b.Logger.Error("error packing message", "err", err) + return err + } + b.responseBuffer = msgBytes + return nil +} + +// Write writes a raw buffer back to the client. +func (b *BufferResponseWriter) Write(m []byte) (int, error) { + b.Logger.Trace("Write was called") + return copy(b.responseBuffer, m), nil +} + +// Close closes the connection. +func (b *BufferResponseWriter) Close() error { + // There's nothing for us to do here as we don't handle the connection. + return nil +} + +// TsigStatus returns the status of the Tsig. +func (b *BufferResponseWriter) TsigStatus() error { + // TSIG doesn't apply to this response writer. + return nil +} + +// TsigTimersOnly sets the tsig timers only boolean. +func (b *BufferResponseWriter) TsigTimersOnly(bool) {} + +// Hijack lets the caller take over the connection. +// After a call to Hijack(), the DNS package will not do anything with the connection. { +func (b *BufferResponseWriter) Hijack() {} diff --git a/agent/dns/discovery_results_fetcher.go b/agent/dns/discovery_results_fetcher.go deleted file mode 100644 index 0a3b70a4bd..0000000000 --- a/agent/dns/discovery_results_fetcher.go +++ /dev/null @@ -1,350 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "encoding/hex" - "net" - "strings" - - "github.com/miekg/dns" - - "github.com/hashicorp/go-hclog" - - "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/agent/discovery" - "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/internal/dnsutil" -) - -// discoveryResultsFetcher is a facade for the DNS router to formulate -// and execute discovery queries. -type discoveryResultsFetcher struct{} - -// getQueryOptions is a struct to hold the options for getQueryResults method. -type getQueryOptions struct { - req *dns.Msg - reqCtx Context - qName string - remoteAddress net.Addr - processor DiscoveryQueryProcessor - logger hclog.Logger - domain string - altDomain string -} - -// getQueryResults returns a discovery.Result from a DNS message. -func (d discoveryResultsFetcher) getQueryResults(opts *getQueryOptions) ([]*discovery.Result, *discovery.Query, error) { - reqType := parseRequestType(opts.req) - - switch reqType { - case requestTypeConsul: - // This is a special case of discovery.QueryByName where we know that we need to query the consul service - // regardless of the question name. - query := &discovery.Query{ - QueryType: discovery.QueryTypeService, - QueryPayload: discovery.QueryPayload{ - Name: structs.ConsulServiceName, - Tenancy: discovery.QueryTenancy{ - // We specify the partition here so that in the case we are a client agent in a non-default partition. - // We don't want the query processors default partition to be used. - // This is a small hack because for V1 CE, this is not the correct default partition name, but we - // need to add something to disambiguate the empty field. - Partition: acl.DefaultPartitionName, //NOTE: note this won't work if we ever have V2 client agents - }, - Limit: 3, - }, - } - - results, err := opts.processor.QueryByName(query, discovery.Context{Token: opts.reqCtx.Token}) - return results, query, err - case requestTypeName: - query, err := buildQueryFromDNSMessage(opts.req, opts.reqCtx, opts.domain, opts.altDomain, opts.remoteAddress) - if err != nil { - opts.logger.Error("error building discovery query from DNS request", "error", err) - return nil, query, err - } - results, err := opts.processor.QueryByName(query, discovery.Context{Token: opts.reqCtx.Token}) - - if getErrorFromECSNotGlobalError(err) != nil { - opts.logger.Error("error processing discovery query", "error", err) - return nil, query, err - } - return results, query, err - case requestTypeIP: - ip := dnsutil.IPFromARPA(opts.qName) - if ip == nil { - opts.logger.Error("error building IP from DNS request", "name", opts.qName) - return nil, nil, errNameNotFound - } - results, err := opts.processor.QueryByIP(ip, discovery.Context{Token: opts.reqCtx.Token}) - return results, nil, err - case requestTypeAddress: - results, err := buildAddressResults(opts.req) - if err != nil { - opts.logger.Error("error processing discovery query", "error", err) - return nil, nil, err - } - return results, nil, nil - } - - opts.logger.Error("error parsing discovery query type", "requestType", reqType) - return nil, nil, errInvalidQuestion -} - -// buildQueryFromDNSMessage returns a discovery.Query from a DNS message. -func buildQueryFromDNSMessage(req *dns.Msg, reqCtx Context, domain, altDomain string, - remoteAddress net.Addr) (*discovery.Query, error) { - queryType, queryParts, querySuffixes := getQueryTypePartsAndSuffixesFromDNSMessage(req, domain, altDomain) - - queryTenancy, err := getQueryTenancy(reqCtx, queryType, querySuffixes) - if err != nil { - return nil, err - } - - name, tag := getQueryNameAndTagFromParts(queryType, queryParts) - - portName := parsePort(queryParts) - - switch { - case queryType == discovery.QueryTypeWorkload && req.Question[0].Qtype == dns.TypeSRV: - // Currently we do not support SRV records for workloads - return nil, errNotImplemented - case queryType == discovery.QueryTypeInvalid, name == "": - return nil, errInvalidQuestion - } - - return &discovery.Query{ - QueryType: queryType, - QueryPayload: discovery.QueryPayload{ - Name: name, - Tenancy: queryTenancy, - Tag: tag, - PortName: portName, - SourceIP: getSourceIP(req, queryType, remoteAddress), - }, - }, nil -} - -// buildAddressResults returns a discovery.Result from a DNS request for addr. records. -func buildAddressResults(req *dns.Msg) ([]*discovery.Result, error) { - domain := dns.CanonicalName(req.Question[0].Name) - labels := dns.SplitDomainName(domain) - hexadecimal := labels[0] - - if len(hexadecimal)/2 != 4 && len(hexadecimal)/2 != 16 { - return nil, errNameNotFound - } - - var ip net.IP - ip, err := hex.DecodeString(hexadecimal) - if err != nil { - return nil, errNameNotFound - } - - return []*discovery.Result{ - { - Node: &discovery.Location{ - Address: ip.String(), - }, - Type: discovery.ResultTypeNode, // We choose node by convention since we do not know the origin of the IP - }, - }, nil -} - -// getQueryNameAndTagFromParts returns the query name and tag from the query parts that are taken from the original dns question. -func getQueryNameAndTagFromParts(queryType discovery.QueryType, queryParts []string) (string, string) { - n := len(queryParts) - if n == 0 { - return "", "" - } - - switch queryType { - case discovery.QueryTypeService: - // Support RFC 2782 style syntax - if n == 2 && strings.HasPrefix(queryParts[1], "_") && strings.HasPrefix(queryParts[0], "_") { - // Grab the tag since we make nuke it if it's tcp - tag := queryParts[1][1:] - - // Treat _name._tcp.service.consul as a default, no need to filter on that tag - if tag == "tcp" { - tag = "" - } - - name := queryParts[0][1:] - // _name._tag.service.consul - return name, tag - } - return queryParts[n-1], "" - case discovery.QueryTypePreparedQuery: - name := "" - - // If the first and last DNS query parts begin with _, this is an RFC 2782 style SRV lookup. - // This allows for prepared query names to include "." (for backwards compatibility). - // Otherwise, this is a standard prepared query lookup. - if n >= 2 && strings.HasPrefix(queryParts[0], "_") && strings.HasPrefix(queryParts[n-1], "_") { - // The last DNS query part is the protocol field (ignored). - // All prior parts are the prepared query name or ID. - name = strings.Join(queryParts[:n-1], ".") - - // Strip leading underscore - name = name[1:] - } else { - // Allow a "." in the query name, just join all the parts. - name = strings.Join(queryParts, ".") - } - return name, "" - } - return queryParts[n-1], "" -} - -// getQueryTenancy returns a discovery.QueryTenancy from a DNS message. -func getQueryTenancy(reqCtx Context, queryType discovery.QueryType, querySuffixes []string) (discovery.QueryTenancy, error) { - labels, ok := parseLabels(querySuffixes) - if !ok { - return discovery.QueryTenancy{}, errNameNotFound - } - - // If we don't have an explicit partition/ns in the request, try the first fallback - // which was supplied in the request context. The agent's partition will be used as the last fallback - // later in the query processor. - if labels.Partition == "" { - labels.Partition = reqCtx.DefaultPartition - } - - if labels.Namespace == "" { - labels.Namespace = reqCtx.DefaultNamespace - } - - // If we have a sameness group, we can return early without further data massage. - if labels.SamenessGroup != "" { - return discovery.QueryTenancy{ - Namespace: labels.Namespace, - Partition: labels.Partition, - SamenessGroup: labels.SamenessGroup, - // Datacenter is not supported - }, nil - } - - if queryType == discovery.QueryTypeVirtual { - if labels.Peer == "" { - // If the peer name was not explicitly defined, fall back to the ambiguously-parsed version. - labels.Peer = labels.PeerOrDatacenter - } - } - - return discovery.QueryTenancy{ - Namespace: labels.Namespace, - Partition: labels.Partition, - Peer: labels.Peer, - Datacenter: getEffectiveDatacenter(labels), - }, nil -} - -// getEffectiveDatacenter returns the effective datacenter from the parsed labels. -func getEffectiveDatacenter(labels *parsedLabels) string { - switch { - case labels.Datacenter != "": - return labels.Datacenter - case labels.PeerOrDatacenter != "" && labels.Peer != labels.PeerOrDatacenter: - return labels.PeerOrDatacenter - } - return "" -} - -// getQueryTypePartsAndSuffixesFromDNSMessage returns the query type, the parts, and suffixes of the query name. -func getQueryTypePartsAndSuffixesFromDNSMessage(req *dns.Msg, domain, altDomain string) (queryType discovery.QueryType, parts []string, suffixes []string) { - // Get the QName without the domain suffix - // TODO (v2-dns): we will also need to handle the "failover" and "no-failover" suffixes here. - // They come AFTER the domain. See `stripAnyFailoverSuffix` in router.go - qName := trimDomainFromQuestionName(req.Question[0].Name, domain, altDomain) - - // Split into the label parts - labels := dns.SplitDomainName(qName) - - done := false - for i := len(labels) - 1; i >= 0 && !done; i-- { - queryType = getQueryTypeFromLabels(labels[i]) - switch queryType { - case discovery.QueryTypeService, discovery.QueryTypeWorkload, - discovery.QueryTypeConnect, discovery.QueryTypeVirtual, discovery.QueryTypeIngress, - discovery.QueryTypeNode, discovery.QueryTypePreparedQuery: - parts = labels[:i] - suffixes = labels[i+1:] - done = true - case discovery.QueryTypeInvalid: - fallthrough - default: - // If this is a SRV query the "service" label is optional, we add it back to use the - // existing code-path. - if req.Question[0].Qtype == dns.TypeSRV && strings.HasPrefix(labels[i], "_") { - queryType = discovery.QueryTypeService - parts = labels[:i+1] - suffixes = labels[i+1:] - done = true - } - } - } - - return queryType, parts, suffixes -} - -// trimDomainFromQuestionName returns the question name without the domain suffix. -func trimDomainFromQuestionName(questionName, domain, altDomain string) string { - qName := dns.CanonicalName(questionName) - longer := domain - shorter := altDomain - - if len(shorter) > len(longer) { - longer, shorter = shorter, longer - } - - if strings.HasSuffix(qName, "."+strings.TrimLeft(longer, ".")) { - return strings.TrimSuffix(qName, longer) - } - return strings.TrimSuffix(qName, shorter) -} - -// getQueryTypeFromLabels returns the query type from the labels. -func getQueryTypeFromLabels(label string) discovery.QueryType { - switch label { - case "service": - return discovery.QueryTypeService - case "connect": - return discovery.QueryTypeConnect - case "virtual": - return discovery.QueryTypeVirtual - case "ingress": - return discovery.QueryTypeIngress - case "node": - return discovery.QueryTypeNode - case "query": - return discovery.QueryTypePreparedQuery - case "workload": - return discovery.QueryTypeWorkload - default: - return discovery.QueryTypeInvalid - } -} - -// getSourceIP returns the source IP from the dns request. -func getSourceIP(req *dns.Msg, queryType discovery.QueryType, remoteAddr net.Addr) (sourceIP net.IP) { - if queryType == discovery.QueryTypePreparedQuery { - subnet := ednsSubnetForRequest(req) - - if subnet != nil { - sourceIP = subnet.Address - } else { - switch v := remoteAddr.(type) { - case *net.UDPAddr: - sourceIP = v.IP - case *net.TCPAddr: - sourceIP = v.IP - case *net.IPAddr: - sourceIP = v.IP - } - } - } - return sourceIP -} diff --git a/agent/dns/discovery_results_fetcher_test.go b/agent/dns/discovery_results_fetcher_test.go deleted file mode 100644 index 01792a0646..0000000000 --- a/agent/dns/discovery_results_fetcher_test.go +++ /dev/null @@ -1,220 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "testing" - - "github.com/miekg/dns" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/discovery" -) - -// testCaseBuildQueryFromDNSMessage is a test case for the buildQueryFromDNSMessage function. -type testCaseBuildQueryFromDNSMessage struct { - name string - request *dns.Msg - requestContext *Context - expectedQuery *discovery.Query -} - -// Test_buildQueryFromDNSMessage tests the buildQueryFromDNSMessage function. -func Test_buildQueryFromDNSMessage(t *testing.T) { - - testCases := []testCaseBuildQueryFromDNSMessage{ - // virtual ip queries - { - name: "test A 'virtual.' query", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "db.virtual.consul", // "intentionally missing the trailing dot" - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - expectedQuery: &discovery.Query{ - QueryType: discovery.QueryTypeVirtual, - QueryPayload: discovery.QueryPayload{ - Name: "db", - Tenancy: discovery.QueryTenancy{}, - }, - }, - }, - { - name: "test A 'virtual.' with kitchen sink labels", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "db.virtual.banana.ns.orange.ap.foo.peer.consul", // "intentionally missing the trailing dot" - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - expectedQuery: &discovery.Query{ - QueryType: discovery.QueryTypeVirtual, - QueryPayload: discovery.QueryPayload{ - Name: "db", - Tenancy: discovery.QueryTenancy{ - Peer: "foo", - Namespace: "banana", - Partition: "orange", - }, - }, - }, - }, - { - name: "test A 'virtual.' with implicit peer", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "db.virtual.foo.consul", // "intentionally missing the trailing dot" - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - expectedQuery: &discovery.Query{ - QueryType: discovery.QueryTypeVirtual, - QueryPayload: discovery.QueryPayload{ - Name: "db", - Tenancy: discovery.QueryTenancy{ - Peer: "foo", - }, - }, - }, - }, - { - name: "test A 'virtual.' with implicit peer and namespace query", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "db.virtual.frontend.foo.consul", // "intentionally missing the trailing dot" - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - expectedQuery: &discovery.Query{ - QueryType: discovery.QueryTypeVirtual, - QueryPayload: discovery.QueryPayload{ - Name: "db", - Tenancy: discovery.QueryTenancy{ - Namespace: "frontend", - Peer: "foo", - }, - }, - }, - }, - { - name: "test A 'workload.'", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.workload.consul", // "intentionally missing the trailing dot" - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - expectedQuery: &discovery.Query{ - QueryType: discovery.QueryTypeWorkload, - QueryPayload: discovery.QueryPayload{ - Name: "foo", - Tenancy: discovery.QueryTenancy{}, - }, - }, - }, - { - name: "test A 'workload.' with all possible labels", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "api.port.foo.workload.banana.ns.orange.ap.apple.peer.consul", // "intentionally missing the trailing dot" - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - requestContext: &Context{ - DefaultPartition: "default-partition", - }, - expectedQuery: &discovery.Query{ - QueryType: discovery.QueryTypeWorkload, - QueryPayload: discovery.QueryPayload{ - Name: "foo", - PortName: "api", - Tenancy: discovery.QueryTenancy{ - Namespace: "banana", - Partition: "orange", - Peer: "apple", - }, - }, - }, - }, - { - name: "test sameness group with all possible labels", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.service.apple.sg.banana.ns.orange.ap.consul", // "intentionally missing the trailing dot" - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - requestContext: &Context{ - DefaultPartition: "default-partition", - }, - expectedQuery: &discovery.Query{ - QueryType: discovery.QueryTypeService, - QueryPayload: discovery.QueryPayload{ - Name: "foo", - Tenancy: discovery.QueryTenancy{ - Namespace: "banana", - Partition: "orange", - SamenessGroup: "apple", - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - context := tc.requestContext - if context == nil { - context = &Context{} - } - query, err := buildQueryFromDNSMessage(tc.request, *context, "consul.", ".", nil) - require.NoError(t, err) - assert.Equal(t, tc.expectedQuery, query) - }) - } -} diff --git a/agent/dns/dns_address.go b/agent/dns/dns_address.go deleted file mode 100644 index e1e61f689f..0000000000 --- a/agent/dns/dns_address.go +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 -package dns - -import ( - "github.com/miekg/dns" - "net" - "strings" -) - -func newDNSAddress(addr string) *dnsAddress { - a := &dnsAddress{} - a.SetAddress(addr) - return a -} - -// dnsAddress is a wrapper around a string that represents a DNS address and -// provides helper methods for determining whether it is an IP or FQDN and -// whether it is internal or external to the domain. -type dnsAddress struct { - addr string - - // store an IP so helpers don't have to parse it multiple times - ip net.IP -} - -// SetAddress sets the address field and the ip field if the string is an IP. -func (a *dnsAddress) SetAddress(addr string) { - a.addr = addr - a.ip = net.ParseIP(addr) -} - -// IP returns the IP address if the address is an IP. -func (a *dnsAddress) IP() net.IP { - return a.ip -} - -// IsIP returns true if the address is an IP. -func (a *dnsAddress) IsIP() bool { - return a.IP() != nil -} - -// IsIPV4 returns true if the address is an IPv4 address. -func (a *dnsAddress) IsIPV4() bool { - if a.IP() == nil { - return false - } - return a.IP().To4() != nil -} - -// FQDN returns the FQDN if the address is not an IP. -func (a *dnsAddress) FQDN() string { - if !a.IsEmptyString() && !a.IsIP() { - return dns.Fqdn(a.addr) - } - return "" -} - -// IsFQDN returns true if the address is a FQDN and not an IP. -func (a *dnsAddress) IsFQDN() bool { - return !a.IsEmptyString() && !a.IsIP() && dns.IsFqdn(a.FQDN()) -} - -// String returns the address as a string. -func (a *dnsAddress) String() string { - return a.addr -} - -// IsEmptyString returns true if the address is an empty string. -func (a *dnsAddress) IsEmptyString() bool { - return a.addr == "" -} - -// IsInternalFQDN returns true if the address is a FQDN and is internal to the domain. -func (a *dnsAddress) IsInternalFQDN(domain string) bool { - return !a.IsIP() && a.IsFQDN() && strings.HasSuffix(a.FQDN(), domain) -} - -// IsInternalFQDNOrIP returns true if the address is an IP or a FQDN and is internal to the domain. -func (a *dnsAddress) IsInternalFQDNOrIP(domain string) bool { - return a.IsIP() || a.IsInternalFQDN(domain) -} - -// IsExternalFQDN returns true if the address is a FQDN and is external to the domain. -func (a *dnsAddress) IsExternalFQDN(domain string) bool { - return !a.IsIP() && a.IsFQDN() && strings.Count(a.FQDN(), ".") > 1 && !strings.HasSuffix(a.FQDN(), domain) -} diff --git a/agent/dns/dns_address_test.go b/agent/dns/dns_address_test.go deleted file mode 100644 index 93460437f2..0000000000 --- a/agent/dns/dns_address_test.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 -package dns - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func Test_dnsAddress(t *testing.T) { - const domain = "consul." - type expectedResults struct { - isIp bool - stringResult string - fqdn string - isFQDN bool - isEmptyString bool - isExternalFQDN bool - isInternalFQDN bool - isInternalFQDNOrIP bool - } - type testCase struct { - name string - input string - expectedResults expectedResults - } - testCases := []testCase{ - { - name: "empty string", - input: "", - expectedResults: expectedResults{ - isIp: false, - stringResult: "", - fqdn: "", - isFQDN: false, - isEmptyString: true, - isExternalFQDN: false, - isInternalFQDN: false, - isInternalFQDNOrIP: false, - }, - }, - { - name: "ipv4 address", - input: "127.0.0.1", - expectedResults: expectedResults{ - isIp: true, - stringResult: "127.0.0.1", - fqdn: "", - isFQDN: false, - isEmptyString: false, - isExternalFQDN: false, - isInternalFQDN: false, - isInternalFQDNOrIP: true, - }, - }, - { - name: "ipv6 address", - input: "2001:db8:1:2:cafe::1337", - expectedResults: expectedResults{ - isIp: true, - stringResult: "2001:db8:1:2:cafe::1337", - fqdn: "", - isFQDN: false, - isEmptyString: false, - isExternalFQDN: false, - isInternalFQDN: false, - isInternalFQDNOrIP: true, - }, - }, - { - name: "internal FQDN without trailing period", - input: "web.service.consul", - expectedResults: expectedResults{ - isIp: false, - stringResult: "web.service.consul", - fqdn: "web.service.consul.", - isFQDN: true, - isEmptyString: false, - isExternalFQDN: false, - isInternalFQDN: true, - isInternalFQDNOrIP: true, - }, - }, - { - name: "internal FQDN with period", - input: "web.service.consul.", - expectedResults: expectedResults{ - isIp: false, - stringResult: "web.service.consul.", - fqdn: "web.service.consul.", - isFQDN: true, - isEmptyString: false, - isExternalFQDN: false, - isInternalFQDN: true, - isInternalFQDNOrIP: true, - }, - }, - { - name: "server name", - input: "web.", - expectedResults: expectedResults{ - isIp: false, - stringResult: "web.", - fqdn: "web.", - isFQDN: true, - isEmptyString: false, - isExternalFQDN: false, - isInternalFQDN: false, - isInternalFQDNOrIP: false, - }, - }, - { - name: "external FQDN without trailing period", - input: "web.service.vault", - expectedResults: expectedResults{ - isIp: false, - stringResult: "web.service.vault", - fqdn: "web.service.vault.", - isFQDN: true, - isEmptyString: false, - isExternalFQDN: true, - isInternalFQDN: false, - isInternalFQDNOrIP: false, - }, - }, - { - name: "external FQDN with trailing period", - input: "web.service.vault.", - expectedResults: expectedResults{ - isIp: false, - stringResult: "web.service.vault.", - fqdn: "web.service.vault.", - isFQDN: true, - isEmptyString: false, - isExternalFQDN: true, - isInternalFQDN: false, - isInternalFQDNOrIP: false, - }, - }, - { - name: "another external FQDN", - input: "www.google.com", - expectedResults: expectedResults{ - isIp: false, - stringResult: "www.google.com", - fqdn: "www.google.com.", - isFQDN: true, - isEmptyString: false, - isExternalFQDN: true, - isInternalFQDN: false, - isInternalFQDNOrIP: false, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - dnsAddress := newDNSAddress(tc.input) - assert.Equal(t, tc.expectedResults.isIp, dnsAddress.IsIP()) - assert.Equal(t, tc.expectedResults.stringResult, dnsAddress.String()) - assert.Equal(t, tc.expectedResults.isFQDN, dnsAddress.IsFQDN()) - assert.Equal(t, tc.expectedResults.isEmptyString, dnsAddress.IsEmptyString()) - assert.Equal(t, tc.expectedResults.isExternalFQDN, dnsAddress.IsExternalFQDN(domain)) - assert.Equal(t, tc.expectedResults.isInternalFQDN, dnsAddress.IsInternalFQDN(domain)) - assert.Equal(t, tc.expectedResults.isInternalFQDNOrIP, dnsAddress.IsInternalFQDNOrIP(domain)) - }) - } -} diff --git a/agent/dns/dns_record_maker.go b/agent/dns/dns_record_maker.go deleted file mode 100644 index c63d9d500b..0000000000 --- a/agent/dns/dns_record_maker.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "regexp" - "strings" - "time" - - "github.com/miekg/dns" - - "github.com/hashicorp/consul/agent/discovery" -) - -// dnsRecordMaker creates DNS records to be used when generating -// responses to dns requests. -type dnsRecordMaker struct{} - -// makeSOA returns an SOA record for the given domain and config. -func (dnsRecordMaker) makeSOA(domain string, cfg *RouterDynamicConfig) dns.RR { - return &dns.SOA{ - Hdr: dns.RR_Header{ - Name: domain, - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - // Has to be consistent with MinTTL to avoid invalidation - Ttl: cfg.SOAConfig.Minttl, - }, - Ns: "ns." + domain, - Serial: uint32(time.Now().Unix()), - Mbox: "hostmaster." + domain, - Refresh: cfg.SOAConfig.Refresh, - Retry: cfg.SOAConfig.Retry, - Expire: cfg.SOAConfig.Expire, - Minttl: cfg.SOAConfig.Minttl, - } -} - -// makeNS returns an NS record for the given domain and fqdn. -func (dnsRecordMaker) makeNS(domain, fqdn string, ttl uint32) dns.RR { - return &dns.NS{ - Hdr: dns.RR_Header{ - Name: domain, - Rrtype: dns.TypeNS, - Class: dns.ClassINET, - Ttl: ttl, - }, - Ns: fqdn, - } -} - -// makeIPBasedRecord returns an A or AAAA record for the given name and IP. -// Note: we might want to pass in the Query Name here, which is used in addr. and virtual. queries -// since there is only ever one result. Right now choosing to leave it off for simplification. -func (dnsRecordMaker) makeIPBasedRecord(name string, addr *dnsAddress, ttl uint32) dns.RR { - - if addr.IsIPV4() { - // check if the query type is A for IPv4 or ANY - return &dns.A{ - Hdr: dns.RR_Header{ - Name: name, - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: ttl, - }, - A: addr.IP(), - } - } - - return &dns.AAAA{ - Hdr: dns.RR_Header{ - Name: name, - Rrtype: dns.TypeAAAA, - Class: dns.ClassINET, - Ttl: ttl, - }, - AAAA: addr.IP(), - } -} - -// makeCNAME returns a CNAME record for the given name and target. -func (dnsRecordMaker) makeCNAME(name string, target string, ttl uint32) *dns.CNAME { - return &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: name, - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - Ttl: ttl, - }, - Target: dns.Fqdn(target), - } -} - -// makeSRV returns an SRV record for the given name and target. -func (dnsRecordMaker) makeSRV(name, target string, weight uint16, ttl uint32, port *discovery.Port) *dns.SRV { - return &dns.SRV{ - Hdr: dns.RR_Header{ - Name: name, - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - Ttl: ttl, - }, - Priority: 1, - Weight: weight, - Port: uint16(port.Number), - Target: target, - } -} - -// makeTXT returns a TXT record for the given name and result metadata. -func (dnsRecordMaker) makeTXT(name string, metadata map[string]string, ttl uint32) []dns.RR { - extra := make([]dns.RR, 0, len(metadata)) - for key, value := range metadata { - txt := value - if !strings.HasPrefix(strings.ToLower(key), "rfc1035-") { - txt = encodeKVasRFC1464(key, value) - } - - extra = append(extra, &dns.TXT{ - Hdr: dns.RR_Header{ - Name: name, - Rrtype: dns.TypeTXT, - Class: dns.ClassINET, - Ttl: ttl, - }, - Txt: []string{txt}, - }) - } - return extra -} - -// encodeKVasRFC1464 encodes a key-value pair according to RFC1464 -func encodeKVasRFC1464(key, value string) (txt string) { - // For details on these replacements c.f. https://www.ietf.org/rfc/rfc1464.txt - key = strings.Replace(key, "`", "``", -1) - key = strings.Replace(key, "=", "`=", -1) - - // Backquote the leading spaces - leadingSpacesRE := regexp.MustCompile("^ +") - numLeadingSpaces := len(leadingSpacesRE.FindString(key)) - key = leadingSpacesRE.ReplaceAllString(key, strings.Repeat("` ", numLeadingSpaces)) - - // Backquote the trailing spaces - numTrailingSpaces := len(trailingSpacesRE.FindString(key)) - key = trailingSpacesRE.ReplaceAllString(key, strings.Repeat("` ", numTrailingSpaces)) - - value = strings.Replace(value, "`", "``", -1) - - return key + "=" + value -} diff --git a/agent/dns/dns_record_maker_test.go b/agent/dns/dns_record_maker_test.go deleted file mode 100644 index 3235ab3ef0..0000000000 --- a/agent/dns/dns_record_maker_test.go +++ /dev/null @@ -1,228 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/discovery" -) - -func TestDNSRecordMaker_makeSOA(t *testing.T) { - cfg := &RouterDynamicConfig{ - SOAConfig: SOAConfig{ - Refresh: 1, - Retry: 2, - Expire: 3, - Minttl: 4, - }, - } - domain := "testdomain." - expected := &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 4, - }, - Ns: "ns.testdomain.", - Serial: uint32(time.Now().Unix()), - Mbox: "hostmaster.testdomain.", - Refresh: 1, - Retry: 2, - Expire: 3, - Minttl: 4, - } - actual := dnsRecordMaker{}.makeSOA(domain, cfg) - require.Equal(t, expected, actual) -} - -func TestDNSRecordMaker_makeNS(t *testing.T) { - domain := "testdomain." - fqdn := "ns.testdomain." - ttl := uint32(123) - expected := &dns.NS{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeNS, - Class: dns.ClassINET, - Ttl: 123, - }, - Ns: "ns.testdomain.", - } - actual := dnsRecordMaker{}.makeNS(domain, fqdn, ttl) - require.Equal(t, expected, actual) -} - -func TestDNSRecordMaker_makeIPBasedRecord(t *testing.T) { - ipv4Addr := newDNSAddress("1.2.3.4") - ipv6Addr := newDNSAddress("2001:db8:1:2:cafe::1337") - testCases := []struct { - name string - recordHeaderName string - addr *dnsAddress - ttl uint32 - expected dns.RR - }{ - { - name: "IPv4", - recordHeaderName: "my.service.dc1.consul.", - addr: ipv4Addr, - ttl: 123, - expected: &dns.A{ - Hdr: dns.RR_Header{ - Name: "my.service.dc1.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: ipv4Addr.IP(), - }, - }, - { - name: "IPv6", - recordHeaderName: "my.service.dc1.consul.", - addr: ipv6Addr, - ttl: 123, - expected: &dns.AAAA{ - Hdr: dns.RR_Header{ - Name: "my.service.dc1.consul.", - Rrtype: dns.TypeAAAA, - Class: dns.ClassINET, - Ttl: 123, - }, - AAAA: ipv6Addr.IP(), - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - actual := dnsRecordMaker{}.makeIPBasedRecord(tc.recordHeaderName, tc.addr, tc.ttl) - require.Equal(t, tc.expected, actual) - }) - } -} - -func TestDNSRecordMaker_makeCNAME(t *testing.T) { - name := "my.service.consul." - target := "foo" - ttl := uint32(123) - expected := &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: "my.service.consul.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - Ttl: 123, - }, - Target: "foo.", - } - actual := dnsRecordMaker{}.makeCNAME(name, target, ttl) - require.Equal(t, expected, actual) -} - -func TestDNSRecordMaker_makeSRV(t *testing.T) { - name := "my.service.consul." - target := "foo" - ttl := uint32(123) - expected := &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "my.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - Ttl: 123, - }, - Priority: 1, - Weight: uint16(345), - Port: uint16(234), - Target: "foo", - } - actual := dnsRecordMaker{}.makeSRV(name, target, uint16(345), ttl, &discovery.Port{Number: 234}) - require.Equal(t, expected, actual) -} - -func TestDNSRecordMaker_makeTXT(t *testing.T) { - testCases := []struct { - name string - metadata map[string]string - ttl uint32 - expected []dns.RR - }{ - { - name: "single metadata", - metadata: map[string]string{ - "key": "value", - }, - ttl: 123, - expected: []dns.RR{ - &dns.TXT{ - Hdr: dns.RR_Header{ - Name: "my.service.consul.", - Rrtype: dns.TypeTXT, - Class: dns.ClassINET, - Ttl: 123, - }, - Txt: []string{"key=value"}, - }, - }, - }, - { - name: "multiple metadata entries", - metadata: map[string]string{ - "key1": "value1", - "key2": "value2", - }, - ttl: 123, - expected: []dns.RR{ - &dns.TXT{ - Hdr: dns.RR_Header{ - Name: "my.service.consul.", - Rrtype: dns.TypeTXT, - Class: dns.ClassINET, - Ttl: 123, - }, - Txt: []string{"key1=value1"}, - }, - &dns.TXT{ - Hdr: dns.RR_Header{ - Name: "my.service.consul.", - Rrtype: dns.TypeTXT, - Class: dns.ClassINET, - Ttl: 123, - }, - Txt: []string{"key2=value2"}, - }, - }, - }, - { - name: "'rfc1035-' prefixed- metadata entry", - metadata: map[string]string{ - "rfc1035-key": "value", - }, - ttl: 123, - expected: []dns.RR{ - &dns.TXT{ - Hdr: dns.RR_Header{ - Name: "my.service.consul.", - Rrtype: dns.TypeTXT, - Class: dns.ClassINET, - Ttl: 123, - }, - Txt: []string{"value"}, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - actual := dnsRecordMaker{}.makeTXT("my.service.consul.", tc.metadata, tc.ttl) - require.ElementsMatchf(t, tc.expected, actual, "expected: %v, actual: %v", tc.expected, actual) - }) - } -} diff --git a/agent/dns/message_serializer.go b/agent/dns/message_serializer.go deleted file mode 100644 index 2369b28319..0000000000 --- a/agent/dns/message_serializer.go +++ /dev/null @@ -1,656 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "encoding/hex" - "fmt" - "net" - "strings" - "time" - - "github.com/miekg/dns" - - "github.com/hashicorp/consul/agent/discovery" - "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/internal/dnsutil" -) - -// messageSerializer is the high level orchestrator for generating the Answer, -// Extra, and Ns records for a DNS response. -type messageSerializer struct{} - -// serializeOptions are the options for serializing a discovery.Result into a DNS message. -type serializeOptions struct { - req *dns.Msg - reqCtx Context - query *discovery.Query - results []*discovery.Result - resp *dns.Msg - cfg *RouterDynamicConfig - responseDomain string - remoteAddress net.Addr - maxRecursionLevel int - dnsRecordMaker dnsRecordMaker - translateAddressFunc func(dc string, addr string, taggedAddresses map[string]string, accept dnsutil.TranslateAddressAccept) string - translateServiceAddressFunc func(dc string, address string, taggedAddresses map[string]structs.ServiceAddress, accept dnsutil.TranslateAddressAccept) string - resolveCnameFunc func(cfgContext *RouterDynamicConfig, name string, reqCtx Context, remoteAddress net.Addr, maxRecursionLevel int) []dns.RR -} - -// serializeQueryResults converts a discovery.Result into a DNS message. -func (d messageSerializer) serialize(opts *serializeOptions) (*dns.Msg, error) { - resp := new(dns.Msg) - resp.SetReply(opts.req) - resp.Compress = !opts.cfg.DisableCompression - resp.Authoritative = true - resp.RecursionAvailable = canRecurse(opts.cfg) - opts.resp = resp - - qType := opts.req.Question[0].Qtype - reqType := parseRequestType(opts.req) - - // Always add the SOA record if requested. - if qType == dns.TypeSOA { - resp.Answer = append(resp.Answer, opts.dnsRecordMaker.makeSOA(opts.responseDomain, opts.cfg)) - } - - switch { - case qType == dns.TypeSOA, reqType == requestTypeAddress: - for _, result := range opts.results { - for _, port := range getPortsFromResult(result) { - ans, ex, ns := d.getAnswerExtraAndNs(serializeToGetAnswerExtraAndNsOptions(opts, result, port)) - resp.Answer = append(resp.Answer, ans...) - resp.Extra = append(resp.Extra, ex...) - resp.Ns = append(resp.Ns, ns...) - } - } - case qType == dns.TypeSRV: - handled := make(map[string]struct{}) - for _, result := range opts.results { - for _, port := range getPortsFromResult(result) { - - // Avoid duplicate entries, possible if a node has - // the same service the same port, etc. - - // The datacenter should be empty during translation if it is a peering lookup. - // This should be fine because we should always prefer the WAN address. - - address := "" - if result.Service != nil { - address = result.Service.Address - } else { - address = result.Node.Address - } - tuple := fmt.Sprintf("%s:%s:%d", result.Node.Name, address, port.Number) - if _, ok := handled[tuple]; ok { - continue - } - handled[tuple] = struct{}{} - - ans, ex, ns := d.getAnswerExtraAndNs(serializeToGetAnswerExtraAndNsOptions(opts, result, port)) - resp.Answer = append(resp.Answer, ans...) - resp.Extra = append(resp.Extra, ex...) - resp.Ns = append(resp.Ns, ns...) - } - } - default: - // default will send it to where it does some de-duping while it calls getAnswerExtraAndNs and recurses. - d.appendResultsToDNSResponse(opts) - } - - if opts.query != nil && opts.query.QueryType != discovery.QueryTypeVirtual && - len(resp.Answer) == 0 && len(resp.Extra) == 0 { - return nil, discovery.ErrNoData - } - - return resp, nil -} - -// appendResultsToDNSResponse builds dns message from the discovery results and -// appends them to the dns response. -func (d messageSerializer) appendResultsToDNSResponse(opts *serializeOptions) { - - // Always add the SOA record if requested. - if opts.req.Question[0].Qtype == dns.TypeSOA { - opts.resp.Answer = append(opts.resp.Answer, opts.dnsRecordMaker.makeSOA(opts.responseDomain, opts.cfg)) - } - - handled := make(map[string]struct{}) - var answerCNAME []dns.RR = nil - - count := 0 - for _, result := range opts.results { - for _, port := range getPortsFromResult(result) { - - // Add the node record - had_answer := false - ans, extra, _ := d.getAnswerExtraAndNs(serializeToGetAnswerExtraAndNsOptions(opts, result, port)) - opts.resp.Extra = append(opts.resp.Extra, extra...) - - if len(ans) == 0 { - continue - } - - // Avoid duplicate entries, possible if a node has - // the same service on multiple ports, etc. - if _, ok := handled[ans[0].String()]; ok { - continue - } - handled[ans[0].String()] = struct{}{} - - switch ans[0].(type) { - case *dns.CNAME: - // keep track of the first CNAME + associated RRs but don't add to the resp.Answer yet - // this will only be added if no non-CNAME RRs are found - if len(answerCNAME) == 0 { - answerCNAME = ans - } - default: - opts.resp.Answer = append(opts.resp.Answer, ans...) - had_answer = true - } - - if had_answer { - count++ - if count == opts.cfg.ARecordLimit { - // We stop only if greater than 0 or we reached the limit - return - } - } - } - } - if len(opts.resp.Answer) == 0 && len(answerCNAME) > 0 { - opts.resp.Answer = answerCNAME - } -} - -// getAnswerExtraAndNsOptions are the options for getting the Answer, Extra, and Ns records for a DNS response. -type getAnswerExtraAndNsOptions struct { - port discovery.Port - result *discovery.Result - req *dns.Msg - reqCtx Context - query *discovery.Query - results []*discovery.Result - resp *dns.Msg - cfg *RouterDynamicConfig - responseDomain string - remoteAddress net.Addr - maxRecursionLevel int - ttl uint32 - dnsRecordMaker dnsRecordMaker - translateAddressFunc func(dc string, addr string, taggedAddresses map[string]string, accept dnsutil.TranslateAddressAccept) string - translateServiceAddressFunc func(dc string, address string, taggedAddresses map[string]structs.ServiceAddress, accept dnsutil.TranslateAddressAccept) string - resolveCnameFunc func(cfgContext *RouterDynamicConfig, name string, reqCtx Context, remoteAddress net.Addr, maxRecursionLevel int) []dns.RR -} - -// getAnswerAndExtra creates the dns answer and extra from discovery results. -func (d messageSerializer) getAnswerExtraAndNs(opts *getAnswerExtraAndNsOptions) (answer []dns.RR, extra []dns.RR, ns []dns.RR) { - serviceAddress, nodeAddress := d.getServiceAndNodeAddresses(opts) - qName := opts.req.Question[0].Name - ttlLookupName := qName - if opts.query != nil { - ttlLookupName = opts.query.QueryPayload.Name - } - - opts.ttl = getTTLForResult(ttlLookupName, opts.result.DNS.TTL, opts.query, opts.cfg) - - qType := opts.req.Question[0].Qtype - - // TODO (v2-dns): skip records that refer to a workload/node that don't have a valid DNS name. - - // Special case responses - switch { - // PTR requests are first since they are a special case of domain overriding question type - case parseRequestType(opts.req) == requestTypeIP: - ptrTarget := "" - if opts.result.Type == discovery.ResultTypeNode { - ptrTarget = opts.result.Node.Name - } else if opts.result.Type == discovery.ResultTypeService { - ptrTarget = opts.result.Service.Name - } - - ptr := &dns.PTR{ - Hdr: dns.RR_Header{Name: qName, Rrtype: dns.TypePTR, Class: dns.ClassINET, Ttl: 0}, - Ptr: canonicalNameForResult(opts.result.Type, ptrTarget, opts.responseDomain, opts.result.Tenancy, opts.port.Name), - } - answer = append(answer, ptr) - case qType == dns.TypeNS: - resultType := opts.result.Type - target := opts.result.Node.Name - if parseRequestType(opts.req) == requestTypeConsul && resultType == discovery.ResultTypeService { - resultType = discovery.ResultTypeNode - } - fqdn := canonicalNameForResult(resultType, target, opts.responseDomain, opts.result.Tenancy, opts.port.Name) - extraRecord := opts.dnsRecordMaker.makeIPBasedRecord(fqdn, nodeAddress, opts.ttl) - - answer = append(answer, opts.dnsRecordMaker.makeNS(opts.responseDomain, fqdn, opts.ttl)) - extra = append(extra, extraRecord) - case qType == dns.TypeSOA: - // to be returned in the result. - fqdn := canonicalNameForResult(opts.result.Type, opts.result.Node.Name, opts.responseDomain, opts.result.Tenancy, opts.port.Name) - extraRecord := opts.dnsRecordMaker.makeIPBasedRecord(fqdn, nodeAddress, opts.ttl) - - ns = append(ns, opts.dnsRecordMaker.makeNS(opts.responseDomain, fqdn, opts.ttl)) - extra = append(extra, extraRecord) - case qType == dns.TypeSRV: - // We put A/AAAA/CNAME records in the additional section for SRV requests - a, e := d.getAnswerExtrasForAddressAndTarget(nodeAddress, serviceAddress, opts) - answer = append(answer, a...) - extra = append(extra, e...) - - default: - a, e := d.getAnswerExtrasForAddressAndTarget(nodeAddress, serviceAddress, opts) - answer = append(answer, a...) - extra = append(extra, e...) - } - - a, e := getAnswerAndExtraTXT(opts.req, opts.cfg, qName, opts.result, opts.ttl, - opts.responseDomain, opts.query, &opts.port, opts.dnsRecordMaker) - answer = append(answer, a...) - extra = append(extra, e...) - return -} - -// getServiceAndNodeAddresses returns the service and node addresses from a discovery result. -func (d messageSerializer) getServiceAndNodeAddresses(opts *getAnswerExtraAndNsOptions) (*dnsAddress, *dnsAddress) { - addrTranslate := dnsutil.TranslateAddressAcceptDomain - if opts.req.Question[0].Qtype == dns.TypeA { - addrTranslate |= dnsutil.TranslateAddressAcceptIPv4 - } else if opts.req.Question[0].Qtype == dns.TypeAAAA { - addrTranslate |= dnsutil.TranslateAddressAcceptIPv6 - } else { - addrTranslate |= dnsutil.TranslateAddressAcceptAny - } - - // The datacenter should be empty during translation if it is a peering lookup. - // This should be fine because we should always prefer the WAN address. - serviceAddress := newDNSAddress("") - if opts.result.Service != nil { - sa := opts.translateServiceAddressFunc(opts.result.Tenancy.Datacenter, - opts.result.Service.Address, getServiceAddressMapFromLocationMap(opts.result.Service.TaggedAddresses), - addrTranslate) - serviceAddress = newDNSAddress(sa) - } - nodeAddress := newDNSAddress("") - if opts.result.Node != nil { - na := opts.translateAddressFunc(opts.result.Tenancy.Datacenter, opts.result.Node.Address, - getStringAddressMapFromTaggedAddressMap(opts.result.Node.TaggedAddresses), addrTranslate) - nodeAddress = newDNSAddress(na) - } - return serviceAddress, nodeAddress -} - -// getAnswerExtrasForAddressAndTarget creates the dns answer and extra from nodeAddress and serviceAddress dnsAddress pairs. -func (d messageSerializer) getAnswerExtrasForAddressAndTarget(nodeAddress *dnsAddress, - serviceAddress *dnsAddress, opts *getAnswerExtraAndNsOptions) (answer []dns.RR, extra []dns.RR) { - qName := opts.req.Question[0].Name - reqType := parseRequestType(opts.req) - - switch { - case (reqType == requestTypeAddress || opts.result.Type == discovery.ResultTypeVirtual) && - serviceAddress.IsEmptyString() && nodeAddress.IsIP(): - a, e := getAnswerExtrasForIP(qName, nodeAddress, opts.req.Question[0], - reqType, opts.result, opts.ttl, opts.responseDomain, &opts.port, opts.dnsRecordMaker) - answer = append(answer, a...) - extra = append(extra, e...) - - case opts.result.Type == discovery.ResultTypeNode && nodeAddress.IsIP(): - canonicalNodeName := canonicalNameForResult(opts.result.Type, - opts.result.Node.Name, opts.responseDomain, opts.result.Tenancy, opts.port.Name) - a, e := getAnswerExtrasForIP(canonicalNodeName, nodeAddress, opts.req.Question[0], reqType, - opts.result, opts.ttl, opts.responseDomain, &opts.port, opts.dnsRecordMaker) - answer = append(answer, a...) - extra = append(extra, e...) - - case opts.result.Type == discovery.ResultTypeNode && !nodeAddress.IsIP(): - a, e := d.makeRecordFromFQDN(serviceAddress.FQDN(), opts) - answer = append(answer, a...) - extra = append(extra, e...) - - case serviceAddress.IsEmptyString() && nodeAddress.IsEmptyString(): - return nil, nil - - // There is no service address and the node address is an IP - case serviceAddress.IsEmptyString() && nodeAddress.IsIP(): - resultType := discovery.ResultTypeNode - if opts.result.Type == discovery.ResultTypeWorkload { - resultType = discovery.ResultTypeWorkload - } - canonicalNodeName := canonicalNameForResult(resultType, opts.result.Node.Name, - opts.responseDomain, opts.result.Tenancy, opts.port.Name) - a, e := getAnswerExtrasForIP(canonicalNodeName, nodeAddress, opts.req.Question[0], - reqType, opts.result, opts.ttl, opts.responseDomain, &opts.port, opts.dnsRecordMaker) - answer = append(answer, a...) - extra = append(extra, e...) - - // There is no service address and the node address is a FQDN (external service) - case serviceAddress.IsEmptyString(): - a, e := d.makeRecordFromFQDN(nodeAddress.FQDN(), opts) - answer = append(answer, a...) - extra = append(extra, e...) - - // The service address is an IP - case serviceAddress.IsIP(): - canonicalServiceName := canonicalNameForResult(discovery.ResultTypeService, - opts.result.Service.Name, opts.responseDomain, opts.result.Tenancy, opts.port.Name) - a, e := getAnswerExtrasForIP(canonicalServiceName, serviceAddress, - opts.req.Question[0], reqType, opts.result, opts.ttl, opts.responseDomain, &opts.port, opts.dnsRecordMaker) - answer = append(answer, a...) - extra = append(extra, e...) - - // If the service address is a CNAME for the service we are looking - // for then use the node address. - case serviceAddress.FQDN() == opts.req.Question[0].Name && nodeAddress.IsIP(): - canonicalNodeName := canonicalNameForResult(discovery.ResultTypeNode, - opts.result.Node.Name, opts.responseDomain, opts.result.Tenancy, opts.port.Name) - a, e := getAnswerExtrasForIP(canonicalNodeName, nodeAddress, opts.req.Question[0], - reqType, opts.result, opts.ttl, opts.responseDomain, &opts.port, opts.dnsRecordMaker) - answer = append(answer, a...) - extra = append(extra, e...) - - // The service address is a FQDN (internal or external service name) - default: - a, e := d.makeRecordFromFQDN(serviceAddress.FQDN(), opts) - answer = append(answer, a...) - extra = append(extra, e...) - } - - return -} - -// makeRecordFromFQDN creates a DNS record from a FQDN. -func (d messageSerializer) makeRecordFromFQDN(fqdn string, opts *getAnswerExtraAndNsOptions) ([]dns.RR, []dns.RR) { - edns := opts.req.IsEdns0() != nil - q := opts.req.Question[0] - - more := opts.resolveCnameFunc(opts.cfg, dns.Fqdn(fqdn), opts.reqCtx, opts.remoteAddress, opts.maxRecursionLevel) - var additional []dns.RR - extra := 0 -MORE_REC: - for _, rr := range more { - switch rr.Header().Rrtype { - case dns.TypeCNAME, dns.TypeA, dns.TypeAAAA, dns.TypeTXT: - // set the TTL manually - rr.Header().Ttl = opts.ttl - additional = append(additional, rr) - - extra++ - if extra == maxRecurseRecords && !edns { - break MORE_REC - } - } - } - - if q.Qtype == dns.TypeSRV { - answer := opts.dnsRecordMaker.makeSRV(q.Name, fqdn, uint16(opts.result.DNS.Weight), opts.ttl, &opts.port) - return []dns.RR{answer}, additional - } - - address := "" - if opts.result.Service != nil && opts.result.Service.Address != "" { - address = opts.result.Service.Address - } else if opts.result.Node != nil { - address = opts.result.Node.Address - } - - answers := []dns.RR{ - opts.dnsRecordMaker.makeCNAME(q.Name, address, opts.ttl), - } - answers = append(answers, additional...) - - return answers, nil -} - -// getAnswerAndExtraTXT determines whether a TXT needs to be create and then -// returns the TXT record in the answer or extra depending on the question type. -func getAnswerAndExtraTXT(req *dns.Msg, cfg *RouterDynamicConfig, qName string, - result *discovery.Result, ttl uint32, domain string, query *discovery.Query, - port *discovery.Port, maker dnsRecordMaker) (answer []dns.RR, extra []dns.RR) { - if !shouldAppendTXTRecord(query, cfg, req) { - return - } - recordHeaderName := qName - serviceAddress := newDNSAddress("") - if result.Service != nil { - serviceAddress = newDNSAddress(result.Service.Address) - } - if result.Type != discovery.ResultTypeNode && - result.Type != discovery.ResultTypeVirtual && - !serviceAddress.IsInternalFQDN(domain) && - !serviceAddress.IsExternalFQDN(domain) { - recordHeaderName = canonicalNameForResult(discovery.ResultTypeNode, result.Node.Name, - domain, result.Tenancy, port.Name) - } - qType := req.Question[0].Qtype - generateMeta := false - metaInAnswer := false - if qType == dns.TypeANY || qType == dns.TypeTXT { - generateMeta = true - metaInAnswer = true - } else if cfg.NodeMetaTXT { - generateMeta = true - } - - // Do not generate txt records if we don't have to: https://github.com/hashicorp/consul/pull/5272 - if generateMeta { - meta := maker.makeTXT(recordHeaderName, result.Metadata, ttl) - if metaInAnswer { - answer = append(answer, meta...) - } else { - extra = append(extra, meta...) - } - } - return answer, extra -} - -// shouldAppendTXTRecord determines whether a TXT record should be appended to the response. -func shouldAppendTXTRecord(query *discovery.Query, cfg *RouterDynamicConfig, req *dns.Msg) bool { - qType := req.Question[0].Qtype - switch { - // Node records - case query != nil && query.QueryType == discovery.QueryTypeNode && (cfg.NodeMetaTXT || qType == dns.TypeANY || qType == dns.TypeTXT): - return true - // Service records - case query != nil && query.QueryType == discovery.QueryTypeService && cfg.NodeMetaTXT && qType == dns.TypeSRV: - return true - // Prepared query records - case query != nil && query.QueryType == discovery.QueryTypePreparedQuery && cfg.NodeMetaTXT && qType == dns.TypeSRV: - return true - } - return false -} - -// getAnswerExtrasForIP creates the dns answer and extra from IP dnsAddress pairs. -func getAnswerExtrasForIP(name string, addr *dnsAddress, question dns.Question, - reqType requestType, result *discovery.Result, ttl uint32, domain string, - port *discovery.Port, maker dnsRecordMaker) (answer []dns.RR, extra []dns.RR) { - qType := question.Qtype - canReturnARecord := qType == dns.TypeSRV || qType == dns.TypeA || qType == dns.TypeANY || qType == dns.TypeNS || qType == dns.TypeTXT - canReturnAAAARecord := qType == dns.TypeSRV || qType == dns.TypeAAAA || qType == dns.TypeANY || qType == dns.TypeNS || qType == dns.TypeTXT - if reqType != requestTypeAddress && result.Type != discovery.ResultTypeVirtual { - switch { - // check IPV4 - case addr.IsIP() && addr.IsIPV4() && !canReturnARecord, - // check IPV6 - addr.IsIP() && !addr.IsIPV4() && !canReturnAAAARecord: - return - } - } - - // Have to pass original question name here even if the system has recursed - // and stripped off the domain suffix. - recHdrName := question.Name - if qType == dns.TypeSRV { - nameSplit := strings.Split(name, ".") - if len(nameSplit) > 1 && nameSplit[1] == addrLabel { - recHdrName = name - } else { - recHdrName = name - } - name = question.Name - } - - if reqType != requestTypeAddress && qType == dns.TypeSRV { - if result.Type == discovery.ResultTypeService && addr.IsIP() && result.Node.Address != addr.String() { - // encode the ip to be used in the header of the A/AAAA record - // as well as the target of the SRV record. - recHdrName = encodeIPAsFqdn(result, addr.IP(), domain) - } - if result.Type == discovery.ResultTypeWorkload { - recHdrName = canonicalNameForResult(result.Type, result.Node.Name, domain, result.Tenancy, port.Name) - } - srv := maker.makeSRV(name, recHdrName, uint16(result.DNS.Weight), ttl, port) - answer = append(answer, srv) - } - - record := maker.makeIPBasedRecord(recHdrName, addr, ttl) - - isARecordWhenNotExplicitlyQueried := record.Header().Rrtype == dns.TypeA && qType != dns.TypeA && qType != dns.TypeANY - isAAAARecordWhenNotExplicitlyQueried := record.Header().Rrtype == dns.TypeAAAA && qType != dns.TypeAAAA && qType != dns.TypeANY - - // For explicit A/AAAA queries, we must only return those records in the answer section. - if isARecordWhenNotExplicitlyQueried || - isAAAARecordWhenNotExplicitlyQueried { - extra = append(extra, record) - } else { - answer = append(answer, record) - } - - return -} - -// getPortsFromResult returns the ports from a discovery result. -func getPortsFromResult(result *discovery.Result) []discovery.Port { - if len(result.Ports) > 0 { - return result.Ports - } - // return one record. - return []discovery.Port{{}} -} - -// encodeIPAsFqdn encodes an IP address as a FQDN. -func encodeIPAsFqdn(result *discovery.Result, ip net.IP, responseDomain string) string { - ipv4 := ip.To4() - ipStr := hex.EncodeToString(ip) - if ipv4 != nil { - ipStr = ipStr[len(ipStr)-(net.IPv4len*2):] - } - if result.Tenancy.PeerName != "" { - // Exclude the datacenter from the FQDN on the addr for peers. - // This technically makes no difference, since the addr endpoint ignores the DC - // component of the request, but do it anyway for a less confusing experience. - return fmt.Sprintf("%s.addr.%s", ipStr, responseDomain) - } - return fmt.Sprintf("%s.addr.%s.%s", ipStr, result.Tenancy.Datacenter, responseDomain) -} - -// canonicalNameForResult returns the canonical name for a discovery result. -func canonicalNameForResult(resultType discovery.ResultType, target, domain string, - tenancy discovery.ResultTenancy, portName string) string { - switch resultType { - case discovery.ResultTypeService: - if tenancy.Namespace != "" { - return fmt.Sprintf("%s.%s.%s.%s.%s", target, "service", tenancy.Namespace, tenancy.Datacenter, domain) - } - return fmt.Sprintf("%s.%s.%s.%s", target, "service", tenancy.Datacenter, domain) - case discovery.ResultTypeNode: - if tenancy.PeerName != "" && tenancy.Partition != "" { - // We must return a more-specific DNS name for peering so - // that there is no ambiguity with lookups. - // Nodes are always registered in the default namespace, so - // the `.ns` qualifier is not required. - return fmt.Sprintf("%s.node.%s.peer.%s.ap.%s", - target, - tenancy.PeerName, - tenancy.Partition, - domain) - } - if tenancy.PeerName != "" { - // We must return a more-specific DNS name for peering so - // that there is no ambiguity with lookups. - return fmt.Sprintf("%s.node.%s.peer.%s", - target, - tenancy.PeerName, - domain) - } - // Return a simpler format for non-peering nodes. - return fmt.Sprintf("%s.node.%s.%s", target, tenancy.Datacenter, domain) - case discovery.ResultTypeWorkload: - // TODO (v2-dns): it doesn't appear this is being used to return a result. Need to investigate and refactor - if portName != "" { - return fmt.Sprintf("%s.port.%s.workload.%s.ns.%s.ap.%s", portName, target, tenancy.Namespace, tenancy.Partition, domain) - } - return fmt.Sprintf("%s.workload.%s.ns.%s.ap.%s", target, tenancy.Namespace, tenancy.Partition, domain) - } - return "" -} - -// getServiceAddressMapFromLocationMap converts a map of Location to a map of ServiceAddress. -func getServiceAddressMapFromLocationMap(taggedAddresses map[string]*discovery.TaggedAddress) map[string]structs.ServiceAddress { - taggedServiceAddresses := make(map[string]structs.ServiceAddress, len(taggedAddresses)) - for k, v := range taggedAddresses { - taggedServiceAddresses[k] = structs.ServiceAddress{ - Address: v.Address, - Port: int(v.Port.Number), - } - } - return taggedServiceAddresses -} - -// getStringAddressMapFromTaggedAddressMap converts a map of Location to a map of string. -func getStringAddressMapFromTaggedAddressMap(taggedAddresses map[string]*discovery.TaggedAddress) map[string]string { - taggedServiceAddresses := make(map[string]string, len(taggedAddresses)) - for k, v := range taggedAddresses { - taggedServiceAddresses[k] = v.Address - } - return taggedServiceAddresses -} - -// getTTLForResult returns the TTL for a given result. -func getTTLForResult(name string, overrideTTL *uint32, query *discovery.Query, cfg *RouterDynamicConfig) uint32 { - // In the case we are not making a discovery query, such as addr. or arpa. lookups, - // use the node TTL by convention - if query == nil { - return uint32(cfg.NodeTTL / time.Second) - } - - if overrideTTL != nil { - // If a result was provided with an override, use that. This is the case for some prepared queries. - return *overrideTTL - } - - switch query.QueryType { - case discovery.QueryTypeService, discovery.QueryTypePreparedQuery: - ttl, ok := cfg.GetTTLForService(name) - if ok { - return uint32(ttl / time.Second) - } - fallthrough - default: - return uint32(cfg.NodeTTL / time.Second) - } -} - -// serializeToGetAnswerExtraAndNsOptions converts serializeOptions to getAnswerExtraAndNsOptions. -func serializeToGetAnswerExtraAndNsOptions(opts *serializeOptions, - result *discovery.Result, port discovery.Port) *getAnswerExtraAndNsOptions { - return &getAnswerExtraAndNsOptions{ - port: port, - result: result, - req: opts.req, - reqCtx: opts.reqCtx, - query: opts.query, - results: opts.results, - resp: opts.resp, - cfg: opts.cfg, - responseDomain: opts.responseDomain, - remoteAddress: opts.remoteAddress, - maxRecursionLevel: opts.maxRecursionLevel, - translateAddressFunc: opts.translateAddressFunc, - translateServiceAddressFunc: opts.translateServiceAddressFunc, - resolveCnameFunc: opts.resolveCnameFunc, - dnsRecordMaker: opts.dnsRecordMaker, - } -} diff --git a/agent/dns/mock_DNSRouter.go b/agent/dns/mock_DNSRouter.go deleted file mode 100644 index 9e90de771e..0000000000 --- a/agent/dns/mock_DNSRouter.go +++ /dev/null @@ -1,82 +0,0 @@ -// Code generated by mockery v2.32.4. DO NOT EDIT. - -package dns - -import ( - config "github.com/hashicorp/consul/agent/config" - miekgdns "github.com/miekg/dns" - - mock "github.com/stretchr/testify/mock" - - net "net" -) - -// MockDNSRouter is an autogenerated mock type for the DNSRouter type -type MockDNSRouter struct { - mock.Mock -} - -// GetConfig provides a mock function with given fields: -func (_m *MockDNSRouter) GetConfig() *RouterDynamicConfig { - ret := _m.Called() - - var r0 *RouterDynamicConfig - if rf, ok := ret.Get(0).(func() *RouterDynamicConfig); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*RouterDynamicConfig) - } - } - - return r0 -} - -// HandleRequest provides a mock function with given fields: req, reqCtx, remoteAddress -func (_m *MockDNSRouter) HandleRequest(req *miekgdns.Msg, reqCtx Context, remoteAddress net.Addr) *miekgdns.Msg { - ret := _m.Called(req, reqCtx, remoteAddress) - - var r0 *miekgdns.Msg - if rf, ok := ret.Get(0).(func(*miekgdns.Msg, Context, net.Addr) *miekgdns.Msg); ok { - r0 = rf(req, reqCtx, remoteAddress) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*miekgdns.Msg) - } - } - - return r0 -} - -// ReloadConfig provides a mock function with given fields: newCfg -func (_m *MockDNSRouter) ReloadConfig(newCfg *config.RuntimeConfig) error { - ret := _m.Called(newCfg) - - var r0 error - if rf, ok := ret.Get(0).(func(*config.RuntimeConfig) error); ok { - r0 = rf(newCfg) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// ServeDNS provides a mock function with given fields: w, req -func (_m *MockDNSRouter) ServeDNS(w miekgdns.ResponseWriter, req *miekgdns.Msg) { - _m.Called(w, req) -} - -// NewMockDNSRouter creates a new instance of MockDNSRouter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func NewMockDNSRouter(t interface { - mock.TestingT - Cleanup(func()) -}) *MockDNSRouter { - mock := &MockDNSRouter{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/agent/dns/mock_dnsRecursor.go b/agent/dns/mock_dnsRecursor.go deleted file mode 100644 index b590661da1..0000000000 --- a/agent/dns/mock_dnsRecursor.go +++ /dev/null @@ -1,55 +0,0 @@ -// Code generated by mockery v2.32.4. DO NOT EDIT. - -package dns - -import ( - miekgdns "github.com/miekg/dns" - mock "github.com/stretchr/testify/mock" - - net "net" -) - -// mockDnsRecursor is an autogenerated mock type for the dnsRecursor type -type mockDnsRecursor struct { - mock.Mock -} - -// handle provides a mock function with given fields: req, cfgCtx, remoteAddr -func (_m *mockDnsRecursor) handle(req *miekgdns.Msg, cfgCtx *RouterDynamicConfig, remoteAddr net.Addr) (*miekgdns.Msg, error) { - ret := _m.Called(req, cfgCtx, remoteAddr) - - var r0 *miekgdns.Msg - var r1 error - if rf, ok := ret.Get(0).(func(*miekgdns.Msg, *RouterDynamicConfig, net.Addr) (*miekgdns.Msg, error)); ok { - return rf(req, cfgCtx, remoteAddr) - } - if rf, ok := ret.Get(0).(func(*miekgdns.Msg, *RouterDynamicConfig, net.Addr) *miekgdns.Msg); ok { - r0 = rf(req, cfgCtx, remoteAddr) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*miekgdns.Msg) - } - } - - if rf, ok := ret.Get(1).(func(*miekgdns.Msg, *RouterDynamicConfig, net.Addr) error); ok { - r1 = rf(req, cfgCtx, remoteAddr) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// newMockDnsRecursor creates a new instance of mockDnsRecursor. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. -// The first argument is typically a *testing.T value. -func newMockDnsRecursor(t interface { - mock.TestingT - Cleanup(func()) -}) *mockDnsRecursor { - mock := &mockDnsRecursor{} - mock.Mock.Test(t) - - t.Cleanup(func() { mock.AssertExpectations(t) }) - - return mock -} diff --git a/agent/dns/parser.go b/agent/dns/parser.go deleted file mode 100644 index e39f91e0f9..0000000000 --- a/agent/dns/parser.go +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -// parsedLabels defines valid DNS labels that are possible for ALL DNS query in Consul. (v1 and v2, CE and ENT) -// It is the job of the parser to populate the struct, the routers to call the query processor, -// and the query processor to validate is the labels. -type parsedLabels struct { - Datacenter string - Namespace string - Partition string - Peer string - PeerOrDatacenter string // deprecated: use Datacenter or Peer - SamenessGroup string -} - -// ParseLabels can parse a DNS query's labels and returns a parsedLabels. -// It also does light validation according to invariants across all possible DNS queries for all Consul versions -func parseLabels(labels []string) (*parsedLabels, bool) { - var result parsedLabels - - switch len(labels) { - case 2, 4, 6: - // Supports the following formats: - // - [..ns][..ap][..dc] - // - . - // - [..ns][..ap][..peer] - // - [..sg][..ap][..ns] - for i := 0; i < len(labels); i += 2 { - switch labels[i+1] { - case "ns": - result.Namespace = labels[i] - case "ap": - result.Partition = labels[i] - case "dc", "cluster": - result.Datacenter = labels[i] - case "sg": - result.SamenessGroup = labels[i] - case "peer": - result.Peer = labels[i] - default: - // The only case in which labels[i+1] is allowed to be a value - // other than ns, ap, or dc is if n == 2 to support the format: - // .. - if len(labels) == 2 { - result.PeerOrDatacenter = labels[1] - result.Namespace = labels[0] - return &result, true - } - return nil, false - } - } - - // VALIDATIONS - // Return nil result and false boolean when both datacenter and peer are specified. - if result.Datacenter != "" && result.Peer != "" { - return nil, false - } - - // Validate that this a valid DNS including sg - if result.SamenessGroup != "" && (result.Datacenter != "" || result.Peer != "") { - return nil, false - } - - return &result, true - - case 1: - result.PeerOrDatacenter = labels[0] - return &result, true - - case 0: - return &result, true - } - - return &result, false -} - -// parsePort looks through the query parts for a named port label. -// It assumes the only valid input format is["", "port", ""]. -// The other expected formats are [""] and ["", ""]. -// It is expected that the queryProcessor validates if the label is allowed for the query type. -func parsePort(parts []string) string { - // The minimum number of parts would be - if len(parts) != 3 || parts[1] != "port" { - return "" - } - return parts[0] -} diff --git a/agent/dns/parser_test.go b/agent/dns/parser_test.go deleted file mode 100644 index cd5beb117a..0000000000 --- a/agent/dns/parser_test.go +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "github.com/stretchr/testify/require" - "testing" -) - -func Test_parseLabels(t *testing.T) { - type testCase struct { - name string - labels []string - expectedOK bool - expectedResult *parsedLabels - } - testCases := []testCase{ - { - name: "6 labels - with datacenter", - labels: []string{"test-ns", "ns", "test-ap", "ap", "test-dc", "dc"}, - expectedResult: &parsedLabels{ - Namespace: "test-ns", - Partition: "test-ap", - Datacenter: "test-dc", - }, - expectedOK: true, - }, - { - name: "6 labels - with cluster", - labels: []string{"test-ns", "ns", "test-ap", "ap", "test-cluster", "cluster"}, - expectedResult: &parsedLabels{ - Namespace: "test-ns", - Partition: "test-ap", - Datacenter: "test-cluster", - }, - expectedOK: true, - }, - { - name: "6 labels - with peer", - labels: []string{"test-ns", "ns", "test-ap", "ap", "test-peer", "peer"}, - expectedResult: &parsedLabels{ - Namespace: "test-ns", - Partition: "test-ap", - Peer: "test-peer", - }, - expectedOK: true, - }, - { - name: "6 labels - with sameness group", - labels: []string{"test-sg", "sg", "test-ap", "ap", "test-ns", "ns"}, - expectedResult: &parsedLabels{ - Namespace: "test-ns", - Partition: "test-ap", - SamenessGroup: "test-sg", - }, - expectedOK: true, - }, - { - name: "6 labels - invalid", - labels: []string{"test-ns", "not-ns", "test-ap", "ap", "test-dc", "dc"}, - expectedResult: nil, - expectedOK: false, - }, - { - name: "4 labels - namespace and datacenter", - labels: []string{"test-ns", "ns", "test-ap", "ap"}, - expectedResult: &parsedLabels{ - Namespace: "test-ns", - Partition: "test-ap", - }, - expectedOK: true, - }, - { - name: "4 labels - invalid", - labels: []string{"test-ns", "not-ns", "test-ap", "ap", "test-dc", "dc"}, - expectedResult: nil, - expectedOK: false, - }, - { - name: "2 labels - namespace and peer or datacenter", - labels: []string{"test-ns", "test-peer-or-dc"}, - expectedResult: &parsedLabels{ - Namespace: "test-ns", - PeerOrDatacenter: "test-peer-or-dc", - }, - expectedOK: true, - }, - { - name: "1 label - peer or datacenter", - labels: []string{"test-peer-or-dc"}, - expectedResult: &parsedLabels{ - PeerOrDatacenter: "test-peer-or-dc", - }, - expectedOK: true, - }, - { - name: "0 labels - returns empty result and true", - labels: []string{}, - expectedResult: &parsedLabels{}, - expectedOK: true, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - result, ok := parseLabels(tc.labels) - require.Equal(t, tc.expectedOK, ok) - require.Equal(t, tc.expectedResult, result) - }) - } -} - -func Test_parsePort(t *testing.T) { - type testCase struct { - name string - labels []string - expectedResult string - } - testCases := []testCase{ - { - name: "given 3 labels where the second label is port, the first label is returned", - labels: []string{"port-name", "port", "target-name"}, - expectedResult: "port-name", - }, - { - name: "given 3 labels where the second label is not port, an empty string is returned", - labels: []string{"port-name", "not-port", "target-name"}, - expectedResult: "", - }, - { - name: "given anything but 3 labels, an empty string is returned", - labels: []string{"port-name", "something-else"}, - expectedResult: "", - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - require.Equal(t, tc.expectedResult, parsePort(tc.labels)) - }) - } -} diff --git a/agent/dns/recursor.go b/agent/dns/recursor.go deleted file mode 100644 index 21ea94a6c8..0000000000 --- a/agent/dns/recursor.go +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "errors" - "net" - "time" - - "github.com/hashicorp/go-hclog" - "github.com/miekg/dns" - - "github.com/hashicorp/consul/ipaddr" - "github.com/hashicorp/consul/logging" -) - -type recursor struct { - logger hclog.Logger -} - -func newRecursor(logger hclog.Logger) *recursor { - return &recursor{ - logger: logger.Named(logging.DNS), - } -} - -// handle is used to process DNS queries for externally configured servers -func (r *recursor) handle(req *dns.Msg, cfgCtx *RouterDynamicConfig, remoteAddr net.Addr) (*dns.Msg, error) { - q := req.Question[0] - - network := "udp" - defer func(s time.Time) { - r.logger.Trace("request served from client", - "question", q, - "network", network, - "latency", time.Since(s).String(), - "client", remoteAddr.String(), - "client_network", remoteAddr.Network(), - ) - }(time.Now()) - - // Switch to TCP if the client is - if _, ok := remoteAddr.(*net.TCPAddr); ok { - network = "tcp" - } - - // Recursively resolve - c := &dns.Client{Net: network, Timeout: cfgCtx.RecursorTimeout} - var resp *dns.Msg - var rtt time.Duration - var err error - for _, idx := range cfgCtx.RecursorStrategy.Indexes(len(cfgCtx.Recursors)) { - recurseAddr := cfgCtx.Recursors[idx] - resp, rtt, err = c.Exchange(req, recurseAddr) - // Check if the response is valid and has the desired Response code - if resp != nil && (resp.Rcode != dns.RcodeSuccess && resp.Rcode != dns.RcodeNameError) { - r.logger.Trace("recurse failed for question", - "question", q, - "rtt", rtt, - "recursor", recurseAddr, - "rcode", dns.RcodeToString[resp.Rcode], - ) - // If we still have recursors to forward the query to, - // we move forward onto the next one else the loop ends - continue - } else if err == nil || (resp != nil && resp.Truncated) { - // Compress the response; we don't know if the incoming - // response was compressed or not, so by not compressing - // we might generate an invalid packet on the way out. - resp.Compress = !cfgCtx.DisableCompression - - // Forward the response - r.logger.Trace("recurse succeeded for question", - "question", q, - "rtt", rtt, - "recursor", recurseAddr, - ) - return resp, nil - } - r.logger.Error("recurse failed", "error", err) - } - - // If all resolvers fail, return a SERVFAIL message - r.logger.Error("all resolvers failed for question from client", - "question", q, - "client", remoteAddr.String(), - "client_network", remoteAddr.Network(), - ) - - return nil, errRecursionFailed -} - -// formatRecursorAddress is used to add a port to the recursor if omitted. -func formatRecursorAddress(recursor string) (string, error) { - _, _, err := net.SplitHostPort(recursor) - var ae *net.AddrError - if errors.As(err, &ae) { - switch ae.Err { - case "missing port in address": - recursor = ipaddr.FormatAddressPort(recursor, 53) - case "too many colons in address": - if ip := net.ParseIP(recursor); ip != nil && ip.To4() == nil { - recursor = ipaddr.FormatAddressPort(recursor, 53) - break - } - fallthrough - default: - return "", err - } - } else if err != nil { - return "", err - } - - // Get the address - addr, err := net.ResolveTCPAddr("tcp", recursor) - if err != nil { - return "", err - } - - // Return string - return addr.String(), nil -} diff --git a/agent/dns/recursor_test.go b/agent/dns/recursor_test.go deleted file mode 100644 index 69514e508e..0000000000 --- a/agent/dns/recursor_test.go +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "strings" - "testing" -) - -// Test_handle cases are covered by the integration tests in agent/dns_test.go. -// They should be moved here when the V1 DNS server is deprecated. -//func Test_handle(t *testing.T) { - -func Test_formatRecursorAddress(t *testing.T) { - t.Parallel() - addr, err := formatRecursorAddress("8.8.8.8") - if err != nil { - t.Fatalf("err: %v", err) - } - if addr != "8.8.8.8:53" { - t.Fatalf("bad: %v", addr) - } - addr, err = formatRecursorAddress("2001:4860:4860::8888") - if err != nil { - t.Fatalf("err: %v", err) - } - if addr != "[2001:4860:4860::8888]:53" { - t.Fatalf("bad: %v", addr) - } - _, err = formatRecursorAddress("1.2.3.4::53") - if err == nil || !strings.Contains(err.Error(), "too many colons in address") { - t.Fatalf("err: %v", err) - } - _, err = formatRecursorAddress("2001:4860:4860::8888:::53") - if err == nil || !strings.Contains(err.Error(), "too many colons in address") { - t.Fatalf("err: %v", err) - } -} diff --git a/agent/dns/response_generator.go b/agent/dns/response_generator.go deleted file mode 100644 index ad7b14270d..0000000000 --- a/agent/dns/response_generator.go +++ /dev/null @@ -1,425 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "errors" - "fmt" - "math" - "net" - "strings" - - "github.com/miekg/dns" - - "github.com/hashicorp/consul/agent/discovery" - "github.com/hashicorp/consul/lib" - "github.com/hashicorp/go-hclog" -) - -const ( - // UDP can fit ~25 A records in a 512B response, and ~14 AAAA - // records. Limit further to prevent unintentional configuration - // abuse that would have a negative effect on application response - // times. - maxUDPAnswerLimit = 8 - - defaultMaxUDPSize = 512 - - // If a consumer sets a buffer size greater than this amount we will default it down - // to this amount to ensure that consul does respond. Previously if consumer had a larger buffer - // size than 65535 - 60 bytes (maximim 60 bytes for IP header. UDP header will be offset in the - // trimUDP call) consul would fail to respond and the consumer timesout - // the request. - maxUDPDatagramSize = math.MaxUint16 - 68 -) - -// dnsResponseGenerator is used to: -// - generate DNS responses for errors -// - trim and truncate DNS responses -// - EDNS to the response -type dnsResponseGenerator struct{} - -// createRefusedResponse returns a REFUSED message. This is the default behavior for unmatched queries in -// upstream miekg/dns. -func (d dnsResponseGenerator) createRefusedResponse(req *dns.Msg) *dns.Msg { - // Return a REFUSED message - m := &dns.Msg{} - m.SetRcode(req, dns.RcodeRefused) - return m -} - -// createServerFailureResponse returns a SERVFAIL message. -func (d dnsResponseGenerator) createServerFailureResponse(req *dns.Msg, cfg *RouterDynamicConfig, recursionAvailable bool) *dns.Msg { - // Return a SERVFAIL message - m := &dns.Msg{} - m.SetReply(req) - m.Compress = !cfg.DisableCompression - m.SetRcode(req, dns.RcodeServerFailure) - m.RecursionAvailable = recursionAvailable - if edns := req.IsEdns0(); edns != nil { - d.setEDNS(req, m, true) - } - - return m -} - -// createAuthoritativeResponse returns an authoritative message that contains the SOA in the event that data is -// not return for a query. There can be multiple reasons for not returning data, hence the rcode argument. -func (d dnsResponseGenerator) createAuthoritativeResponse(req *dns.Msg, cfg *RouterDynamicConfig, domain string, rcode int, ecsGlobal bool) *dns.Msg { - m := &dns.Msg{} - m.SetRcode(req, rcode) - m.Compress = !cfg.DisableCompression - m.Authoritative = true - m.RecursionAvailable = canRecurse(cfg) - if edns := req.IsEdns0(); edns != nil { - d.setEDNS(req, m, ecsGlobal) - } - - // We add the SOA on NameErrors - maker := &dnsRecordMaker{} - soa := maker.makeSOA(domain, cfg) - m.Ns = append(m.Ns, soa) - - return m -} - -// generateResponseFromErrorOpts is used to pass options to generateResponseFromError. -type generateResponseFromErrorOpts struct { - req *dns.Msg - err error - qName string - configCtx *RouterDynamicConfig - responseDomain string - isECSGlobal bool - query *discovery.Query - canRecurse bool - logger hclog.Logger -} - -// generateResponseFromError generates a response from an error. -func (d dnsResponseGenerator) generateResponseFromError(opts *generateResponseFromErrorOpts) *dns.Msg { - switch { - case errors.Is(opts.err, errInvalidQuestion): - opts.logger.Error("invalid question", "name", opts.qName) - - return d.createAuthoritativeResponse(opts.req, opts.configCtx, opts.responseDomain, dns.RcodeNameError, opts.isECSGlobal) - case errors.Is(opts.err, errNameNotFound): - opts.logger.Error("name not found", "name", opts.qName) - - return d.createAuthoritativeResponse(opts.req, opts.configCtx, opts.responseDomain, dns.RcodeNameError, opts.isECSGlobal) - case errors.Is(opts.err, errNotImplemented): - opts.logger.Error("query not implemented", "name", opts.qName, "type", dns.Type(opts.req.Question[0].Qtype).String()) - - return d.createAuthoritativeResponse(opts.req, opts.configCtx, opts.responseDomain, dns.RcodeNotImplemented, opts.isECSGlobal) - case errors.Is(opts.err, discovery.ErrNotSupported): - opts.logger.Debug("query name syntax not supported", "name", opts.req.Question[0].Name) - - return d.createAuthoritativeResponse(opts.req, opts.configCtx, opts.responseDomain, dns.RcodeNameError, opts.isECSGlobal) - case errors.Is(opts.err, discovery.ErrNotFound): - opts.logger.Debug("query name not found", "name", opts.req.Question[0].Name) - - return d.createAuthoritativeResponse(opts.req, opts.configCtx, opts.responseDomain, dns.RcodeNameError, opts.isECSGlobal) - case errors.Is(opts.err, discovery.ErrNoData): - opts.logger.Debug("no data available", "name", opts.qName) - - return d.createAuthoritativeResponse(opts.req, opts.configCtx, opts.responseDomain, dns.RcodeSuccess, opts.isECSGlobal) - case errors.Is(opts.err, discovery.ErrNoPathToDatacenter): - dc := "" - if opts.query != nil { - dc = opts.query.QueryPayload.Tenancy.Datacenter - } - opts.logger.Debug("no path to datacenter", "datacenter", dc) - return d.createAuthoritativeResponse(opts.req, opts.configCtx, opts.responseDomain, dns.RcodeNameError, opts.isECSGlobal) - } - opts.logger.Error("error processing discovery query", "error", opts.err) - return d.createServerFailureResponse(opts.req, opts.configCtx, opts.canRecurse) -} - -// trimDNSResponse will trim the response for UDP and TCP -func (d dnsResponseGenerator) trimDNSResponse(cfg *RouterDynamicConfig, remoteAddress net.Addr, req, resp *dns.Msg, logger hclog.Logger) { - // Switch to TCP if the client is - network := "udp" - if _, ok := remoteAddress.(*net.TCPAddr); ok { - network = "tcp" - } - - var trimmed bool - originalSize := resp.Len() - originalNumRecords := len(resp.Answer) - if network != "tcp" { - trimmed = trimUDPResponse(req, resp, cfg.UDPAnswerLimit) - } else { - trimmed = trimTCPResponse(req, resp) - } - // Flag that there are more records to return in the UDP response - if trimmed { - if cfg.EnableTruncate { - resp.Truncated = true - } - logger.Debug("DNS response too large, truncated", - "protocol", network, - "question", req.Question, - "records", fmt.Sprintf("%d/%d", len(resp.Answer), originalNumRecords), - "size", fmt.Sprintf("%d/%d", resp.Len(), originalSize), - ) - } -} - -// setEDNS is used to set the responses EDNS size headers and -// possibly the ECS headers as well if they were present in the -// original request -func (d dnsResponseGenerator) setEDNS(request *dns.Msg, response *dns.Msg, ecsGlobal bool) { - edns := request.IsEdns0() - if edns == nil { - return - } - - // cannot just use the SetEdns0 function as we need to embed - // the ECS option as well - ednsResp := new(dns.OPT) - ednsResp.Hdr.Name = "." - ednsResp.Hdr.Rrtype = dns.TypeOPT - ednsResp.SetUDPSize(edns.UDPSize()) - - // Set up the ECS option if present - if subnet := ednsSubnetForRequest(request); subnet != nil { - subOp := new(dns.EDNS0_SUBNET) - subOp.Code = dns.EDNS0SUBNET - subOp.Family = subnet.Family - subOp.Address = subnet.Address - subOp.SourceNetmask = subnet.SourceNetmask - if c := response.Rcode; ecsGlobal || c == dns.RcodeNameError || c == dns.RcodeServerFailure || c == dns.RcodeRefused || c == dns.RcodeNotImplemented { - // reply is globally valid and should be cached accordingly - subOp.SourceScope = 0 - } else { - // reply is only valid for the subnet it was queried with - subOp.SourceScope = subnet.SourceNetmask - } - ednsResp.Option = append(ednsResp.Option, subOp) - } - - response.Extra = append(response.Extra, ednsResp) -} - -// ednsSubnetForRequest looks through the request to find any EDS subnet options -func ednsSubnetForRequest(req *dns.Msg) *dns.EDNS0_SUBNET { - // IsEdns0 returns the EDNS RR if present or nil otherwise - edns := req.IsEdns0() - if edns == nil { - return nil - } - - for _, o := range edns.Option { - if subnet, ok := o.(*dns.EDNS0_SUBNET); ok { - return subnet - } - } - return nil -} - -// trimTCPResponse limit the MaximumSize of messages to 64k as it is the limit -// of DNS responses -func trimTCPResponse(req, resp *dns.Msg) (trimmed bool) { - hasExtra := len(resp.Extra) > 0 - // There is some overhead, 65535 does not work - maxSize := 65523 // 64k - 12 bytes DNS raw overhead - - // We avoid some function calls and allocations by only handling the - // extra data when necessary. - var index map[string]dns.RR - - // It is not possible to return more than 4k records even with compression - // Since we are performing binary search it is not a big deal, but it - // improves a bit performance, even with binary search - truncateAt := 4096 - if req.Question[0].Qtype == dns.TypeSRV { - // More than 1024 SRV records do not fit in 64k - truncateAt = 1024 - } - if len(resp.Answer) > truncateAt { - resp.Answer = resp.Answer[:truncateAt] - } - if hasExtra { - index = make(map[string]dns.RR, len(resp.Extra)) - indexRRs(resp.Extra, index) - } - truncated := false - - // This enforces the given limit on 64k, the max limit for DNS messages - for len(resp.Answer) > 1 && resp.Len() > maxSize { - truncated = true - // first try to remove the NS section may be it will truncate enough - if len(resp.Ns) != 0 { - resp.Ns = []dns.RR{} - } - // More than 100 bytes, find with a binary search - if resp.Len()-maxSize > 100 { - bestIndex := dnsBinaryTruncate(resp, maxSize, index, hasExtra) - resp.Answer = resp.Answer[:bestIndex] - } else { - resp.Answer = resp.Answer[:len(resp.Answer)-1] - } - if hasExtra { - syncExtra(index, resp) - } - } - - return truncated -} - -// trimUDPResponse makes sure a UDP response is not longer than allowed by RFC -// 1035. Enforce an arbitrary limit that can be further ratcheted down by -// config, and then make sure the response doesn't exceed 512 bytes. Any extra -// records will be trimmed along with answers. -func trimUDPResponse(req, resp *dns.Msg, udpAnswerLimit int) (trimmed bool) { - numAnswers := len(resp.Answer) - hasExtra := len(resp.Extra) > 0 - maxSize := defaultMaxUDPSize - - // Update to the maximum edns size - if edns := req.IsEdns0(); edns != nil { - if size := edns.UDPSize(); size > uint16(maxSize) { - maxSize = int(size) - } - } - // Overriding maxSize as the maxSize cannot be larger than the - // maxUDPDatagram size. Reliability guarantees disappear > than this amount. - if maxSize > maxUDPDatagramSize { - maxSize = maxUDPDatagramSize - } - - // We avoid some function calls and allocations by only handling the - // extra data when necessary. - var index map[string]dns.RR - if hasExtra { - index = make(map[string]dns.RR, len(resp.Extra)) - indexRRs(resp.Extra, index) - } - - // This cuts UDP responses to a useful but limited number of responses. - maxAnswers := lib.MinInt(maxUDPAnswerLimit, udpAnswerLimit) - compress := resp.Compress - if maxSize == defaultMaxUDPSize && numAnswers > maxAnswers { - // We disable computation of Len ONLY for non-eDNS request (512 bytes) - resp.Compress = false - resp.Answer = resp.Answer[:maxAnswers] - if hasExtra { - syncExtra(index, resp) - } - } - if maxSize == defaultMaxUDPSize && numAnswers > maxAnswers { - // We disable computation of Len ONLY for non-eDNS request (512 bytes) - resp.Compress = false - resp.Answer = resp.Answer[:maxAnswers] - if hasExtra { - syncExtra(index, resp) - } - } - - // This enforces the given limit on the number bytes. The default is 512 as - // per the RFC, but EDNS0 allows for the user to specify larger sizes. Note - // that we temporarily switch to uncompressed so that we limit to a response - // that will not exceed 512 bytes uncompressed, which is more conservative and - // will allow our responses to be compliant even if some downstream server - // uncompresses them. - // Even when size is too big for one single record, try to send it anyway - // (useful for 512 bytes messages). 8 is removed from maxSize to ensure that we account - // for the udp header (8 bytes). - for len(resp.Answer) > 1 && resp.Len() > maxSize-8 { - // first try to remove the NS section may be it will truncate enough - if len(resp.Ns) != 0 { - resp.Ns = []dns.RR{} - } - // More than 100 bytes, find with a binary search - if resp.Len()-maxSize > 100 { - bestIndex := dnsBinaryTruncate(resp, maxSize, index, hasExtra) - resp.Answer = resp.Answer[:bestIndex] - } else { - resp.Answer = resp.Answer[:len(resp.Answer)-1] - } - if hasExtra { - syncExtra(index, resp) - } - } - // For 512 non-eDNS responses, while we compute size non-compressed, - // we send result compressed - resp.Compress = compress - return len(resp.Answer) < numAnswers -} - -// syncExtra takes a DNS response message and sets the extra data to the most -// minimal set needed to cover the answer data. A pre-made index of RRs is given -// so that can be re-used between calls. This assumes that the extra data is -// only used to provide info for SRV records. If that's not the case, then this -// will wipe out any additional data. -func syncExtra(index map[string]dns.RR, resp *dns.Msg) { - extra := make([]dns.RR, 0, len(resp.Answer)) - resolved := make(map[string]struct{}, len(resp.Answer)) - for _, ansRR := range resp.Answer { - srv, ok := ansRR.(*dns.SRV) - if !ok { - continue - } - - // Note that we always use lower case when using the index so - // that compares are not case-sensitive. We don't alter the actual - // RRs we add into the extra section, however. - target := strings.ToLower(srv.Target) - - RESOLVE: - if _, ok := resolved[target]; ok { - continue - } - resolved[target] = struct{}{} - - extraRR, ok := index[target] - if ok { - extra = append(extra, extraRR) - if cname, ok := extraRR.(*dns.CNAME); ok { - target = strings.ToLower(cname.Target) - goto RESOLVE - } - } - } - resp.Extra = extra -} - -// dnsBinaryTruncate find the optimal number of records using a fast binary search and return -// it in order to return a DNS answer lower than maxSize parameter. -func dnsBinaryTruncate(resp *dns.Msg, maxSize int, index map[string]dns.RR, hasExtra bool) int { - originalAnswser := resp.Answer - startIndex := 0 - endIndex := len(resp.Answer) + 1 - for endIndex-startIndex > 1 { - median := startIndex + (endIndex-startIndex)/2 - - resp.Answer = originalAnswser[:median] - if hasExtra { - syncExtra(index, resp) - } - aLen := resp.Len() - if aLen <= maxSize { - if maxSize-aLen < 10 { - // We are good, increasing will go out of bounds - return median - } - startIndex = median - } else { - endIndex = median - } - } - return startIndex -} - -// indexRRs populates a map which indexes a given list of RRs by name. NOTE that -// the names are all squashed to lower case so we can perform case-insensitive -// lookups; the RRs are not modified. -func indexRRs(rrs []dns.RR, index map[string]dns.RR) { - for _, rr := range rrs { - name := strings.ToLower(rr.Header().Name) - if _, ok := index[name]; !ok { - index[name] = rr - } - } -} diff --git a/agent/dns/response_generator_test.go b/agent/dns/response_generator_test.go deleted file mode 100644 index ec9849307e..0000000000 --- a/agent/dns/response_generator_test.go +++ /dev/null @@ -1,739 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "errors" - "net" - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/discovery" - "github.com/hashicorp/consul/sdk/testutil" -) - -func TestDNSResponseGenerator_generateResponseFromError(t *testing.T) { - testCases := []struct { - name string - opts *generateResponseFromErrorOpts - expectedResponse *dns.Msg - }{ - { - name: "error is nil returns server failure", - opts: &generateResponseFromErrorOpts{ - req: &dns.Msg{}, - logger: testutil.Logger(t), - configCtx: &RouterDynamicConfig{ - DisableCompression: true, - }, - err: nil, - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: false, - Rcode: dns.RcodeServerFailure, - }, - }, - }, - { - name: "error is invalid question returns name error", - opts: &generateResponseFromErrorOpts{ - req: &dns.Msg{ - Question: []dns.Question{ - { - Name: "invalid-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - }, - qName: "invalid-question", - responseDomain: "testdomain.", - logger: testutil.Logger(t), - configCtx: &RouterDynamicConfig{ - DisableCompression: true, - }, - err: errInvalidQuestion, - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeNameError, - }, - Question: []dns.Question{ - { - Name: "invalid-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - Ns: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 0, - }, - Ns: "ns.testdomain.", - Mbox: "hostmaster.testdomain.", - Serial: uint32(time.Now().Unix()), - }, - }, - }, - }, - { - name: "error is name not found returns name error", - opts: &generateResponseFromErrorOpts{ - req: &dns.Msg{ - Question: []dns.Question{ - { - Name: "invalid-name", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - }, - qName: "invalid-name", - responseDomain: "testdomain.", - logger: testutil.Logger(t), - configCtx: &RouterDynamicConfig{ - DisableCompression: true, - }, - err: errNameNotFound, - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeNameError, - }, - Question: []dns.Question{ - { - Name: "invalid-name", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - Ns: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 0, - }, - Ns: "ns.testdomain.", - Mbox: "hostmaster.testdomain.", - Serial: uint32(time.Now().Unix()), - }, - }, - }, - }, - { - name: "error is not implemented returns not implemented error", - opts: &generateResponseFromErrorOpts{ - req: &dns.Msg{ - Question: []dns.Question{ - { - Name: "some-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - }, - qName: "some-question", - responseDomain: "testdomain.", - logger: testutil.Logger(t), - configCtx: &RouterDynamicConfig{ - DisableCompression: true, - }, - err: errNotImplemented, - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeNotImplemented, - }, - Question: []dns.Question{ - { - Name: "some-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - Ns: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 0, - }, - Ns: "ns.testdomain.", - Mbox: "hostmaster.testdomain.", - Serial: uint32(time.Now().Unix()), - }, - }, - }, - }, - { - name: "error is not supported returns name error", - opts: &generateResponseFromErrorOpts{ - req: &dns.Msg{ - Question: []dns.Question{ - { - Name: "some-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - }, - qName: "some-question", - responseDomain: "testdomain.", - logger: testutil.Logger(t), - configCtx: &RouterDynamicConfig{ - DisableCompression: true, - }, - err: discovery.ErrNotSupported, - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeNameError, - }, - Question: []dns.Question{ - { - Name: "some-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - Ns: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 0, - }, - Ns: "ns.testdomain.", - Mbox: "hostmaster.testdomain.", - Serial: uint32(time.Now().Unix()), - }, - }, - }, - }, - { - name: "error is not found returns name error", - opts: &generateResponseFromErrorOpts{ - req: &dns.Msg{ - Question: []dns.Question{ - { - Name: "some-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - }, - qName: "some-question", - responseDomain: "testdomain.", - logger: testutil.Logger(t), - configCtx: &RouterDynamicConfig{ - DisableCompression: true, - }, - err: discovery.ErrNotFound, - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeNameError, - }, - Question: []dns.Question{ - { - Name: "some-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - Ns: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 0, - }, - Ns: "ns.testdomain.", - Mbox: "hostmaster.testdomain.", - Serial: uint32(time.Now().Unix()), - }, - }, - }, - }, - { - name: "error is no data returns success with soa", - opts: &generateResponseFromErrorOpts{ - req: &dns.Msg{ - Question: []dns.Question{ - { - Name: "some-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - }, - qName: "some-question", - responseDomain: "testdomain.", - logger: testutil.Logger(t), - configCtx: &RouterDynamicConfig{ - DisableCompression: true, - }, - err: discovery.ErrNoData, - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: "some-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - Ns: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 0, - }, - Ns: "ns.testdomain.", - Mbox: "hostmaster.testdomain.", - Serial: uint32(time.Now().Unix()), - }, - }, - }, - }, - { - name: "error is no path to datacenter returns name error", - opts: &generateResponseFromErrorOpts{ - req: &dns.Msg{ - Question: []dns.Question{ - { - Name: "some-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - }, - qName: "some-question", - responseDomain: "testdomain.", - logger: testutil.Logger(t), - configCtx: &RouterDynamicConfig{ - DisableCompression: true, - }, - err: discovery.ErrNoPathToDatacenter, - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeNameError, - }, - Question: []dns.Question{ - { - Name: "some-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - Ns: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 0, - }, - Ns: "ns.testdomain.", - Mbox: "hostmaster.testdomain.", - Serial: uint32(time.Now().Unix()), - }, - }, - }, - }, - { - name: "error is something else returns server failure error", - opts: &generateResponseFromErrorOpts{ - req: &dns.Msg{ - Question: []dns.Question{ - { - Name: "some-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - }, - qName: "some-question", - responseDomain: "testdomain.", - logger: testutil.Logger(t), - configCtx: &RouterDynamicConfig{ - DisableCompression: true, - }, - err: errors.New("KABOOM"), - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: false, - Rcode: dns.RcodeServerFailure, - }, - Question: []dns.Question{ - { - Name: "some-question", - Qtype: dns.TypeSRV, - Qclass: dns.ClassANY, - }, - }, - Ns: nil, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - tc.opts.req.IsEdns0() - actualResponse := dnsResponseGenerator{}.generateResponseFromError(tc.opts) - require.Equal(t, tc.expectedResponse, actualResponse) - }) - } -} - -func TestDNSResponseGenerator_setEDNS(t *testing.T) { - testCases := []struct { - name string - req *dns.Msg - response *dns.Msg - ecsGlobal bool - expectedResponse *dns.Msg - }{ - { - name: "request is not edns0, response is not edns0", - req: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Extra: []dns.RR{ - &dns.OPT{ - Hdr: dns.RR_Header{ - Name: ".", - Rrtype: dns.TypeOPT, - Class: 4096, - Ttl: 0, - }, - Option: []dns.EDNS0{ - &dns.EDNS0_SUBNET{ - Code: 1, - Family: 2, - SourceNetmask: 3, - SourceScope: 4, - Address: net.ParseIP("255.255.255.255"), - }, - }, - }, - }, - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Extra: []dns.RR{ - &dns.OPT{ - Hdr: dns.RR_Header{ - Name: ".", - Rrtype: dns.TypeOPT, - Class: 4096, - Ttl: 0, - }, - Option: []dns.EDNS0{ - &dns.EDNS0_SUBNET{ - Code: 8, - Family: 2, - SourceNetmask: 3, - SourceScope: 3, - Address: net.ParseIP("255.255.255.255"), - }, - }, - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - dnsResponseGenerator{}.setEDNS(tc.req, tc.response, tc.ecsGlobal) - require.Equal(t, tc.expectedResponse, tc.response) - }) - } -} - -func TestDNSResponseGenerator_trimDNSResponse(t *testing.T) { - testCases := []struct { - name string - req *dns.Msg - response *dns.Msg - cfg *RouterDynamicConfig - remoteAddress net.Addr - expectedResponse *dns.Msg - }{ - { - name: "network is udp, enable truncate is true, answer count of 1 is less/equal than configured max f 1, response is not trimmed", - req: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.query.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - cfg: &RouterDynamicConfig{ - UDPAnswerLimit: 1, - }, - remoteAddress: &net.UDPAddr{ - IP: net.ParseIP("127.0.0.1"), - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: "foo.query.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.query.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: "foo.query.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.query.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - { - name: "network is udp, enable truncate is true, answer count of 2 is greater than configure UDP max f 2, response is trimmed", - req: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.query.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - cfg: &RouterDynamicConfig{ - UDPAnswerLimit: 1, - }, - remoteAddress: &net.UDPAddr{ - IP: net.ParseIP("127.0.0.1"), - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: "foo.query.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo1.query.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo2.query.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("2.2.3.4"), - }, - }, - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: "foo.query.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo1.query.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - { - name: "network is tcp, enable truncate is true, answer is less than 64k limit, response is not trimmed", - req: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.query.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - cfg: &RouterDynamicConfig{}, - remoteAddress: &net.TCPAddr{ - IP: net.ParseIP("127.0.0.1"), - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: "foo.query.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.query.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - expectedResponse: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: "foo.query.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.query.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - logger := testutil.Logger(t) - dnsResponseGenerator{}.trimDNSResponse(tc.cfg, tc.remoteAddress, tc.req, tc.response, logger) - require.Equal(t, tc.expectedResponse, tc.response) - }) - - } -} diff --git a/agent/dns/router.go b/agent/dns/router.go deleted file mode 100644 index 9c0175a776..0000000000 --- a/agent/dns/router.go +++ /dev/null @@ -1,558 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "errors" - "fmt" - "net" - "regexp" - "strings" - "sync/atomic" - "time" - - "github.com/armon/go-metrics" - "github.com/armon/go-radix" - "github.com/hashicorp/go-hclog" - "github.com/miekg/dns" - - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/agent/discovery" - "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/internal/dnsutil" - "github.com/hashicorp/consul/logging" -) - -const ( - addrLabel = "addr" - - arpaDomain = "arpa." - arpaLabel = "arpa" - - suffixFailover = "failover." - suffixNoFailover = "no-failover." - maxRecursionLevelDefault = 3 // This field comes from the V1 DNS server and affects V1 catalog lookups - maxRecurseRecords = 5 -) - -var ( - errInvalidQuestion = fmt.Errorf("invalid question") - errNameNotFound = fmt.Errorf("name not found") - errNotImplemented = fmt.Errorf("not implemented") - errRecursionFailed = fmt.Errorf("recursion failed") - - trailingSpacesRE = regexp.MustCompile(" +$") -) - -// RouterDynamicConfig is the dynamic configuration that can be hot-reloaded -type RouterDynamicConfig struct { - ARecordLimit int - DisableCompression bool - EnableTruncate bool - NodeMetaTXT bool - NodeTTL time.Duration - Recursors []string - RecursorTimeout time.Duration - RecursorStrategy structs.RecursorStrategy - SOAConfig SOAConfig - // TTLRadix sets service TTLs by prefix, eg: "database-*" - TTLRadix *radix.Tree - // TTLStrict sets TTLs to service by full name match. It Has higher priority than TTLRadix - TTLStrict map[string]time.Duration - UDPAnswerLimit int -} - -// GetTTLForService Find the TTL for a given service. -// return ttl, true if found, 0, false otherwise -func (cfg *RouterDynamicConfig) GetTTLForService(service string) (time.Duration, bool) { - if cfg.TTLStrict != nil { - ttl, ok := cfg.TTLStrict[service] - if ok { - return ttl, true - } - } - if cfg.TTLRadix != nil { - _, ttlRaw, ok := cfg.TTLRadix.LongestPrefix(service) - if ok { - return ttlRaw.(time.Duration), true - } - } - return 0, false -} - -type SOAConfig struct { - Refresh uint32 // 3600 by default - Retry uint32 // 600 - Expire uint32 // 86400 - Minttl uint32 // 0 -} - -// DiscoveryQueryProcessor is an interface that can be used by any consumer requesting Service Discovery results. -// This could be attached to a gRPC endpoint in the future in addition to DNS. -// Making this an interface means testing the router with a mock is trivial. -type DiscoveryQueryProcessor interface { - QueryByName(*discovery.Query, discovery.Context) ([]*discovery.Result, error) - QueryByIP(net.IP, discovery.Context) ([]*discovery.Result, error) -} - -// dnsRecursor is an interface that can be used to mock calls to external DNS servers for unit testing. -// -//go:generate mockery --name dnsRecursor --inpackage -type dnsRecursor interface { - handle(req *dns.Msg, cfgCtx *RouterDynamicConfig, remoteAddress net.Addr) (*dns.Msg, error) -} - -// Router replaces miekg/dns.ServeMux with a simpler router that only checks for the 2-3 valid domains -// that Consul supports and forwards to a single DiscoveryQueryProcessor handler. If there is no match, it will recurse. -type Router struct { - processor DiscoveryQueryProcessor - recursor dnsRecursor - domain string - altDomain string - nodeName string - logger hclog.Logger - - tokenFunc func() string - translateAddressFunc func(dc string, addr string, taggedAddresses map[string]string, accept dnsutil.TranslateAddressAccept) string - translateServiceAddressFunc func(dc string, address string, taggedAddresses map[string]structs.ServiceAddress, accept dnsutil.TranslateAddressAccept) string - - // dynamicConfig stores the config as an atomic value (for hot-reloading). - // It is always of type *RouterDynamicConfig - dynamicConfig atomic.Value -} - -var _ = dns.Handler(&Router{}) -var _ = DNSRouter(&Router{}) - -func NewRouter(cfg Config) (*Router, error) { - // Make sure domains are FQDN, make them case-insensitive for DNSRequestRouter - domain := dns.CanonicalName(cfg.AgentConfig.DNSDomain) - altDomain := dns.CanonicalName(cfg.AgentConfig.DNSAltDomain) - - logger := cfg.Logger.Named(logging.DNS) - - router := &Router{ - processor: cfg.Processor, - recursor: newRecursor(logger), - domain: domain, - altDomain: altDomain, - logger: logger, - nodeName: cfg.AgentConfig.NodeName, - tokenFunc: cfg.TokenFunc, - translateAddressFunc: cfg.TranslateAddressFunc, - translateServiceAddressFunc: cfg.TranslateServiceAddressFunc, - } - - if err := router.ReloadConfig(cfg.AgentConfig); err != nil { - return nil, err - } - return router, nil -} - -// HandleRequest is used to process an individual DNS request. It returns a message in success or fail cases. -func (r *Router) HandleRequest(req *dns.Msg, reqCtx Context, remoteAddress net.Addr) *dns.Msg { - configCtx := r.dynamicConfig.Load().(*RouterDynamicConfig) - - respGenerator := dnsResponseGenerator{} - - err := validateAndNormalizeRequest(req) - if err != nil { - r.logger.Error("error parsing DNS query", "error", err) - if errors.Is(err, errInvalidQuestion) { - return respGenerator.createRefusedResponse(req) - } - return respGenerator.createServerFailureResponse(req, configCtx, false) - } - - r.logger.Trace("received request", "question", req.Question[0].Name, "type", dns.Type(req.Question[0].Qtype).String()) - r.normalizeContext(&reqCtx) - - defer func(s time.Time, q dns.Question) { - metrics.MeasureSinceWithLabels([]string{"dns", "query"}, s, - []metrics.Label{ - {Name: "node", Value: r.nodeName}, - {Name: "type", Value: dns.Type(q.Qtype).String()}, - }) - - r.logger.Trace("request served from client", - "name", q.Name, - "type", dns.Type(q.Qtype).String(), - "class", dns.Class(q.Qclass).String(), - "latency", time.Since(s).String(), - "client", remoteAddress.String(), - "client_network", remoteAddress.Network(), - ) - }(time.Now(), req.Question[0]) - - return r.handleRequestRecursively(req, reqCtx, configCtx, remoteAddress, maxRecursionLevelDefault) -} - -// handleRequestRecursively is used to process an individual DNS request. It will recurse as needed -// a maximum number of times and returns a message in success or fail cases. -func (r *Router) handleRequestRecursively(req *dns.Msg, reqCtx Context, configCtx *RouterDynamicConfig, - remoteAddress net.Addr, maxRecursionLevel int) *dns.Msg { - respGenerator := dnsResponseGenerator{} - - r.logger.Trace( - "received request", - "question", req.Question[0].Name, - "type", dns.Type(req.Question[0].Qtype).String(), - "recursion_remaining", maxRecursionLevel) - - responseDomain, needRecurse := r.parseDomain(req.Question[0].Name) - if needRecurse && !canRecurse(configCtx) { - // This is the same error as an unmatched domain - return respGenerator.createRefusedResponse(req) - } - - if needRecurse { - r.logger.Trace("checking recursors to handle request", "question", req.Question[0].Name, "type", dns.Type(req.Question[0].Qtype).String()) - - // This assumes `canRecurse(configCtx)` is true above - resp, err := r.recursor.handle(req, configCtx, remoteAddress) - if err != nil && !errors.Is(err, errRecursionFailed) { - r.logger.Error("unhandled error recursing DNS query", "error", err) - } - if err != nil { - return respGenerator.createServerFailureResponse(req, configCtx, true) - } - return resp - } - - // Need to pass the question name to properly support recursion and the - // trimming of the domain suffixes. - qName := dns.CanonicalName(req.Question[0].Name) - if maxRecursionLevel < maxRecursionLevelDefault { - // Get the QName without the domain suffix - qName = r.trimDomain(qName) - } - - results, query, err := discoveryResultsFetcher{}.getQueryResults(&getQueryOptions{ - req: req, - reqCtx: reqCtx, - qName: qName, - remoteAddress: remoteAddress, - processor: r.processor, - logger: r.logger, - domain: r.domain, - altDomain: r.altDomain, - }) - - // in case of the wrapped ECSNotGlobalError, extract the error from it. - isECSGlobal := !errors.Is(err, discovery.ErrECSNotGlobal) - err = getErrorFromECSNotGlobalError(err) - if err != nil { - return respGenerator.generateResponseFromError(&generateResponseFromErrorOpts{ - req: req, - err: err, - qName: qName, - configCtx: configCtx, - responseDomain: responseDomain, - isECSGlobal: isECSGlobal, - query: query, - canRecurse: canRecurse(configCtx), - logger: r.logger, - }) - } - - r.logger.Trace("serializing results", "question", req.Question[0].Name, "results-found", len(results)) - - // This needs the question information because it affects the serialization format. - // e.g., the Consul service has the same "results" for both NS and A/AAAA queries, but the serialization differs. - serializedOpts := &serializeOptions{ - req: req, - reqCtx: reqCtx, - query: query, - results: results, - cfg: configCtx, - responseDomain: responseDomain, - remoteAddress: remoteAddress, - maxRecursionLevel: maxRecursionLevel, - translateAddressFunc: r.translateAddressFunc, - translateServiceAddressFunc: r.translateServiceAddressFunc, - resolveCnameFunc: r.resolveCNAME, - } - resp, err := messageSerializer{}.serialize(serializedOpts) - if err != nil { - r.logger.Error("error serializing DNS results", "error", err) - return respGenerator.generateResponseFromError(&generateResponseFromErrorOpts{ - req: req, - err: err, - qName: qName, - configCtx: configCtx, - responseDomain: responseDomain, - isECSGlobal: isECSGlobal, - query: query, - canRecurse: false, - logger: r.logger, - }) - } - - respGenerator.trimDNSResponse(configCtx, remoteAddress, req, resp, r.logger) - respGenerator.setEDNS(req, resp, isECSGlobal) - return resp -} - -// trimDomain trims the domain from the question name. -func (r *Router) trimDomain(questionName string) string { - longer := r.domain - shorter := r.altDomain - - if len(shorter) > len(longer) { - longer, shorter = shorter, longer - } - - if strings.HasSuffix(questionName, "."+strings.TrimLeft(longer, ".")) { - return strings.TrimSuffix(questionName, longer) - } - return strings.TrimSuffix(questionName, shorter) -} - -// ServeDNS implements the miekg/dns.Handler interface. -// This is a standard DNS listener. -func (r *Router) ServeDNS(w dns.ResponseWriter, req *dns.Msg) { - out := r.HandleRequest(req, Context{}, w.RemoteAddr()) - w.WriteMsg(out) -} - -// ReloadConfig hot-reloads the router config with new parameters -func (r *Router) ReloadConfig(newCfg *config.RuntimeConfig) error { - cfg, err := getDynamicRouterConfig(newCfg) - if err != nil { - return fmt.Errorf("error loading DNS config: %w", err) - } - r.dynamicConfig.Store(cfg) - return nil -} - -// resolveCNAME is used to recursively resolve CNAME records -func (r *Router) resolveCNAME(cfgContext *RouterDynamicConfig, name string, reqCtx Context, - remoteAddress net.Addr, maxRecursionLevel int) []dns.RR { - // If the CNAME record points to a Consul address, resolve it internally - // Convert query to lowercase because DNS is case-insensitive; r.domain and - // r.altDomain are already converted - - if ln := strings.ToLower(name); strings.HasSuffix(ln, "."+r.domain) || strings.HasSuffix(ln, "."+r.altDomain) { - if maxRecursionLevel < 1 { - r.logger.Error("Infinite recursion detected for name, won't perform any CNAME resolution.", "name", name) - return nil - } - req := &dns.Msg{} - - req.SetQuestion(name, dns.TypeANY) - // TODO: handle error response (this is a comment from the V1 DNS Server) - resp := r.handleRequestRecursively(req, reqCtx, cfgContext, nil, maxRecursionLevel-1) - - return resp.Answer - } - - // Do nothing if we don't have a recursor - if !canRecurse(cfgContext) { - return nil - } - - // Ask for any A records - m := new(dns.Msg) - m.SetQuestion(name, dns.TypeA) - - // Make a DNS lookup request - recursorResponse, err := r.recursor.handle(m, cfgContext, remoteAddress) - if err == nil { - return recursorResponse.Answer - } - - r.logger.Error("all resolvers failed for name", "name", name) - return nil -} - -// Request type is similar to miekg/dns.Type, but correlates to the different query processors we might need to invoke. -type requestType string - -const ( - requestTypeName requestType = "NAME" // A/AAAA/CNAME/SRV - requestTypeIP requestType = "IP" // PTR - requestTypeAddress requestType = "ADDR" // Custom addr. A/AAAA lookups - requestTypeConsul requestType = "CONSUL" // SOA/NS -) - -// parseDomain converts a DNS message into a generic discovery request. -// If the request domain does not match "consul." or the alternative domain, -// it will return true for needRecurse. The logic is based on miekg/dns.ServeDNS matcher. -// The implementation assumes that the only valid domains are "consul." and the alternative domain, and -// that DS query types are not supported. -func (r *Router) parseDomain(questionName string) (string, bool) { - target := dns.CanonicalName(questionName) - target, _ = stripAnyFailoverSuffix(target) - - for offset, overflow := 0, false; !overflow; offset, overflow = dns.NextLabel(target, offset) { - subdomain := target[offset:] - switch subdomain { - case ".": - // We don't support consul having a domain or altdomain attached to the root. - return "", true - case r.domain: - return r.domain, false - case r.altDomain: - return r.altDomain, false - case arpaDomain: - // PTR queries always respond with the primary domain. - return r.domain, false - // Default: fallthrough - } - } - // No match found; recurse if possible - return "", true -} - -// GetConfig returns the current router config -func (r *Router) GetConfig() *RouterDynamicConfig { - return r.dynamicConfig.Load().(*RouterDynamicConfig) -} - -// getErrorFromECSNotGlobalError returns the underlying error from an ECSNotGlobalError, if it exists. -func getErrorFromECSNotGlobalError(err error) error { - if errors.Is(err, discovery.ErrECSNotGlobal) { - return err.(discovery.ECSNotGlobalError).Unwrap() - } - return err -} - -// parseRequestType inspects the DNS message type and question name to determine the requestType of request. -// We assume by the time this is called, we are responding to a question with a domain we serve. -// This is used internally to determine which query processor method (if any) to invoke. -func parseRequestType(req *dns.Msg) requestType { - switch { - case req.Question[0].Qtype == dns.TypeSOA || req.Question[0].Qtype == dns.TypeNS: - // SOA and NS type supersede the domain - // NOTE!: In V1 of the DNS server it was possible to serve a PTR lookup using the arpa domain but a SOA question type. - // This also included the SOA record. This seemed inconsistent and unnecessary - it was removed for simplicity. - return requestTypeConsul - case isPTRSubdomain(req.Question[0].Name): - return requestTypeIP - case isAddrSubdomain(req.Question[0].Name): - return requestTypeAddress - default: - return requestTypeName - } -} - -// validateAndNormalizeRequest validates the DNS request and normalizes the request name. -func validateAndNormalizeRequest(req *dns.Msg) error { - // like upstream miekg/dns, we require at least one question, - // but we will only answer the first. - if len(req.Question) == 0 { - return errInvalidQuestion - } - - // We mutate the request name to respond with the canonical name. - // This is Consul convention. - req.Question[0].Name = dns.CanonicalName(req.Question[0].Name) - return nil -} - -// normalizeContext makes sure context information is populated with agent defaults as needed. -// Right now this is just the ACL token. We do this in the router with the token because DNS doesn't -// allow a token to be passed in the request, and we expect ACL tokens upfront in APIs when they are enabled. -// Tenancy information is left out because it is safe/expected to assume agent defaults in the backend lookup. -func (r *Router) normalizeContext(ctx *Context) { - if ctx.Token == "" { - ctx.Token = r.tokenFunc() - } -} - -// stripAnyFailoverSuffix strips off the suffixes that may have been added to the request name. -func stripAnyFailoverSuffix(target string) (string, bool) { - enableFailover := false - - // Strip off any suffixes that may have been added. - offset, underflow := dns.PrevLabel(target, 1) - if !underflow { - maybeSuffix := target[offset:] - switch maybeSuffix { - case suffixFailover: - target = target[:offset] - enableFailover = true - case suffixNoFailover: - target = target[:offset] - } - } - return target, enableFailover -} - -// isAddrSubdomain returns true if the domain is a valid addr subdomain. -func isAddrSubdomain(domain string) bool { - labels := dns.SplitDomainName(domain) - - // Looking for .addr..consul. - if len(labels) > 2 { - return labels[1] == addrLabel - } - return false -} - -// isPTRSubdomain returns true if the domain ends in the PTR domain, "in-addr.arpa.". -func isPTRSubdomain(domain string) bool { - labels := dns.SplitDomainName(domain) - labelCount := len(labels) - - // We keep this check brief so we can have more specific error handling later. - if labelCount < 1 { - return false - } - - return labels[labelCount-1] == arpaLabel -} - -// getDynamicRouterConfig takes agent config and creates/resets the config used by DNS Router -func getDynamicRouterConfig(conf *config.RuntimeConfig) (*RouterDynamicConfig, error) { - cfg := &RouterDynamicConfig{ - ARecordLimit: conf.DNSARecordLimit, - EnableTruncate: conf.DNSEnableTruncate, - NodeTTL: conf.DNSNodeTTL, - RecursorStrategy: conf.DNSRecursorStrategy, - RecursorTimeout: conf.DNSRecursorTimeout, - UDPAnswerLimit: conf.DNSUDPAnswerLimit, - NodeMetaTXT: conf.DNSNodeMetaTXT, - DisableCompression: conf.DNSDisableCompression, - SOAConfig: SOAConfig{ - Expire: conf.DNSSOA.Expire, - Minttl: conf.DNSSOA.Minttl, - Refresh: conf.DNSSOA.Refresh, - Retry: conf.DNSSOA.Retry, - }, - } - - if conf.DNSServiceTTL != nil { - cfg.TTLRadix = radix.New() - cfg.TTLStrict = make(map[string]time.Duration) - - for key, ttl := range conf.DNSServiceTTL { - // All suffix with '*' are put in radix - // This include '*' that will match anything - if strings.HasSuffix(key, "*") { - cfg.TTLRadix.Insert(key[:len(key)-1], ttl) - } else { - cfg.TTLStrict[key] = ttl - } - } - } else { - cfg.TTLRadix = nil - cfg.TTLStrict = nil - } - - for _, r := range conf.DNSRecursors { - ra, err := formatRecursorAddress(r) - if err != nil { - return nil, fmt.Errorf("invalid recursor address: %w", err) - } - cfg.Recursors = append(cfg.Recursors, ra) - } - - return cfg, nil -} - -// canRecurse returns true if the router can recurse on the request. -func canRecurse(cfg *RouterDynamicConfig) bool { - return len(cfg.Recursors) > 0 -} diff --git a/agent/dns/router_addr_test.go b/agent/dns/router_addr_test.go deleted file mode 100644 index 2c493bd13e..0000000000 --- a/agent/dns/router_addr_test.go +++ /dev/null @@ -1,405 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "net" - "testing" - "time" - - "github.com/miekg/dns" -) - -func Test_HandleRequest_ADDR(t *testing.T) { - testCases := []HandleTestCase{ - { - name: "test A 'addr.' query, ipv4 response", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "c000020a.addr.dc1.consul", // "intentionally missing the trailing dot" - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "c000020a.addr.dc1.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "c000020a.addr.dc1.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("192.0.2.10"), - }, - }, - }, - }, - { - name: "test AAAA 'addr.' query, ipv4 response", - // Since we asked for an AAAA record, the A record that resolves from the address is attached as an extra - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "c000020a.addr.dc1.consul", - Qtype: dns.TypeAAAA, - Qclass: dns.ClassINET, - }, - }, - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "c000020a.addr.dc1.consul.", - Qtype: dns.TypeAAAA, - Qclass: dns.ClassINET, - }, - }, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "c000020a.addr.dc1.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("192.0.2.10"), - }, - }, - }, - }, - { - name: "test SRV 'addr.' query, ipv4 response", - // Since we asked for a SRV record, the A record that resolves from the address is attached as an extra - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "c000020a.addr.dc1.consul", - Qtype: dns.TypeSRV, - Qclass: dns.ClassINET, - }, - }, - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "c000020a.addr.dc1.consul.", - Qtype: dns.TypeSRV, - Qclass: dns.ClassINET, - }, - }, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "c000020a.addr.dc1.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("192.0.2.10"), - }, - }, - }, - }, - { - name: "test ANY 'addr.' query, ipv4 response", - // The response to ANY should look the same as the A response - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "c000020a.addr.dc1.consul", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "c000020a.addr.dc1.consul.", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "c000020a.addr.dc1.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("192.0.2.10"), - }, - }, - }, - }, - { - name: "test AAAA 'addr.' query, ipv6 response", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "20010db800010002cafe000000001337.addr.dc1.consul", - Qtype: dns.TypeAAAA, - Qclass: dns.ClassINET, - }, - }, - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "20010db800010002cafe000000001337.addr.dc1.consul.", - Qtype: dns.TypeAAAA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.AAAA{ - Hdr: dns.RR_Header{ - Name: "20010db800010002cafe000000001337.addr.dc1.consul.", - Rrtype: dns.TypeAAAA, - Class: dns.ClassINET, - Ttl: 123, - }, - AAAA: net.ParseIP("2001:db8:1:2:cafe::1337"), - }, - }, - }, - }, - { - name: "test A 'addr.' query, ipv6 response", - // Since we asked for an A record, the AAAA record that resolves from the address is attached as an extra - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "20010db800010002cafe000000001337.addr.dc1.consul", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "20010db800010002cafe000000001337.addr.dc1.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Extra: []dns.RR{ - &dns.AAAA{ - Hdr: dns.RR_Header{ - Name: "20010db800010002cafe000000001337.addr.dc1.consul.", - Rrtype: dns.TypeAAAA, - Class: dns.ClassINET, - Ttl: 123, - }, - AAAA: net.ParseIP("2001:db8:1:2:cafe::1337"), - }, - }, - }, - }, - { - name: "test SRV 'addr.' query, ipv6 response", - // Since we asked for an SRV record, the AAAA record that resolves from the address is attached as an extra - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "20010db800010002cafe000000001337.addr.dc1.consul", - Qtype: dns.TypeSRV, - Qclass: dns.ClassINET, - }, - }, - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "20010db800010002cafe000000001337.addr.dc1.consul.", - Qtype: dns.TypeSRV, - Qclass: dns.ClassINET, - }, - }, - Extra: []dns.RR{ - &dns.AAAA{ - Hdr: dns.RR_Header{ - Name: "20010db800010002cafe000000001337.addr.dc1.consul.", - Rrtype: dns.TypeAAAA, - Class: dns.ClassINET, - Ttl: 123, - }, - AAAA: net.ParseIP("2001:db8:1:2:cafe::1337"), - }, - }, - }, - }, - { - name: "test ANY 'addr.' query, ipv6 response", - // The response to ANY should look the same as the AAAA response - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "20010db800010002cafe000000001337.addr.dc1.consul", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "20010db800010002cafe000000001337.addr.dc1.consul.", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.AAAA{ - Hdr: dns.RR_Header{ - Name: "20010db800010002cafe000000001337.addr.dc1.consul.", - Rrtype: dns.TypeAAAA, - Class: dns.ClassINET, - Ttl: 123, - }, - AAAA: net.ParseIP("2001:db8:1:2:cafe::1337"), - }, - }, - }, - }, - { - name: "test malformed 'addr.' query", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "c000.addr.dc1.consul", // too short - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Rcode: dns.RcodeNameError, // NXDOMAIN - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "c000.addr.dc1.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Ns: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "consul.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 4, - }, - Ns: "ns.consul.", - Serial: uint32(time.Now().Unix()), - Mbox: "hostmaster.consul.", - Refresh: 1, - Expire: 3, - Retry: 2, - Minttl: 4, - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runHandleTestCases(t, tc) - }) - } -} diff --git a/agent/dns/router_ns_test.go b/agent/dns/router_ns_test.go deleted file mode 100644 index 0011ac8626..0000000000 --- a/agent/dns/router_ns_test.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "net" - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/agent/discovery" - "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/internal/resource" -) - -func Test_HandleRequest_NS(t *testing.T) { - testCases := []HandleTestCase{ - { - name: "vanilla NS query", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "consul.", - Qtype: dns.TypeNS, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). - Return([]*discovery.Result{ - { - Node: &discovery.Location{Name: "server-one", Address: "1.2.3.4"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - }, - { - Node: &discovery.Location{Name: "server-two", Address: "4.5.6.7"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - }, - }, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - reqType := args.Get(2).(discovery.LookupType) - - require.Equal(t, discovery.LookupTypeService, reqType) - require.Equal(t, structs.ConsulServiceName, req.Name) - require.Equal(t, 3, req.Limit) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "consul.", - Qtype: dns.TypeNS, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.NS{ - Hdr: dns.RR_Header{ - Name: "consul.", - Rrtype: dns.TypeNS, - Class: dns.ClassINET, - Ttl: 123, - }, - Ns: "server-one.workload.default.ns.default.ap.consul.", - }, - &dns.NS{ - Hdr: dns.RR_Header{ - Name: "consul.", - Rrtype: dns.TypeNS, - Class: dns.ClassINET, - Ttl: 123, - }, - Ns: "server-two.workload.default.ns.default.ap.consul.", - }, - }, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "server-one.workload.default.ns.default.ap.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "server-two.workload.default.ns.default.ap.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("4.5.6.7"), - }, - }, - }, - }, - { - name: "NS query against alternate domain", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "testdomain.", - Qtype: dns.TypeNS, - Qclass: dns.ClassINET, - }, - }, - }, - agentConfig: &config.RuntimeConfig{ - DNSDomain: "consul", - DNSAltDomain: "testdomain", - DNSNodeTTL: 123 * time.Second, - DNSSOA: config.RuntimeSOAConfig{ - Refresh: 1, - Retry: 2, - Expire: 3, - Minttl: 4, - }, - DNSUDPAnswerLimit: maxUDPAnswerLimit, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). - Return([]*discovery.Result{ - { - Node: &discovery.Location{Name: "server-one", Address: "1.2.3.4"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - }, - { - Node: &discovery.Location{Name: "server-two", Address: "4.5.6.7"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - }, - }, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - reqType := args.Get(2).(discovery.LookupType) - - require.Equal(t, discovery.LookupTypeService, reqType) - require.Equal(t, structs.ConsulServiceName, req.Name) - require.Equal(t, 3, req.Limit) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "testdomain.", - Qtype: dns.TypeNS, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.NS{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeNS, - Class: dns.ClassINET, - Ttl: 123, - }, - Ns: "server-one.workload.default.ns.default.ap.testdomain.", - }, - &dns.NS{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeNS, - Class: dns.ClassINET, - Ttl: 123, - }, - Ns: "server-two.workload.default.ns.default.ap.testdomain.", - }, - }, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "server-one.workload.default.ns.default.ap.testdomain.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "server-two.workload.default.ns.default.ap.testdomain.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("4.5.6.7"), - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runHandleTestCases(t, tc) - }) - } -} diff --git a/agent/dns/router_prepared_query_test.go b/agent/dns/router_prepared_query_test.go deleted file mode 100644 index ff33395a5e..0000000000 --- a/agent/dns/router_prepared_query_test.go +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "net" - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/agent/discovery" -) - -func Test_HandleRequest_PreparedQuery(t *testing.T) { - testCases := []HandleTestCase{ - { - name: "v1 prepared query w/ TTL override, ANY query, returns A record", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.query.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - agentConfig: &config.RuntimeConfig{ - DNSDomain: "consul", - DNSNodeTTL: 123 * time.Second, - DNSSOA: config.RuntimeSOAConfig{ - Refresh: 1, - Retry: 2, - Expire: 3, - Minttl: 4, - }, - DNSUDPAnswerLimit: maxUDPAnswerLimit, - // We shouldn't use this if we have the override defined - DNSServiceTTL: map[string]time.Duration{ - "foo": 1 * time.Second, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchPreparedQuery", mock.Anything, mock.Anything). - Return([]*discovery.Result{ - { - Service: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, - Node: &discovery.Location{Name: "bar", Address: "1.2.3.4"}, - Type: discovery.ResultTypeService, - Tenancy: discovery.ResultTenancy{ - Datacenter: "dc1", - }, - DNS: discovery.DNSConfig{ - TTL: getUint32Ptr(3), - Weight: 1, - }, - }, - }, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - require.Equal(t, "foo", req.Name) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "foo.query.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.query.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 3, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - { - name: "v1 prepared query w/ matching service TTL, ANY query, returns A record", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.query.dc1.cluster.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - agentConfig: &config.RuntimeConfig{ - DNSDomain: "consul", - DNSNodeTTL: 123 * time.Second, - DNSSOA: config.RuntimeSOAConfig{ - Refresh: 1, - Retry: 2, - Expire: 3, - Minttl: 4, - }, - DNSUDPAnswerLimit: maxUDPAnswerLimit, - // Results should use this as the TTL - DNSServiceTTL: map[string]time.Duration{ - "foo": 1 * time.Second, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchPreparedQuery", mock.Anything, mock.Anything). - Return([]*discovery.Result{ - { - Service: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, - Node: &discovery.Location{Name: "bar", Address: "1.2.3.4"}, - Type: discovery.ResultTypeService, - Tenancy: discovery.ResultTenancy{ - Datacenter: "dc1", - }, - DNS: discovery.DNSConfig{ - // Intentionally no TTL here. - Weight: 1, - }, - }, - }, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - require.Equal(t, "foo", req.Name) - require.Equal(t, "dc1", req.Tenancy.Datacenter) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "foo.query.dc1.cluster.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.query.dc1.cluster.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 1, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runHandleTestCases(t, tc) - }) - } -} diff --git a/agent/dns/router_ptr_test.go b/agent/dns/router_ptr_test.go deleted file mode 100644 index 7704e350d1..0000000000 --- a/agent/dns/router_ptr_test.go +++ /dev/null @@ -1,563 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "net" - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/discovery" -) - -func Test_HandleRequest_PTR(t *testing.T) { - testCases := []HandleTestCase{ - { - name: "PTR lookup for node, query type is ANY", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "4.3.2.1.in-addr.arpa", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - results := []*discovery.Result{ - { - Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, - Service: &discovery.Location{Name: "bar", Address: "foo"}, - Type: discovery.ResultTypeNode, - Tenancy: discovery.ResultTenancy{ - Datacenter: "dc2", - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchRecordsByIp", mock.Anything, mock.Anything). - Return(results, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(net.IP) - - require.NotNil(t, req) - require.Equal(t, "1.2.3.4", req.String()) - }) - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "4.3.2.1.in-addr.arpa.", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.PTR{ - Hdr: dns.RR_Header{ - Name: "4.3.2.1.in-addr.arpa.", - Rrtype: dns.TypePTR, - Class: dns.ClassINET, - }, - Ptr: "foo.node.dc2.consul.", - }, - }, - }, - }, - { - name: "PTR lookup for IPV6 node", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa", - Qtype: dns.TypePTR, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - results := []*discovery.Result{ - { - Node: &discovery.Location{Name: "foo", Address: "2001:db8::567:89ab"}, - Service: &discovery.Location{Name: "web", Address: "foo"}, - Type: discovery.ResultTypeNode, - Tenancy: discovery.ResultTenancy{ - Datacenter: "dc2", - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchRecordsByIp", mock.Anything, mock.Anything). - Return(results, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(net.IP) - - require.NotNil(t, req) - require.Equal(t, "2001:db8::567:89ab", req.String()) - }) - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.", - Qtype: dns.TypePTR, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.PTR{ - Hdr: dns.RR_Header{ - Name: "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.", - Rrtype: dns.TypePTR, - Class: dns.ClassINET, - }, - Ptr: "foo.node.dc2.consul.", - }, - }, - }, - }, - { - name: "PTR lookup for invalid IP address", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "257.3.2.1.in-addr.arpa", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeNameError, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "257.3.2.1.in-addr.arpa.", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - Ns: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "consul.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 4, - }, - Ns: "ns.consul.", - Serial: uint32(time.Now().Unix()), - Mbox: "hostmaster.consul.", - Refresh: 1, - Expire: 3, - Retry: 2, - Minttl: 4, - }, - }, - }, - }, - { - name: "PTR lookup for invalid subdomain", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "4.3.2.1.blah.arpa", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeNameError, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "4.3.2.1.blah.arpa.", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - Ns: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "consul.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 4, - }, - Ns: "ns.consul.", - Serial: uint32(time.Now().Unix()), - Mbox: "hostmaster.consul.", - Refresh: 1, - Expire: 3, - Retry: 2, - Minttl: 4, - }, - }, - }, - }, - { - name: "[ENT] PTR Lookup for node w/ peer name in default partition, query type is ANY", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "4.3.2.1.in-addr.arpa", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - results := []*discovery.Result{ - { - Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, - Type: discovery.ResultTypeNode, - Service: &discovery.Location{Name: "foo-web", Address: "foo"}, - Tenancy: discovery.ResultTenancy{ - Datacenter: "dc2", - PeerName: "peer1", - Partition: "default", - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchRecordsByIp", mock.Anything, mock.Anything). - Return(results, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(net.IP) - - require.NotNil(t, req) - require.Equal(t, "1.2.3.4", req.String()) - }) - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "4.3.2.1.in-addr.arpa.", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.PTR{ - Hdr: dns.RR_Header{ - Name: "4.3.2.1.in-addr.arpa.", - Rrtype: dns.TypePTR, - Class: dns.ClassINET, - }, - Ptr: "foo.node.peer1.peer.default.ap.consul.", - }, - }, - }, - }, - { - name: "[ENT] PTR Lookup for service in default namespace, query type is PTR", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "4.3.2.1.in-addr.arpa", - Qtype: dns.TypePTR, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - results := []*discovery.Result{ - { - Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, - Type: discovery.ResultTypeService, - Service: &discovery.Location{Name: "foo", Address: "foo"}, - Tenancy: discovery.ResultTenancy{ - Datacenter: "dc2", - Namespace: "default", - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchRecordsByIp", mock.Anything, mock.Anything). - Return(results, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(net.IP) - - require.NotNil(t, req) - require.Equal(t, "1.2.3.4", req.String()) - }) - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "4.3.2.1.in-addr.arpa.", - Qtype: dns.TypePTR, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.PTR{ - Hdr: dns.RR_Header{ - Name: "4.3.2.1.in-addr.arpa.", - Rrtype: dns.TypePTR, - Class: dns.ClassINET, - }, - Ptr: "foo.service.default.dc2.consul.", - }, - }, - }, - }, - { - name: "[ENT] PTR Lookup for service in a non-default namespace, query type is PTR", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "4.3.2.1.in-addr.arpa", - Qtype: dns.TypePTR, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - results := []*discovery.Result{ - { - Node: &discovery.Location{Name: "foo-node", Address: "1.2.3.4"}, - Type: discovery.ResultTypeService, - Service: &discovery.Location{Name: "foo", Address: "foo"}, - Tenancy: discovery.ResultTenancy{ - Datacenter: "dc2", - Namespace: "bar", - Partition: "baz", - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchRecordsByIp", mock.Anything, mock.Anything). - Return(results, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(net.IP) - - require.NotNil(t, req) - require.Equal(t, "1.2.3.4", req.String()) - }) - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "4.3.2.1.in-addr.arpa.", - Qtype: dns.TypePTR, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.PTR{ - Hdr: dns.RR_Header{ - Name: "4.3.2.1.in-addr.arpa.", - Rrtype: dns.TypePTR, - Class: dns.ClassINET, - }, - Ptr: "foo.service.bar.dc2.consul.", - }, - }, - }, - }, - { - name: "[CE] PTR Lookup for node w/ peer name, query type is ANY", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "4.3.2.1.in-addr.arpa", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - results := []*discovery.Result{ - { - Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, - Type: discovery.ResultTypeNode, - Service: &discovery.Location{Name: "foo", Address: "foo"}, - Tenancy: discovery.ResultTenancy{ - Datacenter: "dc2", - PeerName: "peer1", - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchRecordsByIp", mock.Anything, mock.Anything). - Return(results, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(net.IP) - - require.NotNil(t, req) - require.Equal(t, "1.2.3.4", req.String()) - }) - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "4.3.2.1.in-addr.arpa.", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.PTR{ - Hdr: dns.RR_Header{ - Name: "4.3.2.1.in-addr.arpa.", - Rrtype: dns.TypePTR, - Class: dns.ClassINET, - }, - Ptr: "foo.node.peer1.peer.consul.", - }, - }, - }, - }, - { - name: "[CE] PTR Lookup for service, query type is PTR", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "4.3.2.1.in-addr.arpa", - Qtype: dns.TypePTR, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - results := []*discovery.Result{ - { - Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, - Service: &discovery.Location{Name: "foo", Address: "foo"}, - Type: discovery.ResultTypeService, - Tenancy: discovery.ResultTenancy{ - Datacenter: "dc2", - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchRecordsByIp", mock.Anything, mock.Anything). - Return(results, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(net.IP) - - require.NotNil(t, req) - require.Equal(t, "1.2.3.4", req.String()) - }) - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "4.3.2.1.in-addr.arpa.", - Qtype: dns.TypePTR, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.PTR{ - Hdr: dns.RR_Header{ - Name: "4.3.2.1.in-addr.arpa.", - Rrtype: dns.TypePTR, - Class: dns.ClassINET, - }, - Ptr: "foo.service.dc2.consul.", - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runHandleTestCases(t, tc) - }) - } -} diff --git a/agent/dns/router_recursor_test.go b/agent/dns/router_recursor_test.go deleted file mode 100644 index b5e4c029c0..0000000000 --- a/agent/dns/router_recursor_test.go +++ /dev/null @@ -1,296 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "errors" - "github.com/hashicorp/consul/agent/config" - "github.com/miekg/dns" - "github.com/stretchr/testify/mock" - "net" - "testing" -) - -func Test_HandleRequest_recursor(t *testing.T) { - testCases := []HandleTestCase{ - { - name: "recursors not configured, non-matching domain", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "google.com", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - // configureRecursor: call not expected. - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Rcode: dns.RcodeRefused, - }, - Question: []dns.Question{ - { - Name: "google.com.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - }, - { - name: "recursors configured, matching domain", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "google.com", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - agentConfig: &config.RuntimeConfig{ - DNSRecursors: []string{"8.8.8.8"}, - DNSUDPAnswerLimit: maxUDPAnswerLimit, - }, - configureRecursor: func(recursor dnsRecursor) { - resp := &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: "google.com.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "google.com.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - } - recursor.(*mockDnsRecursor).On("handle", - mock.Anything, mock.Anything, mock.Anything).Return(resp, nil) - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: "google.com.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "google.com.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - { - name: "recursors configured, no matching domain", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "google.com", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - agentConfig: &config.RuntimeConfig{ - DNSRecursors: []string{"8.8.8.8"}, - DNSUDPAnswerLimit: maxUDPAnswerLimit, - }, - configureRecursor: func(recursor dnsRecursor) { - recursor.(*mockDnsRecursor).On("handle", mock.Anything, mock.Anything, mock.Anything). - Return(nil, errRecursionFailed) - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: false, - Rcode: dns.RcodeServerFailure, - RecursionAvailable: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "google.com.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - }, - { - name: "recursors configured, unhandled error calling recursors", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "google.com", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - agentConfig: &config.RuntimeConfig{ - DNSRecursors: []string{"8.8.8.8"}, - DNSUDPAnswerLimit: maxUDPAnswerLimit, - }, - configureRecursor: func(recursor dnsRecursor) { - err := errors.New("ahhhhh!!!!") - recursor.(*mockDnsRecursor).On("handle", mock.Anything, mock.Anything, mock.Anything). - Return(nil, err) - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: false, - Rcode: dns.RcodeServerFailure, - RecursionAvailable: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "google.com.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - }, - { - name: "recursors configured, the root domain is handled by the recursor", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: ".", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - agentConfig: &config.RuntimeConfig{ - DNSRecursors: []string{"8.8.8.8"}, - DNSUDPAnswerLimit: maxUDPAnswerLimit, - }, - configureRecursor: func(recursor dnsRecursor) { - // this response is modeled after `dig .` - resp := &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: ".", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: ".", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 86391, - }, - Ns: "a.root-servers.net.", - Serial: 2024012200, - Mbox: "nstld.verisign-grs.com.", - Refresh: 1800, - Retry: 900, - Expire: 604800, - Minttl: 86400, - }, - }, - } - recursor.(*mockDnsRecursor).On("handle", - mock.Anything, mock.Anything, mock.Anything).Return(resp, nil) - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: ".", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: ".", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 86391, - }, - Ns: "a.root-servers.net.", - Serial: 2024012200, - Mbox: "nstld.verisign-grs.com.", - Refresh: 1800, - Retry: 900, - Expire: 604800, - Minttl: 86400, - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runHandleTestCases(t, tc) - }) - } -} diff --git a/agent/dns/router_service_test.go b/agent/dns/router_service_test.go deleted file mode 100644 index cf78f8687a..0000000000 --- a/agent/dns/router_service_test.go +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "net" - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/discovery" -) - -func Test_HandleRequest_ServiceQuestions(t *testing.T) { - testCases := []HandleTestCase{ - // Service Lookup - { - name: "When no data is return from a query, send SOA", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.service.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). - Return(nil, discovery.ErrNoData). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - reqType := args.Get(2).(discovery.LookupType) - - require.Equal(t, discovery.LookupTypeService, reqType) - require.Equal(t, "foo", req.Name) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeSuccess, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "foo.service.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Ns: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "consul.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 4, - }, - Ns: "ns.consul.", - Serial: uint32(time.Now().Unix()), - Mbox: "hostmaster.consul.", - Refresh: 1, - Expire: 3, - Retry: 2, - Minttl: 4, - }, - }, - }, - }, - { - // TestDNS_ExternalServiceToConsulCNAMELookup - name: "req type: service / question type: SRV / CNAME required: no", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "alias.service.consul.", - Qtype: dns.TypeSRV, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, - &discovery.QueryPayload{ - Name: "alias", - Tenancy: discovery.QueryTenancy{}, - }, discovery.LookupTypeService). - Return([]*discovery.Result{ - { - Type: discovery.ResultTypeVirtual, - Service: &discovery.Location{Name: "alias", Address: "web.service.consul"}, - Node: &discovery.Location{Name: "web", Address: "web.service.consul"}, - }, - }, - nil).On("FetchEndpoints", mock.Anything, - &discovery.QueryPayload{ - Name: "web", - Tenancy: discovery.QueryTenancy{}, - }, discovery.LookupTypeService). - Return([]*discovery.Result{ - { - Type: discovery.ResultTypeNode, - Service: &discovery.Location{Name: "web", Address: "webnode"}, - Node: &discovery.Location{Name: "webnode", Address: "127.0.0.2"}, - }, - }, nil).On("ValidateRequest", mock.Anything, - mock.Anything).Return(nil).On("NormalizeRequest", mock.Anything) - }, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "alias.service.consul.", - Qtype: dns.TypeSRV, - }, - }, - Answer: []dns.RR{ - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "alias.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - Ttl: 123, - }, - Target: "web.service.consul.", - Priority: 1, - }, - }, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "web.service.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("127.0.0.2"), - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runHandleTestCases(t, tc) - }) - } -} diff --git a/agent/dns/router_soa_test.go b/agent/dns/router_soa_test.go deleted file mode 100644 index f578a4abe3..0000000000 --- a/agent/dns/router_soa_test.go +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "net" - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/agent/discovery" - "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/internal/resource" -) - -func Test_HandleRequest_SOA(t *testing.T) { - testCases := []HandleTestCase{ - { - name: "vanilla SOA query", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "consul.", - Qtype: dns.TypeSOA, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). - Return([]*discovery.Result{ - { - Node: &discovery.Location{Name: "server-one", Address: "1.2.3.4"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - }, - { - Node: &discovery.Location{Name: "server-two", Address: "4.5.6.7"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - }, - }, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - reqType := args.Get(2).(discovery.LookupType) - - require.Equal(t, discovery.LookupTypeService, reqType) - require.Equal(t, structs.ConsulServiceName, req.Name) - require.Equal(t, 3, req.Limit) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "consul.", - Qtype: dns.TypeSOA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "consul.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 4, - }, - Ns: "ns.consul.", - Serial: uint32(time.Now().Unix()), - Mbox: "hostmaster.consul.", - Refresh: 1, - Expire: 3, - Retry: 2, - Minttl: 4, - }, - }, - Ns: []dns.RR{ - &dns.NS{ - Hdr: dns.RR_Header{ - Name: "consul.", - Rrtype: dns.TypeNS, - Class: dns.ClassINET, - Ttl: 123, - }, - Ns: "server-one.workload.default.ns.default.ap.consul.", - }, - &dns.NS{ - Hdr: dns.RR_Header{ - Name: "consul.", - Rrtype: dns.TypeNS, - Class: dns.ClassINET, - Ttl: 123, - }, - Ns: "server-two.workload.default.ns.default.ap.consul.", - }, - }, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "server-one.workload.default.ns.default.ap.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "server-two.workload.default.ns.default.ap.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("4.5.6.7"), - }, - }, - }, - }, - { - name: "SOA query against alternate domain", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "testdomain.", - Qtype: dns.TypeSOA, - Qclass: dns.ClassINET, - }, - }, - }, - agentConfig: &config.RuntimeConfig{ - DNSDomain: "consul", - DNSAltDomain: "testdomain", - DNSNodeTTL: 123 * time.Second, - DNSSOA: config.RuntimeSOAConfig{ - Refresh: 1, - Retry: 2, - Expire: 3, - Minttl: 4, - }, - DNSUDPAnswerLimit: maxUDPAnswerLimit, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). - Return([]*discovery.Result{ - { - Node: &discovery.Location{Name: "server-one", Address: "1.2.3.4"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - }, - { - Node: &discovery.Location{Name: "server-two", Address: "4.5.6.7"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }}, - }, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - reqType := args.Get(2).(discovery.LookupType) - - require.Equal(t, discovery.LookupTypeService, reqType) - require.Equal(t, structs.ConsulServiceName, req.Name) - require.Equal(t, 3, req.Limit) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "testdomain.", - Qtype: dns.TypeSOA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.SOA{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - Ttl: 4, - }, - Ns: "ns.testdomain.", - Serial: uint32(time.Now().Unix()), - Mbox: "hostmaster.testdomain.", - Refresh: 1, - Expire: 3, - Retry: 2, - Minttl: 4, - }, - }, - Ns: []dns.RR{ - &dns.NS{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeNS, - Class: dns.ClassINET, - Ttl: 123, - }, - Ns: "server-one.workload.default.ns.default.ap.testdomain.", - }, - &dns.NS{ - Hdr: dns.RR_Header{ - Name: "testdomain.", - Rrtype: dns.TypeNS, - Class: dns.ClassINET, - Ttl: 123, - }, - Ns: "server-two.workload.default.ns.default.ap.testdomain.", - }, - }, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "server-one.workload.default.ns.default.ap.testdomain.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "server-two.workload.default.ns.default.ap.testdomain.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("4.5.6.7"), - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runHandleTestCases(t, tc) - }) - } -} diff --git a/agent/dns/router_test.go b/agent/dns/router_test.go deleted file mode 100644 index 717ee9e16b..0000000000 --- a/agent/dns/router_test.go +++ /dev/null @@ -1,842 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "fmt" - "net" - "reflect" - "testing" - "time" - - "github.com/armon/go-radix" - - "github.com/hashicorp/consul/internal/dnsutil" - - "github.com/miekg/dns" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/go-hclog" - - "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/agent/discovery" - "github.com/hashicorp/consul/agent/structs" -) - -// HandleTestCase is a test case for the HandleRequest function. -// Tests for HandleRequest are split into multiple files to make it easier to -// manage and understand the tests. Other test files are: -// - router_addr_test.go -// - router_ns_test.go -// - router_prepared_query_test.go -// - router_ptr_test.go -// - router_recursor_test.go -// - router_service_test.go -// - router_soa_test.go -// - router_virtual_test.go -// - router_v2_services_test.go -// - router_workload_test.go -type HandleTestCase struct { - name string - agentConfig *config.RuntimeConfig // This will override the default test Router Config - configureDataFetcher func(fetcher discovery.CatalogDataFetcher) - validateAndNormalizeExpected bool - configureRecursor func(recursor dnsRecursor) - mockProcessorError error - request *dns.Msg - requestContext *Context - remoteAddress net.Addr - response *dns.Msg -} - -func Test_HandleRequest_Validation(t *testing.T) { - testCases := []HandleTestCase{ - { - name: "request with empty message", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{}, - }, - validateAndNormalizeExpected: false, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: false, - Rcode: dns.RcodeRefused, - }, - Compress: false, - Question: nil, - Answer: nil, - Ns: nil, - Extra: nil, - }, - }, - // Context Tests - { - name: "When a request context is provided, use those field in the query", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.service.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - requestContext: &Context{ - Token: "test-token", - DefaultNamespace: "test-namespace", - DefaultPartition: "test-partition", - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - result := []*discovery.Result{ - { - Type: discovery.ResultTypeNode, - Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, - Tenancy: discovery.ResultTenancy{ - Namespace: "test-namespace", - Partition: "test-partition", - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). - Return(result, nil). - Run(func(args mock.Arguments) { - ctx := args.Get(0).(discovery.Context) - req := args.Get(1).(*discovery.QueryPayload) - reqType := args.Get(2).(discovery.LookupType) - - require.Equal(t, "test-token", ctx.Token) - - require.Equal(t, "foo", req.Name) - require.Equal(t, "test-namespace", req.Tenancy.Namespace) - require.Equal(t, "test-partition", req.Tenancy.Partition) - - require.Equal(t, discovery.LookupTypeService, reqType) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "foo.service.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.service.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - { - name: "When a request context is provided, values do not override explicit tenancy", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.service.bar.ns.baz.ap.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - requestContext: &Context{ - Token: "test-token", - DefaultNamespace: "test-namespace", - DefaultPartition: "test-partition", - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - result := []*discovery.Result{ - { - Type: discovery.ResultTypeNode, - Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, - Tenancy: discovery.ResultTenancy{ - Namespace: "bar", - Partition: "baz", - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). - Return(result, nil). - Run(func(args mock.Arguments) { - ctx := args.Get(0).(discovery.Context) - req := args.Get(1).(*discovery.QueryPayload) - reqType := args.Get(2).(discovery.LookupType) - - require.Equal(t, "test-token", ctx.Token) - - require.Equal(t, "foo", req.Name) - require.Equal(t, "bar", req.Tenancy.Namespace) - require.Equal(t, "baz", req.Tenancy.Partition) - - require.Equal(t, discovery.LookupTypeService, reqType) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "foo.service.bar.ns.baz.ap.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.service.bar.ns.baz.ap.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runHandleTestCases(t, tc) - }) - } -} - -// runHandleTestCases runs the test cases for the HandleRequest function. -func runHandleTestCases(t *testing.T, tc HandleTestCase) { - cdf := discovery.NewMockCatalogDataFetcher(t) - if tc.validateAndNormalizeExpected { - cdf.On("ValidateRequest", mock.Anything, mock.Anything).Return(nil) - cdf.On("NormalizeRequest", mock.Anything).Return() - } - - if tc.configureDataFetcher != nil { - tc.configureDataFetcher(cdf) - } - cfg := buildDNSConfig(tc.agentConfig, cdf, tc.mockProcessorError) - - router, err := NewRouter(cfg) - require.NoError(t, err) - - // Replace the recursor with a mock and configure - router.recursor = newMockDnsRecursor(t) - if tc.configureRecursor != nil { - tc.configureRecursor(router.recursor) - } - - ctx := tc.requestContext - if ctx == nil { - ctx = &Context{} - } - - var remoteAddress net.Addr - if tc.remoteAddress != nil { - remoteAddress = tc.remoteAddress - } else { - remoteAddress = &net.UDPAddr{} - } - - actual := router.HandleRequest(tc.request, *ctx, remoteAddress) - require.Equal(t, tc.response, actual) -} - -func TestRouterDynamicConfig_GetTTLForService(t *testing.T) { - type testCase struct { - name string - inputKey string - shouldMatch bool - expectedDuration time.Duration - } - - testCases := []testCase{ - { - name: "strict match", - inputKey: "foo", - shouldMatch: true, - expectedDuration: 1 * time.Second, - }, - { - name: "wildcard match", - inputKey: "bar", - shouldMatch: true, - expectedDuration: 2 * time.Second, - }, - { - name: "wildcard match 2", - inputKey: "bart", - shouldMatch: true, - expectedDuration: 2 * time.Second, - }, - { - name: "no match", - inputKey: "homer", - shouldMatch: false, - expectedDuration: 0 * time.Second, - }, - } - - rtCfg := &config.RuntimeConfig{ - DNSServiceTTL: map[string]time.Duration{ - "foo": 1 * time.Second, - "bar*": 2 * time.Second, - }, - } - cfg, err := getDynamicRouterConfig(rtCfg) - require.NoError(t, err) - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - actual, ok := cfg.GetTTLForService(tc.inputKey) - require.Equal(t, tc.shouldMatch, ok) - require.Equal(t, tc.expectedDuration, actual) - }) - } -} -func buildDNSConfig(agentConfig *config.RuntimeConfig, cdf discovery.CatalogDataFetcher, _ error) Config { - cfg := Config{ - AgentConfig: &config.RuntimeConfig{ - DNSDomain: "consul", - DNSNodeTTL: 123 * time.Second, - DNSSOA: config.RuntimeSOAConfig{ - Refresh: 1, - Retry: 2, - Expire: 3, - Minttl: 4, - }, - DNSUDPAnswerLimit: maxUDPAnswerLimit, - }, - EntMeta: acl.EnterpriseMeta{}, - Logger: hclog.NewNullLogger(), - Processor: discovery.NewQueryProcessor(cdf), - TokenFunc: func() string { return "" }, - TranslateServiceAddressFunc: func(dc string, address string, taggedAddresses map[string]structs.ServiceAddress, accept dnsutil.TranslateAddressAccept) string { - return address - }, - TranslateAddressFunc: func(dc string, addr string, taggedAddresses map[string]string, accept dnsutil.TranslateAddressAccept) string { - return addr - }, - } - - if agentConfig != nil { - cfg.AgentConfig = agentConfig - } - - return cfg -} - -// TestDNS_BinaryTruncate tests the dnsBinaryTruncate function. -func TestDNS_BinaryTruncate(t *testing.T) { - msgSrc := new(dns.Msg) - msgSrc.Compress = true - msgSrc.SetQuestion("redis.service.consul.", dns.TypeSRV) - - for i := 0; i < 5000; i++ { - target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", i/256, i%256) - msgSrc.Answer = append(msgSrc.Answer, &dns.SRV{Hdr: dns.RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: dns.TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target}) - msgSrc.Extra = append(msgSrc.Extra, &dns.CNAME{Hdr: dns.RR_Header{Name: target, Class: 1, Rrtype: dns.TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", i/256, i%256)}) - } - for _, compress := range []bool{true, false} { - for idx, maxSize := range []int{12, 256, 512, 8192, 65535} { - t.Run(fmt.Sprintf("binarySearch %d", maxSize), func(t *testing.T) { - msg := new(dns.Msg) - msgSrc.Compress = compress - msgSrc.SetQuestion("redis.service.consul.", dns.TypeSRV) - msg.Answer = msgSrc.Answer - msg.Extra = msgSrc.Extra - msg.Ns = msgSrc.Ns - index := make(map[string]dns.RR, len(msg.Extra)) - indexRRs(msg.Extra, index) - blen := dnsBinaryTruncate(msg, maxSize, index, true) - msg.Answer = msg.Answer[:blen] - syncExtra(index, msg) - predicted := msg.Len() - buf, err := msg.Pack() - if err != nil { - t.Error(err) - } - if predicted < len(buf) { - t.Fatalf("Bug in DNS library: %d != %d", predicted, len(buf)) - } - if len(buf) > maxSize || (idx != 0 && len(buf) < 16) { - t.Fatalf("bad[%d]: %d > %d", idx, len(buf), maxSize) - } - }) - } - } -} - -// TestDNS_syncExtra tests the syncExtra function. -func TestDNS_syncExtra(t *testing.T) { - resp := &dns.Msg{ - Answer: []dns.RR{ - // These two are on the same host so the redundant extra - // records should get deduplicated. - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Port: 1001, - Target: "ip-10-0-1-185.node.dc1.consul.", - }, - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Port: 1002, - Target: "ip-10-0-1-185.node.dc1.consul.", - }, - // This one isn't in the Consul domain so it will get a - // CNAME and then an A record from the recursor. - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Port: 1003, - Target: "demo.consul.io.", - }, - // This one isn't in the Consul domain and it will get - // a CNAME and A record from a recursor that alters the - // case of the name. This proves we look up in the index - // in a case-insensitive way. - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Port: 1001, - Target: "insensitive.consul.io.", - }, - // This is also a CNAME, but it'll be set up to loop to - // make sure we don't crash. - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Port: 1001, - Target: "deadly.consul.io.", - }, - // This is also a CNAME, but it won't have another record. - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Port: 1001, - Target: "nope.consul.io.", - }, - }, - Extra: []dns.RR{ - // These should get deduplicated. - &dns.A{ - Hdr: dns.RR_Header{ - Name: "ip-10-0-1-185.node.dc1.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("10.0.1.185"), - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "ip-10-0-1-185.node.dc1.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("10.0.1.185"), - }, - // This is a normal CNAME followed by an A record but we - // have flipped the order. The algorithm should emit them - // in the opposite order. - &dns.A{ - Hdr: dns.RR_Header{ - Name: "fakeserver.consul.io.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("127.0.0.1"), - }, - &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: "demo.consul.io.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - }, - Target: "fakeserver.consul.io.", - }, - // These differ in case to test case insensitivity. - &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: "INSENSITIVE.CONSUL.IO.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - }, - Target: "Another.Server.Com.", - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "another.server.com.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("127.0.0.1"), - }, - // This doesn't appear in the answer, so should get - // dropped. - &dns.A{ - Hdr: dns.RR_Header{ - Name: "ip-10-0-1-186.node.dc1.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("10.0.1.186"), - }, - // These two test edge cases with CNAME handling. - &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: "deadly.consul.io.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - }, - Target: "deadly.consul.io.", - }, - &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: "nope.consul.io.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - }, - Target: "notthere.consul.io.", - }, - }, - } - - index := make(map[string]dns.RR) - indexRRs(resp.Extra, index) - syncExtra(index, resp) - - expected := &dns.Msg{ - Answer: resp.Answer, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "ip-10-0-1-185.node.dc1.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("10.0.1.185"), - }, - &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: "demo.consul.io.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - }, - Target: "fakeserver.consul.io.", - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "fakeserver.consul.io.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("127.0.0.1"), - }, - &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: "INSENSITIVE.CONSUL.IO.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - }, - Target: "Another.Server.Com.", - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "another.server.com.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("127.0.0.1"), - }, - &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: "deadly.consul.io.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - }, - Target: "deadly.consul.io.", - }, - &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: "nope.consul.io.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - }, - Target: "notthere.consul.io.", - }, - }, - } - if !reflect.DeepEqual(resp, expected) { - t.Fatalf("Bad %#v vs. %#v", *resp, *expected) - } -} - -// getUint32Ptr return the pointer of an uint32 literal -func getUint32Ptr(i uint32) *uint32 { - return &i -} - -func TestRouter_ReloadConfig(t *testing.T) { - cdf := discovery.NewMockCatalogDataFetcher(t) - cfg := buildDNSConfig(nil, cdf, nil) - router, err := NewRouter(cfg) - require.NoError(t, err) - - router.recursor = newMockDnsRecursor(t) - - // Reload the config - newAgentConfig := &config.RuntimeConfig{ - DNSARecordLimit: 123, - DNSEnableTruncate: true, - DNSNodeTTL: 234, - DNSRecursorStrategy: "strategy-123", - DNSRecursorTimeout: 345, - DNSUDPAnswerLimit: 456, - DNSNodeMetaTXT: true, - DNSDisableCompression: true, - DNSSOA: config.RuntimeSOAConfig{ - Expire: 123, - Minttl: 234, - Refresh: 345, - Retry: 456, - }, - DNSServiceTTL: map[string]time.Duration{ - "wildcard-config-*": 123, - "strict-config": 234, - }, - DNSRecursors: []string{ - "8.8.8.8", - "2001:4860:4860::8888", - }, - } - - expectTTLRadix := radix.New() - expectTTLRadix.Insert("wildcard-config-", time.Duration(123)) - - expectedCfg := &RouterDynamicConfig{ - ARecordLimit: 123, - EnableTruncate: true, - NodeTTL: 234, - RecursorStrategy: "strategy-123", - RecursorTimeout: 345, - UDPAnswerLimit: 456, - NodeMetaTXT: true, - DisableCompression: true, - SOAConfig: SOAConfig{ - Expire: 123, - Minttl: 234, - Refresh: 345, - Retry: 456, - }, - TTLRadix: expectTTLRadix, - TTLStrict: map[string]time.Duration{ - "strict-config": 234, - }, - Recursors: []string{ - "8.8.8.8:53", - "[2001:4860:4860::8888]:53", - }, - } - err = router.ReloadConfig(newAgentConfig) - require.NoError(t, err) - savedCfg := router.dynamicConfig.Load().(*RouterDynamicConfig) - - // Ensure the new config is used - require.Equal(t, expectedCfg, savedCfg) -} - -func Test_isPTRSubdomain(t *testing.T) { - testCases := []struct { - name string - domain string - expected bool - }{ - { - name: "empty domain returns false", - domain: "", - expected: false, - }, - { - name: "last label is 'arpa' returns true", - domain: "my-addr.arpa.", - expected: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - actual := isPTRSubdomain(tc.domain) - require.Equal(t, tc.expected, actual) - }) - } -} - -func Test_isAddrSubdomain(t *testing.T) { - testCases := []struct { - name string - domain string - expected bool - }{ - { - name: "empty domain returns false", - domain: "", - expected: false, - }, - { - name: "'c000020a.addr.dc1.consul.' returns true", - domain: "c000020a.addr.dc1.consul.", - expected: true, - }, - { - name: "'c000020a.addr.consul.' returns true", - domain: "c000020a.addr.consul.", - expected: true, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - actual := isAddrSubdomain(tc.domain) - require.Equal(t, tc.expected, actual) - }) - } -} - -func Test_stripAnyFailoverSuffix(t *testing.T) { - testCases := []struct { - name string - target string - expectedEnableFailover bool - expectedResult string - }{ - { - name: "my-addr.service.dc1.consul.failover. returns 'my-addr.service.dc1.consul' and true", - target: "my-addr.service.dc1.consul.failover.", - expectedEnableFailover: true, - expectedResult: "my-addr.service.dc1.consul.", - }, - { - name: "my-addr.service.dc1.consul.no-failover. returns 'my-addr.service.dc1.consul' and false", - target: "my-addr.service.dc1.consul.no-failover.", - expectedEnableFailover: false, - expectedResult: "my-addr.service.dc1.consul.", - }, - { - name: "my-addr.service.dc1.consul. returns 'my-addr.service.dc1.consul' and false", - target: "my-addr.service.dc1.consul.", - expectedEnableFailover: false, - expectedResult: "my-addr.service.dc1.consul.", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - actual, actualEnableFailover := stripAnyFailoverSuffix(tc.target) - require.Equal(t, tc.expectedEnableFailover, actualEnableFailover) - require.Equal(t, tc.expectedResult, actual) - }) - } -} - -func Test_trimDomain(t *testing.T) { - testCases := []struct { - name string - domain string - altDomain string - questionName string - expectedResult string - }{ - { - name: "given domain is 'consul.' and altDomain is 'my.consul.', when calling trimDomain with 'my-service.my.consul.', it returns 'my-service.'", - questionName: "my-service.my.consul.", - domain: "consul.", - altDomain: "my.consul.", - expectedResult: "my-service.", - }, - { - name: "given domain is 'consul.' and altDomain is 'my.consul.', when calling trimDomain with 'my-service.consul.', it returns 'my-service.'", - questionName: "my-service.consul.", - domain: "consul.", - altDomain: "my.consul.", - expectedResult: "my-service.", - }, - { - name: "given domain is 'consul.' and altDomain is 'my-consul.', when calling trimDomain with 'my-service.consul.', it returns 'my-service.'", - questionName: "my-service.consul.", - domain: "consul.", - altDomain: "my-consul.", - expectedResult: "my-service.", - }, - { - name: "given domain is 'consul.' and altDomain is 'my-consul.', when calling trimDomain with 'my-service.my-consul.', it returns 'my-service.'", - questionName: "my-service.my-consul.", - domain: "consul.", - altDomain: "my-consul.", - expectedResult: "my-service.", - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - router := Router{ - domain: tc.domain, - altDomain: tc.altDomain, - } - actual := router.trimDomain(tc.questionName) - require.Equal(t, tc.expectedResult, actual) - }) - } -} diff --git a/agent/dns/router_v2_services_test.go b/agent/dns/router_v2_services_test.go deleted file mode 100644 index 45cb98b615..0000000000 --- a/agent/dns/router_v2_services_test.go +++ /dev/null @@ -1,628 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "net" - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/agent/discovery" - "github.com/hashicorp/consul/internal/resource" -) - -func Test_HandleRequest_V2Services(t *testing.T) { - testCases := []HandleTestCase{ - { - name: "A/AAAA Query a service and return multiple A records", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.service.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - results := []*discovery.Result{ - { - Node: &discovery.Location{Name: "foo-1", Address: "10.0.0.1"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - Ports: []discovery.Port{ - { - Name: "api", - Number: 5678, - }, - // Intentionally not in the mesh - }, - DNS: discovery.DNSConfig{ - Weight: 2, - }, - }, - { - Node: &discovery.Location{Name: "foo-2", Address: "10.0.0.2"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - Ports: []discovery.Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "mesh", - Number: 21000, - }, - }, - DNS: discovery.DNSConfig{ - Weight: 3, - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). - Return(results, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - reqType := args.Get(2).(discovery.LookupType) - - require.Equal(t, "foo", req.Name) - require.Empty(t, req.PortName) - require.Equal(t, discovery.LookupTypeService, reqType) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "foo.service.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.service.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - A: net.ParseIP("10.0.0.1"), - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.service.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - A: net.ParseIP("10.0.0.2"), - }, - }, - }, - }, - { - name: "SRV Query with a multi-port service return multiple SRV records", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.service.consul.", - Qtype: dns.TypeSRV, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - results := []*discovery.Result{ - { - Node: &discovery.Location{Name: "foo-1", Address: "10.0.0.1"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - Ports: []discovery.Port{ - { - Name: "api", - Number: 5678, - }, - // Intentionally not in the mesh - }, - DNS: discovery.DNSConfig{ - Weight: 2, - }, - }, - { - Node: &discovery.Location{Name: "foo-2", Address: "10.0.0.2"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - Ports: []discovery.Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "mesh", - Number: 21000, - }, - }, - DNS: discovery.DNSConfig{ - Weight: 3, - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). - Return(results, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - reqType := args.Get(2).(discovery.LookupType) - - require.Equal(t, "foo", req.Name) - require.Empty(t, req.PortName) - require.Equal(t, discovery.LookupTypeService, reqType) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "foo.service.consul.", - Qtype: dns.TypeSRV, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "foo.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - Weight: 2, - Priority: 1, - Port: 5678, - Target: "api.port.foo-1.workload.default.ns.default.ap.consul.", - }, - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "foo.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - Weight: 3, - Priority: 1, - Port: 5678, - Target: "api.port.foo-2.workload.default.ns.default.ap.consul.", - }, - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "foo.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - Weight: 3, - Priority: 1, - Port: 21000, - Target: "mesh.port.foo-2.workload.default.ns.default.ap.consul.", - }, - }, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "api.port.foo-1.workload.default.ns.default.ap.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - A: net.ParseIP("10.0.0.1"), - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "api.port.foo-2.workload.default.ns.default.ap.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - A: net.ParseIP("10.0.0.2"), - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "mesh.port.foo-2.workload.default.ns.default.ap.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - A: net.ParseIP("10.0.0.2"), - }, - }, - }, - }, - { - name: "SRV Query with a multi-port service where the client requests a specific port, returns SRV and A records", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "mesh.port.foo.service.consul.", - Qtype: dns.TypeSRV, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - results := []*discovery.Result{ - { - Node: &discovery.Location{Name: "foo-2", Address: "10.0.0.2"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - Ports: []discovery.Port{ - { - Name: "mesh", - Number: 21000, - }, - }, - DNS: discovery.DNSConfig{ - Weight: 3, - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). - Return(results, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - reqType := args.Get(2).(discovery.LookupType) - - require.Equal(t, "foo", req.Name) - require.Equal(t, "mesh", req.PortName) - require.Equal(t, discovery.LookupTypeService, reqType) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "mesh.port.foo.service.consul.", - Qtype: dns.TypeSRV, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "mesh.port.foo.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - Weight: 3, - Priority: 1, - Port: 21000, - Target: "mesh.port.foo-2.workload.default.ns.default.ap.consul.", - }, - }, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "mesh.port.foo-2.workload.default.ns.default.ap.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - A: net.ParseIP("10.0.0.2"), - }, - }, - }, - }, - { - name: "SRV Query with a multi-port service that has workloads w/ hostnames (no recursors)", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.service.consul.", - Qtype: dns.TypeSRV, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - results := []*discovery.Result{ - { - Node: &discovery.Location{Name: "foo-1", Address: "foo-1.example.com"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - Ports: []discovery.Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "web", - Number: 8080, - }, - }, - DNS: discovery.DNSConfig{ - Weight: 2, - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). - Return(results, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - reqType := args.Get(2).(discovery.LookupType) - - require.Equal(t, "foo", req.Name) - require.Empty(t, req.PortName) - require.Equal(t, discovery.LookupTypeService, reqType) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "foo.service.consul.", - Qtype: dns.TypeSRV, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "foo.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - Weight: 2, - Priority: 1, - Port: 5678, - Target: "foo-1.example.com.", - }, - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "foo.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - Weight: 2, - Priority: 1, - Port: 8080, - Target: "foo-1.example.com.", - }, - }, - }, - }, - { - name: "SRV Query with a multi-port service that has workloads w/ hostnames (no recursor)", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.service.consul.", - Qtype: dns.TypeSRV, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - results := []*discovery.Result{ - { - Node: &discovery.Location{Name: "foo-1", Address: "foo-1.example.com"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: resource.DefaultNamespaceName, - Partition: resource.DefaultPartitionName, - }, - Ports: []discovery.Port{ - { - Name: "api", - Number: 5678, - }, - { - Name: "web", - Number: 8080, - }, - }, - DNS: discovery.DNSConfig{ - Weight: 2, - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchEndpoints", mock.Anything, mock.Anything, mock.Anything). - Return(results, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - reqType := args.Get(2).(discovery.LookupType) - - require.Equal(t, "foo", req.Name) - require.Empty(t, req.PortName) - require.Equal(t, discovery.LookupTypeService, reqType) - }) - }, - agentConfig: &config.RuntimeConfig{ - DNSRecursors: []string{"8.8.8.8"}, - DNSDomain: "consul", - DNSNodeTTL: 123 * time.Second, - DNSSOA: config.RuntimeSOAConfig{ - Refresh: 1, - Retry: 2, - Expire: 3, - Minttl: 4, - }, - DNSUDPAnswerLimit: maxUDPAnswerLimit, - }, - configureRecursor: func(recursor dnsRecursor) { - resp := &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: "foo-1.example.com.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo-1.example.com.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - } - recursor.(*mockDnsRecursor).On("handle", - mock.Anything, mock.Anything, mock.Anything).Return(resp, nil) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - RecursionAvailable: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "foo.service.consul.", - Qtype: dns.TypeSRV, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "foo.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - Weight: 2, - Priority: 1, - Port: 5678, - Target: "foo-1.example.com.", - }, - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "foo.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - Weight: 2, - Priority: 1, - Port: 8080, - Target: "foo-1.example.com.", - }, - }, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo-1.example.com.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - A: net.ParseIP("1.2.3.4"), - }, - // TODO (v2-dns): This needs to be de-duplicated (NET-8064) - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo-1.example.com.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: uint32(123), - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runHandleTestCases(t, tc) - }) - } -} diff --git a/agent/dns/router_virtual_test.go b/agent/dns/router_virtual_test.go deleted file mode 100644 index 5d70425e0e..0000000000 --- a/agent/dns/router_virtual_test.go +++ /dev/null @@ -1,122 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "github.com/hashicorp/consul/agent/discovery" - "github.com/miekg/dns" - "github.com/stretchr/testify/mock" - "net" - "testing" -) - -func Test_HandleRequest_Virtual(t *testing.T) { - testCases := []HandleTestCase{ - { - name: "test A 'virtual.' query, ipv4 response", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "c000020a.virtual.dc1.consul", // "intentionally missing the trailing dot" - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - fetcher.(*discovery.MockCatalogDataFetcher).On("FetchVirtualIP", - mock.Anything, mock.Anything).Return(&discovery.Result{ - Node: &discovery.Location{Address: "240.0.0.2"}, - Type: discovery.ResultTypeVirtual, - }, nil) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "c000020a.virtual.dc1.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "c000020a.virtual.dc1.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("240.0.0.2"), - }, - }, - }, - }, - { - name: "test A 'virtual.' query, ipv6 response", - // Since we asked for an A record, the AAAA record that resolves from the address is attached as an extra - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "20010db800010002cafe000000001337.virtual.dc1.consul", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - fetcher.(*discovery.MockCatalogDataFetcher).On("FetchVirtualIP", - mock.Anything, mock.Anything).Return(&discovery.Result{ - Node: &discovery.Location{Address: "2001:db8:1:2:cafe::1337"}, - Type: discovery.ResultTypeVirtual, - }, nil) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "20010db800010002cafe000000001337.virtual.dc1.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Extra: []dns.RR{ - &dns.AAAA{ - Hdr: dns.RR_Header{ - Name: "20010db800010002cafe000000001337.virtual.dc1.consul.", - Rrtype: dns.TypeAAAA, - Class: dns.ClassINET, - Ttl: 123, - }, - AAAA: net.ParseIP("2001:db8:1:2:cafe::1337"), - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runHandleTestCases(t, tc) - }) - } -} diff --git a/agent/dns/router_workload_test.go b/agent/dns/router_workload_test.go deleted file mode 100644 index 04170a495c..0000000000 --- a/agent/dns/router_workload_test.go +++ /dev/null @@ -1,515 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "net" - "testing" - "time" - - "github.com/miekg/dns" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/agent/discovery" -) - -func Test_HandleRequest_workloads(t *testing.T) { - testCases := []HandleTestCase{ - { - name: "workload A query w/ port, returns A record", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "api.port.foo.workload.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - result := &discovery.Result{ - Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{}, - Ports: []discovery.Port{ - { - Name: "api", - Number: 5678, - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchWorkload", mock.Anything, mock.Anything). - Return(result, nil). //TODO - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - - require.Equal(t, "foo", req.Name) - require.Equal(t, "api", req.PortName) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "api.port.foo.workload.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "api.port.foo.workload.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - { - name: "workload ANY query w/o port, returns A record", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.workload.consul.", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - result := &discovery.Result{ - Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{}, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchWorkload", mock.Anything, mock.Anything). - Return(result, nil). //TODO - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - - require.Equal(t, "foo", req.Name) - require.Empty(t, req.PortName) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "foo.workload.consul.", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.workload.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - { - name: "workload A query with namespace, partition, and cluster id; IPV4 address; returns A record", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "foo.workload.bar.ns.baz.ap.dc3.dc.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - result := &discovery.Result{ - Node: &discovery.Location{Name: "foo", Address: "1.2.3.4"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{ - Namespace: "bar", - Partition: "baz", - // We currently don't set the datacenter in any of the V2 results. - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchWorkload", mock.Anything, mock.Anything). - Return(result, nil). - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - - require.Equal(t, "foo", req.Name) - require.Empty(t, req.PortName) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "foo.workload.bar.ns.baz.ap.dc3.dc.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.workload.bar.ns.baz.ap.dc3.dc.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - { - name: "workload w/hostname address, ANY query (no recursor)", - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "api.port.foo.workload.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - result := &discovery.Result{ - Node: &discovery.Location{Name: "foo", Address: "foo.example.com"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{}, - Ports: []discovery.Port{ - { - Name: "api", - Number: 5678, - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchWorkload", mock.Anything, mock.Anything). - Return(result, nil). //TODO - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - - require.Equal(t, "foo", req.Name) - require.Equal(t, "api", req.PortName) - }) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "api.port.foo.workload.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: "api.port.foo.workload.consul.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - Ttl: 123, - }, - Target: "foo.example.com.", - }, - }, - }, - }, - { - name: "workload w/hostname address, ANY query (w/ recursor)", - // https://datatracker.ietf.org/doc/html/rfc1034#section-3.6.2 both the CNAME and the A record should be in the answer - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "api.port.foo.workload.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - result := &discovery.Result{ - Node: &discovery.Location{Name: "foo", Address: "foo.example.com"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{}, - Ports: []discovery.Port{ - { - Name: "api", - Number: 5678, - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchWorkload", mock.Anything, mock.Anything). - Return(result, nil). //TODO - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - - require.Equal(t, "foo", req.Name) - require.Equal(t, "api", req.PortName) - }) - }, - agentConfig: &config.RuntimeConfig{ - DNSRecursors: []string{"8.8.8.8"}, - DNSDomain: "consul", - DNSNodeTTL: 123 * time.Second, - DNSSOA: config.RuntimeSOAConfig{ - Refresh: 1, - Retry: 2, - Expire: 3, - Minttl: 4, - }, - DNSUDPAnswerLimit: maxUDPAnswerLimit, - }, - configureRecursor: func(recursor dnsRecursor) { - resp := &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: "foo.example.com.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.example.com.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - } - recursor.(*mockDnsRecursor).On("handle", - mock.Anything, mock.Anything, mock.Anything).Return(resp, nil) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - RecursionAvailable: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "api.port.foo.workload.consul.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: "api.port.foo.workload.consul.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - Ttl: 123, - }, - Target: "foo.example.com.", - }, - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.example.com.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - { - name: "workload w/hostname address, CNAME query (w/ recursor)", - // https://datatracker.ietf.org/doc/html/rfc1034#section-3.6.2 only the CNAME should be in the answer - request: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - }, - Question: []dns.Question{ - { - Name: "api.port.foo.workload.consul.", - Qtype: dns.TypeCNAME, - Qclass: dns.ClassINET, - }, - }, - }, - configureDataFetcher: func(fetcher discovery.CatalogDataFetcher) { - result := &discovery.Result{ - Node: &discovery.Location{Name: "foo", Address: "foo.example.com"}, - Type: discovery.ResultTypeWorkload, - Tenancy: discovery.ResultTenancy{}, - Ports: []discovery.Port{ - { - Name: "api", - Number: 5678, - }, - }, - } - - fetcher.(*discovery.MockCatalogDataFetcher). - On("FetchWorkload", mock.Anything, mock.Anything). - Return(result, nil). //TODO - Run(func(args mock.Arguments) { - req := args.Get(1).(*discovery.QueryPayload) - - require.Equal(t, "foo", req.Name) - require.Equal(t, "api", req.PortName) - }) - }, - agentConfig: &config.RuntimeConfig{ - DNSRecursors: []string{"8.8.8.8"}, - DNSDomain: "consul", - DNSNodeTTL: 123 * time.Second, - DNSSOA: config.RuntimeSOAConfig{ - Refresh: 1, - Retry: 2, - Expire: 3, - Minttl: 4, - }, - DNSUDPAnswerLimit: maxUDPAnswerLimit, - }, - configureRecursor: func(recursor dnsRecursor) { - resp := &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - Rcode: dns.RcodeSuccess, - }, - Question: []dns.Question{ - { - Name: "foo.example.com.", - Qtype: dns.TypeA, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.example.com.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - } - recursor.(*mockDnsRecursor).On("handle", - mock.Anything, mock.Anything, mock.Anything).Return(resp, nil) - }, - validateAndNormalizeExpected: true, - response: &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - RecursionAvailable: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "api.port.foo.workload.consul.", - Qtype: dns.TypeCNAME, - Qclass: dns.ClassINET, - }, - }, - Answer: []dns.RR{ - &dns.CNAME{ - Hdr: dns.RR_Header{ - Name: "api.port.foo.workload.consul.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - Ttl: 123, - }, - Target: "foo.example.com.", - }, - // TODO (v2-dns): this next record is wrong per the RFC-1034 mentioned in the comment above (NET-8060) - &dns.A{ - Hdr: dns.RR_Header{ - Name: "foo.example.com.", - Rrtype: dns.TypeCNAME, - Class: dns.ClassINET, - Ttl: 123, - }, - A: net.ParseIP("1.2.3.4"), - }, - }, - }, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - runHandleTestCases(t, tc) - }) - } -} diff --git a/agent/dns/server.go b/agent/dns/server.go deleted file mode 100644 index 764fb15980..0000000000 --- a/agent/dns/server.go +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "fmt" - "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/internal/dnsutil" - "net" - - "github.com/miekg/dns" - - "github.com/hashicorp/go-hclog" - - "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/logging" -) - -// DNSRouter is a mock for Router that can be used for testing. -// -//go:generate mockery --name DNSRouter --inpackage -type DNSRouter interface { - HandleRequest(req *dns.Msg, reqCtx Context, remoteAddress net.Addr) *dns.Msg - ServeDNS(w dns.ResponseWriter, req *dns.Msg) - GetConfig() *RouterDynamicConfig - ReloadConfig(newCfg *config.RuntimeConfig) error -} - -// Server is used to expose service discovery queries using a DNS interface. -// It implements the agent.dnsServer interface. -type Server struct { - *dns.Server // Used for setting up listeners - Router DNSRouter // Used to routes and parse DNS requests - - logger hclog.Logger -} - -// Config represent all the DNS configuration required to construct a DNS server. -type Config struct { - AgentConfig *config.RuntimeConfig - EntMeta acl.EnterpriseMeta - Logger hclog.Logger - Processor DiscoveryQueryProcessor - TokenFunc func() string - TranslateAddressFunc func(dc string, addr string, taggedAddresses map[string]string, accept dnsutil.TranslateAddressAccept) string - TranslateServiceAddressFunc func(dc string, address string, taggedAddresses map[string]structs.ServiceAddress, accept dnsutil.TranslateAddressAccept) string -} - -// NewServer creates a new DNS server. -func NewServer(config Config) (*Server, error) { - router, err := NewRouter(config) - if err != nil { - return nil, fmt.Errorf("error creating DNS router: %w", err) - } - - srv := &Server{ - Router: router, - logger: config.Logger.Named(logging.DNS), - } - return srv, nil -} - -// ListenAndServe starts the DNS server. -func (d *Server) ListenAndServe(network, addr string, notif func()) error { - d.Server = &dns.Server{ - Addr: addr, - Net: network, - Handler: d.Router, - NotifyStartedFunc: notif, - } - if network == "udp" { - d.UDPSize = 65535 - } - return d.Server.ListenAndServe() -} - -// ReloadConfig hot-reloads the server config with new parameters under config.RuntimeConfig.DNS* -func (d *Server) ReloadConfig(newCfg *config.RuntimeConfig) error { - return d.Router.ReloadConfig(newCfg) -} - -// Shutdown stops the DNS server. -func (d *Server) Shutdown() { - if d.Server != nil { - d.logger.Info("Stopping server", - "protocol", "DNS", - "address", d.Server.Addr, - "network", d.Server.Net, - ) - err := d.Server.Shutdown() - if err != nil { - d.logger.Error("Error stopping DNS server", "error", err) - } - } - d.Router = nil -} - -// GetAddr is a function to return the server address if is not nil. -func (d *Server) GetAddr() string { - if d.Server != nil { - return d.Server.Addr - } - return "" -} diff --git a/agent/dns/server_test.go b/agent/dns/server_test.go deleted file mode 100644 index 7ede22efda..0000000000 --- a/agent/dns/server_test.go +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "github.com/hashicorp/consul/agent/config" - "github.com/hashicorp/consul/sdk/testutil" - "github.com/stretchr/testify/require" - "testing" -) - -// TestServer_ReloadConfig tests that the ReloadConfig method calls the router's ReloadConfig method. -func TestDNSServer_ReloadConfig(t *testing.T) { - srv, err := NewServer(Config{ - AgentConfig: &config.RuntimeConfig{ - DNSDomain: "test-domain", - DNSAltDomain: "test-alt-domain", - }, - Logger: testutil.Logger(t), - }) - srv.Router = NewMockDNSRouter(t) - require.NoError(t, err) - cfg := &config.RuntimeConfig{ - DNSARecordLimit: 123, - DNSEnableTruncate: true, - DNSNodeTTL: 123, - DNSRecursorStrategy: "test", - DNSRecursorTimeout: 123, - DNSUDPAnswerLimit: 123, - DNSNodeMetaTXT: true, - DNSDisableCompression: true, - DNSSOA: config.RuntimeSOAConfig{ - Expire: 123, - Refresh: 123, - Retry: 123, - Minttl: 123, - }, - } - srv.Router.(*MockDNSRouter).On("ReloadConfig", cfg).Return(nil) - err = srv.ReloadConfig(cfg) - require.NoError(t, err) - require.True(t, srv.Router.(*MockDNSRouter).AssertExpectations(t)) -} - -// TestDNSServer_Lifecycle tests that the server can be started and shutdown. -func TestDNSServer_Lifecycle(t *testing.T) { - // Arrange - srv, err := NewServer(Config{ - AgentConfig: &config.RuntimeConfig{ - DNSDomain: "test-domain", - DNSAltDomain: "test-alt-domain", - }, - Logger: testutil.Logger(t), - }) - defer srv.Shutdown() - require.NotNil(t, srv.Router) - require.NoError(t, err) - require.NotNil(t, srv) - - ch := make(chan bool) - go func() { - err = srv.ListenAndServe("udp", "127.0.0.1:8500", func() { - ch <- true - }) - require.NoError(t, err) - }() - started, ok := <-ch - require.True(t, ok) - require.True(t, started) - require.NotNil(t, srv.Handler) - require.NotNil(t, srv.Handler.(*Router)) - require.NotNil(t, srv.PacketConn) - - //Shutdown - srv.Shutdown() - require.Nil(t, srv.Router) -} diff --git a/agent/dns_catalogv2_test.go b/agent/dns_catalogv2_test.go deleted file mode 100644 index 7aef88751c..0000000000 --- a/agent/dns_catalogv2_test.go +++ /dev/null @@ -1,516 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package agent - -import ( - "context" - "fmt" - "net" - "testing" - - "github.com/miekg/dns" - "github.com/stretchr/testify/require" - "google.golang.org/protobuf/proto" - "google.golang.org/protobuf/types/known/anypb" - - "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/internal/resource" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/sdk/testutil/retry" - "github.com/hashicorp/consul/testrpc" -) - -// Similar to TestDNS_ServiceLookup, but removes config for features unsupported in v2 and -// tests against DNS v2 and Catalog v2 explicitly using a resource API client. -func TestDNS_CatalogV2_Basic(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - var err error - a := NewTestAgent(t, `experiments=["resource-apis"]`) // v2dns is implicit w/ resource-apis - defer a.Shutdown() - - testrpc.WaitForRaftLeader(t, a.RPC, "dc1") - - client := a.delegate.ResourceServiceClient() - - // Smoke test for `consul-server` service. - readResource(t, client, &pbresource.ID{ - Name: structs.ConsulServiceName, - Type: pbcatalog.ServiceType, - Tenancy: resource.DefaultNamespacedTenancy(), - }, new(pbcatalog.Service)) - - // Register a new service. - dbServiceId := &pbresource.ID{ - Name: "db", - Type: pbcatalog.ServiceType, - Tenancy: resource.DefaultNamespacedTenancy(), - } - emptyServiceId := &pbresource.ID{ - Name: "empty", - Type: pbcatalog.ServiceType, - Tenancy: resource.DefaultNamespacedTenancy(), - } - dbService := &pbcatalog.Service{ - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"db-"}, - }, - Ports: []*pbcatalog.ServicePort{ - { - TargetPort: "tcp", - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - { - TargetPort: "admin", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - } - emptyService := &pbcatalog.Service{ - Workloads: &pbcatalog.WorkloadSelector{ - Prefixes: []string{"empty-"}, - }, - Ports: []*pbcatalog.ServicePort{ - { - TargetPort: "tcp", - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - { - TargetPort: "admin", - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - { - TargetPort: "mesh", - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - }, - } - dbServiceResource := &pbresource.Resource{ - Id: dbServiceId, - Data: toAny(t, dbService), - } - emptyServiceResource := &pbresource.Resource{ - Id: emptyServiceId, - Data: toAny(t, emptyService), - } - for _, r := range []*pbresource.Resource{dbServiceResource, emptyServiceResource} { - _, err := client.Write(context.Background(), &pbresource.WriteRequest{Resource: r}) - if err != nil { - t.Fatalf("failed to create the %s service: %v", r.Id.Name, err) - } - } - - // Validate services written. - readResource(t, client, dbServiceId, new(pbcatalog.Service)) - readResource(t, client, emptyServiceId, new(pbcatalog.Service)) - - // Register workloads. - dbWorkloadId1 := &pbresource.ID{ - Name: "db-1", - Type: pbcatalog.WorkloadType, - Tenancy: resource.DefaultNamespacedTenancy(), - } - dbWorkloadId2 := &pbresource.ID{ - Name: "db-2", - Type: pbcatalog.WorkloadType, - Tenancy: resource.DefaultNamespacedTenancy(), - } - dbWorkloadId3 := &pbresource.ID{ - Name: "db-3", - Type: pbcatalog.WorkloadType, - Tenancy: resource.DefaultNamespacedTenancy(), - } - dbWorkloadPorts := map[string]*pbcatalog.WorkloadPort{ - "tcp": { - Port: 12345, - Protocol: pbcatalog.Protocol_PROTOCOL_TCP, - }, - "admin": { - Port: 23456, - Protocol: pbcatalog.Protocol_PROTOCOL_HTTP, - }, - "mesh": { - Port: 20000, - Protocol: pbcatalog.Protocol_PROTOCOL_MESH, - }, - } - dbWorkloadFn := func(ip string) *pbcatalog.Workload { - return &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - { - Host: ip, - }, - }, - Identity: "test-identity", - Ports: dbWorkloadPorts, - } - } - dbWorkload1 := dbWorkloadFn("172.16.1.1") - _, err = client.Write(context.Background(), &pbresource.WriteRequest{Resource: &pbresource.Resource{ - Id: dbWorkloadId1, - Data: toAny(t, dbWorkload1), - }}) - if err != nil { - t.Fatalf("failed to create the %s workload: %v", dbWorkloadId1.Name, err) - } - dbWorkload2 := dbWorkloadFn("172.16.1.2") - _, err = client.Write(context.Background(), &pbresource.WriteRequest{Resource: &pbresource.Resource{ - Id: dbWorkloadId2, - Data: toAny(t, dbWorkload2), - }}) - if err != nil { - t.Fatalf("failed to create the %s workload: %v", dbWorkloadId2.Name, err) - } - dbWorkload3 := dbWorkloadFn("2001:db8:85a3::8a2e:370:7334") // test IPv6 - _, err = client.Write(context.Background(), &pbresource.WriteRequest{Resource: &pbresource.Resource{ - Id: dbWorkloadId3, - Data: toAny(t, dbWorkload3), - }}) - if err != nil { - t.Fatalf("failed to create the %s workload: %v", dbWorkloadId2.Name, err) - } - - // Validate workloads written. - dbWorkloads := make(map[string]*pbcatalog.Workload) - dbWorkloads["db-1"] = readResource(t, client, dbWorkloadId1, new(pbcatalog.Workload)).(*pbcatalog.Workload) - dbWorkloads["db-2"] = readResource(t, client, dbWorkloadId2, new(pbcatalog.Workload)).(*pbcatalog.Workload) - dbWorkloads["db-3"] = readResource(t, client, dbWorkloadId3, new(pbcatalog.Workload)).(*pbcatalog.Workload) - - // Ensure endpoints exist and have health status, which is required for inclusion in DNS results. - retry.Run(t, func(r *retry.R) { - endpoints := readResource(r, client, resource.ReplaceType(pbcatalog.ServiceEndpointsType, dbServiceId), new(pbcatalog.ServiceEndpoints)).(*pbcatalog.ServiceEndpoints) - require.Equal(r, 3, len(endpoints.GetEndpoints())) - for _, e := range endpoints.GetEndpoints() { - require.True(r, - // We only return results for passing and warning health checks. - e.HealthStatus == pbcatalog.Health_HEALTH_PASSING || e.HealthStatus == pbcatalog.Health_HEALTH_WARNING, - fmt.Sprintf("unexpected health status: %v", e.HealthStatus)) - } - }) - - // Test UDP and TCP clients. - for _, client := range []*dns.Client{ - newDNSClient(false), - newDNSClient(true), - } { - // Lookup a service without matching workloads, we should receive an SOA and no answers. - questions := []string{ - "empty.service.consul.", - "_empty._tcp.service.consul.", - } - for _, question := range questions { - for _, dnsType := range []uint16{dns.TypeSRV, dns.TypeA, dns.TypeAAAA} { - m := new(dns.Msg) - m.SetQuestion(question, dnsType) - - in, _, err := client.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - require.Equal(t, 0, len(in.Answer), "Bad: %s", in.String()) - require.Equal(t, 0, len(in.Extra), "Bad: %s", in.String()) - require.Equal(t, 1, len(in.Ns), "Bad: %s", in.String()) - - soaRec, ok := in.Ns[0].(*dns.SOA) - require.True(t, ok, "Bad: %s", in.Ns[0].String()) - require.EqualValues(t, 0, soaRec.Hdr.Ttl, "Bad: %s", in.Ns[0].String()) - } - } - - // Look up the service directly including all ports. - questions = []string{ - "db.service.consul.", - "_db._tcp.service.consul.", // RFC 2782 query. All ports are TCP, so this should return the same result. - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) - - in, _, err := client.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - // This check only runs for a TCP client because a UDP client will truncate the response. - if client.Net == "tcp" { - for portName, port := range dbWorkloadPorts { - for workloadName, workload := range dbWorkloads { - workloadTarget := fmt.Sprintf("%s.port.%s.workload.default.ns.default.ap.consul.", portName, workloadName) - workloadHost := workload.Addresses[0].Host - - srvRec := findSrvAnswerForTarget(t, in, workloadTarget) - require.EqualValues(t, port.Port, srvRec.Port, "Bad: %s", srvRec.String()) - require.EqualValues(t, 0, srvRec.Hdr.Ttl, "Bad: %s", srvRec.String()) - - a := findAorAAAAForName(t, in, in.Extra, workloadTarget) - require.Equal(t, workloadHost, a.AorAAAA.String(), "Bad: %s", a.Original.String()) - require.EqualValues(t, 0, a.Hdr.Ttl, "Bad: %s", a.Original.String()) - } - } - - // Expect 1 result per port, per workload. - require.Equal(t, 9, len(in.Answer), "answer count did not match expected\n\n%s", in.String()) - require.Equal(t, 9, len(in.Extra), "extra answer count did not match expected\n\n%s", in.String()) - } else { - // Expect 1 result per port, per workload, up to the default limit of 3. In practice the results are truncated - // at 2 because of the record byte size. - require.Equal(t, 2, len(in.Answer), "answer count did not match expected\n\n%s", in.String()) - require.Equal(t, 2, len(in.Extra), "extra answer count did not match expected\n\n%s", in.String()) - } - } - - // Look up the service directly by each port. - for portName, port := range dbWorkloadPorts { - question := fmt.Sprintf("%s.port.db.service.consul.", portName) - - for workloadName, workload := range dbWorkloads { - workloadTarget := fmt.Sprintf("%s.port.%s.workload.default.ns.default.ap.consul.", portName, workloadName) - workloadHost := workload.Addresses[0].Host - - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) - - in, _, err := client.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - srvRec := findSrvAnswerForTarget(t, in, workloadTarget) - require.EqualValues(t, port.Port, srvRec.Port, "Bad: %s", srvRec.String()) - require.EqualValues(t, 0, srvRec.Hdr.Ttl, "Bad: %s", srvRec.String()) - - a := findAorAAAAForName(t, in, in.Extra, workloadTarget) - require.Equal(t, workloadHost, a.AorAAAA.String(), "Bad: %s", a.Original.String()) - require.EqualValues(t, 0, a.Hdr.Ttl, "Bad: %s", a.Original.String()) - - // Expect 1 result per port. - require.Equal(t, 3, len(in.Answer), "answer count did not match expected\n\n%s", in.String()) - require.Equal(t, 3, len(in.Extra), "extra answer count did not match expected\n\n%s", in.String()) - } - } - - // Look up A/AAAA by service. - questions = []string{ - "db.service.consul.", - } - for _, question := range questions { - for workloadName, dnsType := range map[string]uint16{ - "db-1": dns.TypeA, - "db-2": dns.TypeA, - "db-3": dns.TypeAAAA, - } { - workload := dbWorkloads[workloadName] - - m := new(dns.Msg) - m.SetQuestion(question, dnsType) - - in, _, err := client.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - workloadHost := workload.Addresses[0].Host - - a := findAorAAAAForAddress(t, in, in.Answer, workloadHost) - require.Equal(t, question, a.Hdr.Name, "Bad: %s", a.Original.String()) - require.EqualValues(t, 0, a.Hdr.Ttl, "Bad: %s", a.Original.String()) - - // Expect 1 answer per workload. For A records, expect 2 answers because there's 2 IPv4 workloads. - if dnsType == dns.TypeA { - require.Equal(t, 2, len(in.Answer), "answer count did not match expected\n\n%s", in.String()) - } else { - require.Equal(t, 1, len(in.Answer), "answer count did not match expected\n\n%s", in.String()) - } - require.Equal(t, 0, len(in.Extra), "extra answer count did not match expected\n\n%s", in.String()) - } - } - - // Lookup a non-existing service, we should receive an SOA. - questions = []string{ - "nodb.service.consul.", - "nope.query.consul.", // prepared query is not supported in v2 - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) - - in, _, err := client.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - require.Equal(t, 1, len(in.Ns), "Bad: %s", in.String()) - - soaRec, ok := in.Ns[0].(*dns.SOA) - require.True(t, ok, "Bad: %s", in.Ns[0].String()) - require.EqualValues(t, 0, soaRec.Hdr.Ttl, "Bad: %s", in.Ns[0].String()) - } - - // Lookup workloads directly with a port. - for workloadName, dnsType := range map[string]uint16{ - "db-1": dns.TypeA, - "db-2": dns.TypeA, - "db-3": dns.TypeAAAA, - } { - for _, question := range []string{ - fmt.Sprintf("%s.workload.default.ns.default.ap.consul.", workloadName), - fmt.Sprintf("tcp.port.%s.workload.default.ns.default.ap.consul.", workloadName), - fmt.Sprintf("admin.port.%s.workload.default.ns.default.ap.consul.", workloadName), - fmt.Sprintf("mesh.port.%s.workload.default.ns.default.ap.consul.", workloadName), - } { - workload := dbWorkloads[workloadName] - workloadHost := workload.Addresses[0].Host - - m := new(dns.Msg) - m.SetQuestion(question, dnsType) - - in, _, err := client.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - require.Equal(t, 1, len(in.Answer), "Bad: %s", in.String()) - - a := findAorAAAAForName(t, in, in.Answer, question) - require.Equal(t, workloadHost, a.AorAAAA.String(), "Bad: %s", a.Original.String()) - require.EqualValues(t, 0, a.Hdr.Ttl, "Bad: %s", a.Original.String()) - } - } - - // Lookup a non-existing workload, we should receive an NXDOMAIN response. - for _, aType := range []uint16{dns.TypeA, dns.TypeAAAA} { - question := "unknown.workload.consul." - - m := new(dns.Msg) - m.SetQuestion(question, aType) - - in, _, err := client.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - require.Equal(t, 0, len(in.Answer), "Bad: %s", in.String()) - require.Equal(t, dns.RcodeNameError, in.Rcode, "Bad: %s", in.String()) - } - } -} - -func findSrvAnswerForTarget(t *testing.T, in *dns.Msg, target string) *dns.SRV { - t.Helper() - - for _, a := range in.Answer { - srvRec, ok := a.(*dns.SRV) - if ok && srvRec.Target == target { - return srvRec - } - } - t.Fatalf("could not find SRV record for target: %s\n\n%s", target, in.String()) - return nil -} - -func findAorAAAAForName(t *testing.T, in *dns.Msg, rrs []dns.RR, name string) *dnsAOrAAAA { - t.Helper() - - for _, rr := range rrs { - a := newAOrAAAA(t, rr) - if a.Hdr.Name == name { - return a - } - } - t.Fatalf("could not find A/AAAA record for name: %s\n\n%+v", name, in.String()) - return nil -} - -func findAorAAAAForAddress(t *testing.T, in *dns.Msg, rrs []dns.RR, address string) *dnsAOrAAAA { - t.Helper() - - for _, rr := range rrs { - a := newAOrAAAA(t, rr) - if a.AorAAAA.String() == address { - return a - } - } - t.Fatalf("could not find A/AAAA record for address: %s\n\n%+v", address, in.String()) - return nil -} - -func readResource(t retry.TestingTB, client pbresource.ResourceServiceClient, id *pbresource.ID, m proto.Message) proto.Message { - t.Helper() - - retry.Run(t, func(r *retry.R) { - res, err := client.Read(context.Background(), &pbresource.ReadRequest{Id: id}) - if err != nil { - r.Fatalf("err: %v", err) - } - data := res.GetResource() - require.NotEmpty(r, data) - - err = data.Data.UnmarshalTo(m) - require.NoError(r, err) - }) - - return m -} - -func toAny(t retry.TestingTB, m proto.Message) *anypb.Any { - t.Helper() - a, err := anypb.New(m) - if err != nil { - t.Fatalf("could not convert proto to `any` message: %v", err) - } - return a -} - -// dnsAOrAAAA unifies A and AAAA records for simpler testing when the IP type doesn't matter. -type dnsAOrAAAA struct { - Original dns.RR - Hdr dns.RR_Header - AorAAAA net.IP - isAAAA bool -} - -func newAOrAAAA(t *testing.T, rr dns.RR) *dnsAOrAAAA { - t.Helper() - - if aRec, ok := rr.(*dns.A); ok { - return &dnsAOrAAAA{ - Original: rr, - Hdr: aRec.Hdr, - AorAAAA: aRec.A, - isAAAA: false, - } - } - if aRec, ok := rr.(*dns.AAAA); ok { - return &dnsAOrAAAA{ - Original: rr, - Hdr: aRec.Hdr, - AorAAAA: aRec.AAAA, - isAAAA: true, - } - } - - t.Fatalf("Bad A or AAAA record: %#v", rr) - return nil -} - -func newDNSClient(tcp bool) *dns.Client { - c := new(dns.Client) - - // Use TCP to avoid truncation of larger responses and - // sidestep the default UDP size limit of 3 answers - // set by config.DefaultSource() in agent/config/default.go. - if tcp { - c.Net = "tcp" - } - - return c -} diff --git a/agent/dns_ce.go b/agent/dns_ce.go index 4eb74442fa..03595cb5bd 100644 --- a/agent/dns_ce.go +++ b/agent/dns_ce.go @@ -10,6 +10,8 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/config" + agentdns "github.com/hashicorp/consul/agent/dns" + "github.com/hashicorp/consul/agent/structs" ) // NOTE: these functions have also been copied to agent/dns package for dns v2. @@ -25,9 +27,9 @@ func getEnterpriseDNSConfig(conf *config.RuntimeConfig) enterpriseDNSConfig { // parseLocality can parse peer name or datacenter from a DNS query's labels. // Peer name is parsed from the same query part that datacenter is, so given this ambiguity // we parse a "peerOrDatacenter". The caller or RPC handler are responsible for disambiguating. -func (d *DNSServer) parseLocality(labels []string, cfg *dnsConfig) (queryLocality, bool) { +func (d *DNSServer) parseLocality(labels []string, cfg *dnsRequestConfig) (queryLocality, bool) { locality := queryLocality{ - EnterpriseMeta: d.defaultEnterpriseMeta, + EnterpriseMeta: cfg.defaultEnterpriseMeta, } switch len(labels) { @@ -51,7 +53,10 @@ func (d *DNSServer) parseLocality(labels []string, cfg *dnsConfig) (queryLocalit } return locality, true case 1: - return queryLocality{peerOrDatacenter: labels[0]}, true + return queryLocality{ + peerOrDatacenter: labels[0], + EnterpriseMeta: cfg.defaultEnterpriseMeta, + }, true case 0: return queryLocality{}, true @@ -63,27 +68,35 @@ func (d *DNSServer) parseLocality(labels []string, cfg *dnsConfig) (queryLocalit type querySameness struct{} // parseSamenessGroupLocality wraps parseLocality in CE -func (d *DNSServer) parseSamenessGroupLocality(cfg *dnsConfig, labels []string, errfnc func() error) ([]queryLocality, error) { +func (d *DNSServer) parseSamenessGroupLocality(cfg *dnsRequestConfig, labels []string, errfnc func() error) (queryLocality, error) { locality, ok := d.parseLocality(labels, cfg) if !ok { - return nil, errfnc() + return queryLocality{ + EnterpriseMeta: cfg.defaultEnterpriseMeta, + }, errfnc() } - return []queryLocality{locality}, nil + return locality, nil } func serviceCanonicalDNSName(name, kind, datacenter, domain string, _ *acl.EnterpriseMeta) string { return fmt.Sprintf("%s.%s.%s.%s", name, kind, datacenter, domain) } -func nodeCanonicalDNSName(lookup serviceLookup, nodeName, respDomain string) string { - if lookup.PeerName != "" { +func nodeCanonicalDNSName(node *structs.Node, respDomain string) string { + if node.PeerName != "" { // We must return a more-specific DNS name for peering so // that there is no ambiguity with lookups. return fmt.Sprintf("%s.node.%s.peer.%s", - nodeName, - lookup.PeerName, + node.Node, + node.PeerName, respDomain) } // Return a simpler format for non-peering nodes. - return fmt.Sprintf("%s.node.%s.%s", nodeName, lookup.Datacenter, respDomain) + return fmt.Sprintf("%s.node.%s.%s", node.Node, node.Datacenter, respDomain) +} + +// setEnterpriseMetaFromRequestContext sets the DefaultNamespace and DefaultPartition on the requestDnsConfig +// based on the requestContext's DefaultNamespace and DefaultPartition. +func (d *DNSServer) setEnterpriseMetaFromRequestContext(requestContext agentdns.Context, requestDnsConfig *dnsRequestConfig) { + // do nothing } diff --git a/agent/dns_ce_test.go b/agent/dns_ce_test.go index 44e3028562..6787c42577 100644 --- a/agent/dns_ce_test.go +++ b/agent/dns_ce_test.go @@ -22,116 +22,112 @@ func TestDNS_CE_PeeredServices(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := StartTestAgent(t, TestAgent{HCL: ``, Overrides: `peering = { test_allow_peer_registrations = true } ` + experimentsHCL}) - defer a.Shutdown() - testrpc.WaitForTestAgent(t, a.RPC, "dc1") + a := StartTestAgent(t, TestAgent{HCL: ``, Overrides: `peering = { test_allow_peer_registrations = true } `}) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") - makeReq := func() *structs.RegisterRequest { - return &structs.RegisterRequest{ - PeerName: "peer1", - Datacenter: "dc1", - Node: "peernode1", - Address: "198.18.1.1", - Service: &structs.NodeService{ - PeerName: "peer1", - Kind: structs.ServiceKindConnectProxy, - Service: "web-proxy", - Address: "199.0.0.1", - Port: 12345, - Proxy: structs.ConnectProxyConfig{ - DestinationServiceName: "peer-web", - }, - EnterpriseMeta: *acl.DefaultEnterpriseMeta(), - }, - EnterpriseMeta: *acl.DefaultEnterpriseMeta(), - } - } - - dnsQuery := func(t *testing.T, question string, typ uint16) *dns.Msg { - m := new(dns.Msg) - m.SetQuestion(question, typ) - - c := new(dns.Client) - reply, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, reply.Answer, 1, "zero valid records found for %q", question) - return reply - } - - assertARec := func(t *testing.T, rec dns.RR, expectName, expectIP string) { - aRec, ok := rec.(*dns.A) - require.True(t, ok, "Extra is not an A record: %T", rec) - require.Equal(t, expectName, aRec.Hdr.Name) - require.Equal(t, expectIP, aRec.A.String()) - } - - assertSRVRec := func(t *testing.T, rec dns.RR, expectName string, expectPort uint16) { - srvRec, ok := rec.(*dns.SRV) - require.True(t, ok, "Answer is not a SRV record: %T", rec) - require.Equal(t, expectName, srvRec.Target) - require.Equal(t, expectPort, srvRec.Port) - } - - t.Run("srv-with-addr-reply", func(t *testing.T) { - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", makeReq(), &struct{}{})) - q := dnsQuery(t, "web-proxy.service.peer1.peer.consul.", dns.TypeSRV) - require.Len(t, q.Answer, 1) - require.Len(t, q.Extra, 1) - - addr := "c7000001.addr.consul." - assertSRVRec(t, q.Answer[0], addr, 12345) - assertARec(t, q.Extra[0], addr, "199.0.0.1") - - // Query the addr to make sure it's also valid. - q = dnsQuery(t, addr, dns.TypeA) - require.Len(t, q.Answer, 1) - require.Len(t, q.Extra, 0) - assertARec(t, q.Answer[0], addr, "199.0.0.1") - }) - - t.Run("srv-with-node-reply", func(t *testing.T) { - req := makeReq() - // Clear service address to trigger node response - req.Service.Address = "" - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", req, &struct{}{})) - q := dnsQuery(t, "web-proxy.service.peer1.peer.consul.", dns.TypeSRV) - require.Len(t, q.Answer, 1) - require.Len(t, q.Extra, 1) - - nodeName := "peernode1.node.peer1.peer.consul." - assertSRVRec(t, q.Answer[0], nodeName, 12345) - assertARec(t, q.Extra[0], nodeName, "198.18.1.1") - - // Query the node to make sure it's also valid. - q = dnsQuery(t, nodeName, dns.TypeA) - require.Len(t, q.Answer, 1) - require.Len(t, q.Extra, 0) - assertARec(t, q.Answer[0], nodeName, "198.18.1.1") - }) - - t.Run("srv-with-fqdn-reply", func(t *testing.T) { - req := makeReq() - // Set non-ip address to trigger external response - req.Address = "localhost" - req.Service.Address = "" - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", req, &struct{}{})) - q := dnsQuery(t, "web-proxy.service.peer1.peer.consul.", dns.TypeSRV) - require.Len(t, q.Answer, 1) - require.Len(t, q.Extra, 0) - assertSRVRec(t, q.Answer[0], "localhost.", 12345) - }) - - t.Run("a-reply", func(t *testing.T) { - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", makeReq(), &struct{}{})) - q := dnsQuery(t, "web-proxy.service.peer1.peer.consul.", dns.TypeA) - require.Len(t, q.Answer, 1) - require.Len(t, q.Extra, 0) - assertARec(t, q.Answer[0], "web-proxy.service.peer1.peer.consul.", "199.0.0.1") - }) - }) + makeReq := func() *structs.RegisterRequest { + return &structs.RegisterRequest{ + PeerName: "peer1", + Datacenter: "dc1", + Node: "peernode1", + Address: "198.18.1.1", + Service: &structs.NodeService{ + PeerName: "peer1", + Kind: structs.ServiceKindConnectProxy, + Service: "web-proxy", + Address: "199.0.0.1", + Port: 12345, + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "peer-web", + }, + EnterpriseMeta: *acl.DefaultEnterpriseMeta(), + }, + EnterpriseMeta: *acl.DefaultEnterpriseMeta(), + } } + + dnsQuery := func(t *testing.T, question string, typ uint16) *dns.Msg { + m := new(dns.Msg) + m.SetQuestion(question, typ) + + c := new(dns.Client) + reply, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, reply.Answer, 1, "zero valid records found for %q", question) + return reply + } + + assertARec := func(t *testing.T, rec dns.RR, expectName, expectIP string) { + aRec, ok := rec.(*dns.A) + require.True(t, ok, "Extra is not an A record: %T", rec) + require.Equal(t, expectName, aRec.Hdr.Name) + require.Equal(t, expectIP, aRec.A.String()) + } + + assertSRVRec := func(t *testing.T, rec dns.RR, expectName string, expectPort uint16) { + srvRec, ok := rec.(*dns.SRV) + require.True(t, ok, "Answer is not a SRV record: %T", rec) + require.Equal(t, expectName, srvRec.Target) + require.Equal(t, expectPort, srvRec.Port) + } + + t.Run("srv-with-addr-reply", func(t *testing.T) { + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", makeReq(), &struct{}{})) + q := dnsQuery(t, "web-proxy.service.peer1.peer.consul.", dns.TypeSRV) + require.Len(t, q.Answer, 1) + require.Len(t, q.Extra, 1) + + addr := "c7000001.addr.consul." + assertSRVRec(t, q.Answer[0], addr, 12345) + assertARec(t, q.Extra[0], addr, "199.0.0.1") + + // Query the addr to make sure it's also valid. + q = dnsQuery(t, addr, dns.TypeA) + require.Len(t, q.Answer, 1) + require.Len(t, q.Extra, 0) + assertARec(t, q.Answer[0], addr, "199.0.0.1") + }) + + t.Run("srv-with-node-reply", func(t *testing.T) { + req := makeReq() + // Clear service address to trigger node response + req.Service.Address = "" + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", req, &struct{}{})) + q := dnsQuery(t, "web-proxy.service.peer1.peer.consul.", dns.TypeSRV) + require.Len(t, q.Answer, 1) + require.Len(t, q.Extra, 1) + + nodeName := "peernode1.node.peer1.peer.consul." + assertSRVRec(t, q.Answer[0], nodeName, 12345) + assertARec(t, q.Extra[0], nodeName, "198.18.1.1") + + // Query the node to make sure it's also valid. + q = dnsQuery(t, nodeName, dns.TypeA) + require.Len(t, q.Answer, 1) + require.Len(t, q.Extra, 0) + assertARec(t, q.Answer[0], nodeName, "198.18.1.1") + }) + + t.Run("srv-with-fqdn-reply", func(t *testing.T) { + req := makeReq() + // Set non-ip address to trigger external response + req.Address = "localhost" + req.Service.Address = "" + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", req, &struct{}{})) + q := dnsQuery(t, "web-proxy.service.peer1.peer.consul.", dns.TypeSRV) + require.Len(t, q.Answer, 1) + require.Len(t, q.Extra, 0) + assertSRVRec(t, q.Answer[0], "localhost.", 12345) + }) + + t.Run("a-reply", func(t *testing.T) { + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", makeReq(), &struct{}{})) + q := dnsQuery(t, "web-proxy.service.peer1.peer.consul.", dns.TypeA) + require.Len(t, q.Answer, 1) + require.Len(t, q.Extra, 0) + assertARec(t, q.Answer[0], "web-proxy.service.peer1.peer.consul.", "199.0.0.1") + }) } func getTestCasesParseLocality() []testCaseParseLocality { diff --git a/agent/dns_node_lookup_test.go b/agent/dns_node_lookup_test.go index 8dbb3d407e..e66fca99ab 100644 --- a/agent/dns_node_lookup_test.go +++ b/agent/dns_node_lookup_test.go @@ -19,101 +19,97 @@ func TestDNS_NodeLookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - TaggedAddresses: map[string]string{ - "wan": "127.0.0.2", - }, - NodeMeta: map[string]string{ - "key": "value", - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - m := new(dns.Msg) - m.SetQuestion("foo.node.consul.", dns.TypeANY) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, in.Answer, 2) - require.Len(t, in.Extra, 0) - - aRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok, "First answer is not an A record") - require.Equal(t, "127.0.0.1", aRec.A.String()) - require.Equal(t, uint32(0), aRec.Hdr.Ttl) - - txt, ok := in.Answer[1].(*dns.TXT) - require.True(t, ok, "Second answer is not a TXT record") - require.Len(t, txt.Txt, 1) - require.Equal(t, "key=value", txt.Txt[0]) - - // Re-do the query, but only for an A RR - - m = new(dns.Msg) - m.SetQuestion("foo.node.consul.", dns.TypeA) - - c = new(dns.Client) - in, _, err = c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, in.Answer, 1) - require.Len(t, in.Extra, 1) - - aRec, ok = in.Answer[0].(*dns.A) - require.True(t, ok, "Answer is not an A record") - require.Equal(t, "127.0.0.1", aRec.A.String()) - require.Equal(t, uint32(0), aRec.Hdr.Ttl) - - txt, ok = in.Extra[0].(*dns.TXT) - require.True(t, ok, "Extra record is not a TXT record") - require.Len(t, txt.Txt, 1) - require.Equal(t, "key=value", txt.Txt[0]) - - // Re-do the query, but specify the DC - m = new(dns.Msg) - m.SetQuestion("foo.node.dc1.consul.", dns.TypeANY) - - c = new(dns.Client) - in, _, err = c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, in.Answer, 2) - require.Len(t, in.Extra, 0) - - aRec, ok = in.Answer[0].(*dns.A) - require.True(t, ok, "First answer is not an A record") - require.Equal(t, "127.0.0.1", aRec.A.String()) - require.Equal(t, uint32(0), aRec.Hdr.Ttl) - - _, ok = in.Answer[1].(*dns.TXT) - require.True(t, ok, "Second answer is not a TXT record") - - // lookup a non-existing node, we should receive a SOA - m = new(dns.Msg) - m.SetQuestion("nofoo.node.dc1.consul.", dns.TypeANY) - - c = new(dns.Client) - in, _, err = c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, in.Ns, 1) - soaRec, ok := in.Ns[0].(*dns.SOA) - require.True(t, ok, "NS RR is not a SOA record") - require.Equal(t, uint32(0), soaRec.Hdr.Ttl) - }) + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + TaggedAddresses: map[string]string{ + "wan": "127.0.0.2", + }, + NodeMeta: map[string]string{ + "key": "value", + }, } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + m := new(dns.Msg) + m.SetQuestion("foo.node.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Answer, 2) + require.Len(t, in.Extra, 0) + + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok, "First answer is not an A record") + require.Equal(t, "127.0.0.1", aRec.A.String()) + require.Equal(t, uint32(0), aRec.Hdr.Ttl) + + txt, ok := in.Answer[1].(*dns.TXT) + require.True(t, ok, "Second answer is not a TXT record") + require.Len(t, txt.Txt, 1) + require.Equal(t, "key=value", txt.Txt[0]) + + // Re-do the query, but only for an A RR + + m = new(dns.Msg) + m.SetQuestion("foo.node.consul.", dns.TypeA) + + c = new(dns.Client) + in, _, err = c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Answer, 1) + require.Len(t, in.Extra, 1) + + aRec, ok = in.Answer[0].(*dns.A) + require.True(t, ok, "Answer is not an A record") + require.Equal(t, "127.0.0.1", aRec.A.String()) + require.Equal(t, uint32(0), aRec.Hdr.Ttl) + + txt, ok = in.Extra[0].(*dns.TXT) + require.True(t, ok, "Extra record is not a TXT record") + require.Len(t, txt.Txt, 1) + require.Equal(t, "key=value", txt.Txt[0]) + + // Re-do the query, but specify the DC + m = new(dns.Msg) + m.SetQuestion("foo.node.dc1.consul.", dns.TypeANY) + + c = new(dns.Client) + in, _, err = c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Answer, 2) + require.Len(t, in.Extra, 0) + + aRec, ok = in.Answer[0].(*dns.A) + require.True(t, ok, "First answer is not an A record") + require.Equal(t, "127.0.0.1", aRec.A.String()) + require.Equal(t, uint32(0), aRec.Hdr.Ttl) + + _, ok = in.Answer[1].(*dns.TXT) + require.True(t, ok, "Second answer is not a TXT record") + + // lookup a non-existing node, we should receive a SOA + m = new(dns.Msg) + m.SetQuestion("nofoo.node.dc1.consul.", dns.TypeANY) + + c = new(dns.Client) + in, _, err = c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Ns, 1) + soaRec, ok := in.Ns[0].(*dns.SOA) + require.True(t, ok, "NS RR is not a SOA record") + require.Equal(t, uint32(0), soaRec.Hdr.Ttl) } func TestDNS_NodeLookup_CaseInsensitive(t *testing.T) { @@ -121,37 +117,33 @@ func TestDNS_NodeLookup_CaseInsensitive(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "Foo", - Address: "127.0.0.1", - } + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "Foo", + Address: "127.0.0.1", + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - m := new(dns.Msg) - m.SetQuestion("fOO.node.dc1.consul.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("fOO.node.dc1.consul.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("empty lookup: %#v", in) - } - }) + if len(in.Answer) != 1 { + t.Fatalf("empty lookup: %#v", in) } } @@ -161,45 +153,41 @@ func TestDNS_NodeLookup_PeriodName(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(false) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node with period in name - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo.bar", - Address: "127.0.0.1", - } + // Register node with period in name + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo.bar", + Address: "127.0.0.1", + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - m := new(dns.Msg) - m.SetQuestion("foo.bar.node.consul.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("foo.bar.node.consul.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - aRec, ok := in.Answer[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - }) + aRec, ok := in.Answer[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Answer[0]) } } @@ -208,48 +196,44 @@ func TestDNS_NodeLookup_AAAA(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "::4242:4242", - } + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "::4242:4242", + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - m := new(dns.Msg) - m.SetQuestion("bar.node.consul.", dns.TypeAAAA) + m := new(dns.Msg) + m.SetQuestion("bar.node.consul.", dns.TypeAAAA) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - aRec, ok := in.Answer[0].(*dns.AAAA) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aRec.AAAA.String() != "::4242:4242" { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - }) + aRec, ok := in.Answer[0].(*dns.AAAA) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aRec.AAAA.String() != "::4242:4242" { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) } } @@ -267,57 +251,53 @@ func TestDNS_NodeLookup_CNAME(t *testing.T) { }) defer recursor.Shutdown() - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` recursors = ["`+recursor.Addr+`"] - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "google", - Address: "www.google.com", - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - m := new(dns.Msg) - m.SetQuestion("google.node.consul.", dns.TypeANY) - m.SetEdns0(8192, true) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - wantAnswer := []dns.RR{ - &dns.CNAME{ - Hdr: dns.RR_Header{Name: "google.node.consul.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x10}, - Target: "www.google.com.", - }, - &dns.CNAME{ - Hdr: dns.RR_Header{Name: "www.google.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Rdlength: 0x2}, - Target: "google.com.", - }, - &dns.A{ - Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, - A: []byte{0x1, 0x2, 0x3, 0x4}, // 1.2.3.4 - }, - &dns.TXT{ - Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xd}, - Txt: []string{"my_txt_value"}, - }, - } - require.Equal(t, wantAnswer, in.Answer) - }) + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "google", + Address: "www.google.com", } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + m := new(dns.Msg) + m.SetQuestion("google.node.consul.", dns.TypeANY) + m.SetEdns0(8192, true) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + wantAnswer := []dns.RR{ + &dns.CNAME{ + Hdr: dns.RR_Header{Name: "google.node.consul.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x10}, + Target: "www.google.com.", + }, + &dns.CNAME{ + Hdr: dns.RR_Header{Name: "www.google.com.", Rrtype: dns.TypeCNAME, Class: dns.ClassINET, Rdlength: 0x2}, + Target: "google.com.", + }, + &dns.A{ + Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, + A: []byte{0x1, 0x2, 0x3, 0x4}, // 1.2.3.4 + }, + &dns.TXT{ + Hdr: dns.RR_Header{Name: "google.com.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xd}, + Txt: []string{"my_txt_value"}, + }, + } + require.Equal(t, wantAnswer, in.Answer) } func TestDNS_NodeLookup_TXT(t *testing.T) { @@ -325,52 +305,48 @@ func TestDNS_NodeLookup_TXT(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "google", - Address: "127.0.0.1", - NodeMeta: map[string]string{ - "rfc1035-00": "value0", - "key0": "value1", - }, - } + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "google", + Address: "127.0.0.1", + NodeMeta: map[string]string{ + "rfc1035-00": "value0", + "key0": "value1", + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - m := new(dns.Msg) - m.SetQuestion("google.node.consul.", dns.TypeTXT) + m := new(dns.Msg) + m.SetQuestion("google.node.consul.", dns.TypeTXT) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - // Should have the 1 TXT record reply - if len(in.Answer) != 2 { - t.Fatalf("Bad: %#v", in) - } + // Should have the 1 TXT record reply + if len(in.Answer) != 2 { + t.Fatalf("Bad: %#v", in) + } - txtRec, ok := in.Answer[0].(*dns.TXT) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if len(txtRec.Txt) != 1 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if txtRec.Txt[0] != "value0" && txtRec.Txt[0] != "key0=value1" { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - }) + txtRec, ok := in.Answer[0].(*dns.TXT) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if len(txtRec.Txt) != 1 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if txtRec.Txt[0] != "value0" && txtRec.Txt[0] != "key0=value1" { + t.Fatalf("Bad: %#v", in.Answer[0]) } } @@ -379,52 +355,48 @@ func TestDNS_NodeLookup_TXT_DontSuppress(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false } `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false } `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "google", - Address: "127.0.0.1", - NodeMeta: map[string]string{ - "rfc1035-00": "value0", - "key0": "value1", - }, - } + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "google", + Address: "127.0.0.1", + NodeMeta: map[string]string{ + "rfc1035-00": "value0", + "key0": "value1", + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - m := new(dns.Msg) - m.SetQuestion("google.node.consul.", dns.TypeTXT) + m := new(dns.Msg) + m.SetQuestion("google.node.consul.", dns.TypeTXT) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - // Should have the 1 TXT record reply - if len(in.Answer) != 2 { - t.Fatalf("Bad: %#v", in) - } + // Should have the 1 TXT record reply + if len(in.Answer) != 2 { + t.Fatalf("Bad: %#v", in) + } - txtRec, ok := in.Answer[0].(*dns.TXT) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if len(txtRec.Txt) != 1 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if txtRec.Txt[0] != "value0" && txtRec.Txt[0] != "key0=value1" { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - }) + txtRec, ok := in.Answer[0].(*dns.TXT) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if len(txtRec.Txt) != 1 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if txtRec.Txt[0] != "value0" && txtRec.Txt[0] != "key0=value1" { + t.Fatalf("Bad: %#v", in.Answer[0]) } } @@ -433,48 +405,44 @@ func TestDNS_NodeLookup_ANY(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.1", - NodeMeta: map[string]string{ - "key": "value", - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - m := new(dns.Msg) - m.SetQuestion("bar.node.consul.", dns.TypeANY) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - wantAnswer := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, - A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 - }, - &dns.TXT{ - Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa}, - Txt: []string{"key=value"}, - }, - } - require.Equal(t, wantAnswer, in.Answer) - }) + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.1", + NodeMeta: map[string]string{ + "key": "value", + }, } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + m := new(dns.Msg) + m.SetQuestion("bar.node.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + wantAnswer := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, + A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 + }, + &dns.TXT{ + Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa}, + Txt: []string{"key=value"}, + }, + } + require.Equal(t, wantAnswer, in.Answer) } func TestDNS_NodeLookup_ANY_DontSuppressTXT(t *testing.T) { @@ -482,48 +450,44 @@ func TestDNS_NodeLookup_ANY_DontSuppressTXT(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false } `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false } `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.1", - NodeMeta: map[string]string{ - "key": "value", - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - m := new(dns.Msg) - m.SetQuestion("bar.node.consul.", dns.TypeANY) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - wantAnswer := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, - A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 - }, - &dns.TXT{ - Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa}, - Txt: []string{"key=value"}, - }, - } - require.Equal(t, wantAnswer, in.Answer) - }) + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.1", + NodeMeta: map[string]string{ + "key": "value", + }, } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + m := new(dns.Msg) + m.SetQuestion("bar.node.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + wantAnswer := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, + A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 + }, + &dns.TXT{ + Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa}, + Txt: []string{"key=value"}, + }, + } + require.Equal(t, wantAnswer, in.Answer) } func TestDNS_NodeLookup_A_SuppressTXT(t *testing.T) { @@ -531,43 +495,39 @@ func TestDNS_NodeLookup_A_SuppressTXT(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false } `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false } `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.1", - NodeMeta: map[string]string{ - "key": "value", - }, - } - - var out struct{} - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - - m := new(dns.Msg) - m.SetQuestion("bar.node.consul.", dns.TypeA) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - - wantAnswer := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, - A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 - }, - } - require.Equal(t, wantAnswer, in.Answer) - - // ensure TXT RR suppression - require.Len(t, in.Extra, 0) - }) + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.1", + NodeMeta: map[string]string{ + "key": "value", + }, } + + var out struct{} + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) + + m := new(dns.Msg) + m.SetQuestion("bar.node.consul.", dns.TypeA) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + + wantAnswer := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "bar.node.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, + A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 + }, + } + require.Equal(t, wantAnswer, in.Answer) + + // ensure TXT RR suppression + require.Len(t, in.Extra, 0) } func TestDNS_NodeLookup_TTL(t *testing.T) { @@ -583,122 +543,118 @@ func TestDNS_NodeLookup_TTL(t *testing.T) { }) defer recursor.Shutdown() - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` recursors = ["`+recursor.Addr+`"] dns_config { node_ttl = "10s" allow_stale = true max_stale = "1s" } - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - } + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - m := new(dns.Msg) - m.SetQuestion("foo.node.consul.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("foo.node.consul.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - aRec, ok := in.Answer[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aRec.Hdr.Ttl != 10 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + aRec, ok := in.Answer[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aRec.Hdr.Ttl != 10 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - // Register node with IPv6 - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "::4242:4242", - } - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + // Register node with IPv6 + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "::4242:4242", + } + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - // Check an IPv6 record - m = new(dns.Msg) - m.SetQuestion("bar.node.consul.", dns.TypeANY) + // Check an IPv6 record + m = new(dns.Msg) + m.SetQuestion("bar.node.consul.", dns.TypeANY) - in, _, err = c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + in, _, err = c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - aaaaRec, ok := in.Answer[0].(*dns.AAAA) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aaaaRec.AAAA.String() != "::4242:4242" { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aaaaRec.Hdr.Ttl != 10 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + aaaaRec, ok := in.Answer[0].(*dns.AAAA) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aaaaRec.AAAA.String() != "::4242:4242" { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aaaaRec.Hdr.Ttl != 10 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - // Register node with CNAME - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "google", - Address: "www.google.com", - } - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + // Register node with CNAME + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "google", + Address: "www.google.com", + } + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - m = new(dns.Msg) - m.SetQuestion("google.node.consul.", dns.TypeANY) + m = new(dns.Msg) + m.SetQuestion("google.node.consul.", dns.TypeANY) - in, _, err = c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + in, _, err = c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - // Should have the CNAME record + a few A records - if len(in.Answer) < 2 { - t.Fatalf("Bad: %#v", in) - } + // Should have the CNAME record + a few A records + if len(in.Answer) < 2 { + t.Fatalf("Bad: %#v", in) + } - cnRec, ok := in.Answer[0].(*dns.CNAME) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if cnRec.Target != "www.google.com." { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if cnRec.Hdr.Ttl != 10 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - }) + cnRec, ok := in.Answer[0].(*dns.CNAME) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if cnRec.Target != "www.google.com." { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if cnRec.Hdr.Ttl != 10 { + t.Fatalf("Bad: %#v", in.Answer[0]) } } diff --git a/agent/dns_reverse_lookup_test.go b/agent/dns_reverse_lookup_test.go index 6a7cc49456..755921833b 100644 --- a/agent/dns_reverse_lookup_test.go +++ b/agent/dns_reverse_lookup_test.go @@ -17,45 +17,41 @@ func TestDNS_ReverseLookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo2", - Address: "127.0.0.2", - } + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo2", + Address: "127.0.0.2", + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - m := new(dns.Msg) - m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != "foo2.node.dc1.consul." { - t.Fatalf("Bad: %#v", ptrRec) - } - }) + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != "foo2.node.dc1.consul." { + t.Fatalf("Bad: %#v", ptrRec) } } @@ -64,47 +60,43 @@ func TestDNS_ReverseLookup_CustomDomain(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` domain = "custom" - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo2", - Address: "127.0.0.2", - } + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo2", + Address: "127.0.0.2", + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - m := new(dns.Msg) - m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != "foo2.node.dc1.custom." { - t.Fatalf("Bad: %#v", ptrRec) - } - }) + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != "foo2.node.dc1.custom." { + t.Fatalf("Bad: %#v", ptrRec) } } @@ -113,45 +105,41 @@ func TestDNS_ReverseLookup_IPV6(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "::4242:4242", - } + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "::4242:4242", + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - m := new(dns.Msg) - m.SetQuestion("2.4.2.4.2.4.2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("2.4.2.4.2.4.2.4.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.ip6.arpa.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != "bar.node.dc1.consul." { - t.Fatalf("Bad: %#v", ptrRec) - } - }) + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != "bar.node.dc1.consul." { + t.Fatalf("Bad: %#v", ptrRec) } } @@ -160,58 +148,53 @@ func TestDNS_Compression_ReverseLookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + // Register node. + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo2", + Address: "127.0.0.2", + } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - // Register node. - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo2", - Address: "127.0.0.2", - } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + m := new(dns.Msg) + m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) - m := new(dns.Msg) - m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) + conn, err := dns.Dial("udp", a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - conn, err := dns.Dial("udp", a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + // Do a manual exchange with compression on (the default). + if err := conn.WriteMsg(m); err != nil { + t.Fatalf("err: %v", err) + } + p := make([]byte, dns.MaxMsgSize) + compressed, err := conn.Read(p) + if err != nil { + t.Fatalf("err: %v", err) + } - // Do a manual exchange with compression on (the default). - if err := conn.WriteMsg(m); err != nil { - t.Fatalf("err: %v", err) - } - p := make([]byte, dns.MaxMsgSize) - compressed, err := conn.Read(p) - if err != nil { - t.Fatalf("err: %v", err) - } + // Disable compression and try again. + a.DNSDisableCompression(true) + if err := conn.WriteMsg(m); err != nil { + t.Fatalf("err: %v", err) + } + unc, err := conn.Read(p) + if err != nil { + t.Fatalf("err: %v", err) + } - // Disable compression and try again. - a.DNSDisableCompression(true) - if err := conn.WriteMsg(m); err != nil { - t.Fatalf("err: %v", err) - } - unc, err := conn.Read(p) - if err != nil { - t.Fatalf("err: %v", err) - } - - // We can't see the compressed status given the DNS API, so we just make - // sure the message is smaller to see if it's respecting the flag. - if compressed == 0 || unc == 0 || compressed >= unc { - t.Fatalf("doesn't look compressed: %d vs. %d", compressed, unc) - } - }) + // We can't see the compressed status given the DNS API, so we just make + // sure the message is smaller to see if it's respecting the flag. + if compressed == 0 || unc == 0 || compressed >= unc { + t.Fatalf("doesn't look compressed: %d vs. %d", compressed, unc) } } @@ -220,53 +203,49 @@ func TestDNS_ServiceReverseLookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - Address: "127.0.0.2", - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + Address: "127.0.0.2", + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - m := new(dns.Msg) - m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != serviceCanonicalDNSName("db", "service", "dc1", "consul", nil)+"." { - t.Fatalf("Bad: %#v", ptrRec) - } - }) + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != serviceCanonicalDNSName("db", "service", "dc1", "consul", nil)+"." { + t.Fatalf("Bad: %#v", ptrRec) } } @@ -275,53 +254,49 @@ func TestDNS_ServiceReverseLookup_IPV6(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "2001:db8::1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - Address: "2001:db8::ff00:42:8329", - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "2001:db8::1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + Address: "2001:db8::ff00:42:8329", + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - m := new(dns.Msg) - m.SetQuestion("9.2.3.8.2.4.0.0.0.0.f.f.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("9.2.3.8.2.4.0.0.0.0.f.f.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != serviceCanonicalDNSName("db", "service", "dc1", "consul", nil)+"." { - t.Fatalf("Bad: %#v", ptrRec) - } - }) + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != serviceCanonicalDNSName("db", "service", "dc1", "consul", nil)+"." { + t.Fatalf("Bad: %#v", ptrRec) } } @@ -330,55 +305,51 @@ func TestDNS_ServiceReverseLookup_CustomDomain(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` domain = "custom" - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - Address: "127.0.0.2", - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + Address: "127.0.0.2", + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - m := new(dns.Msg) - m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("2.0.0.127.in-addr.arpa.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != serviceCanonicalDNSName("db", "service", "dc1", "custom", nil)+"." { - t.Fatalf("Bad: %#v", ptrRec) - } - }) + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != serviceCanonicalDNSName("db", "service", "dc1", "custom", nil)+"." { + t.Fatalf("Bad: %#v", ptrRec) } } @@ -387,53 +358,49 @@ func TestDNS_ServiceReverseLookupNodeAddress(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - Address: "127.0.0.1", - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + Address: "127.0.0.1", + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - m := new(dns.Msg) - m.SetQuestion("1.0.0.127.in-addr.arpa.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("1.0.0.127.in-addr.arpa.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - ptrRec, ok := in.Answer[0].(*dns.PTR) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if ptrRec.Ptr != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", ptrRec) - } - }) + ptrRec, ok := in.Answer[0].(*dns.PTR) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if ptrRec.Ptr != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", ptrRec) } } @@ -442,35 +409,31 @@ func TestDNS_ReverseLookup_NotFound(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - // do not configure recursors - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + // do not configure recursors + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Do not register any nodes - m := new(dns.Msg) - qName := "2.0.0.127.in-addr.arpa." - m.SetQuestion(qName, dns.TypeANY) + // Do not register any nodes + m := new(dns.Msg) + qName := "2.0.0.127.in-addr.arpa." + m.SetQuestion(qName, dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Nil(t, in.Answer) - require.Nil(t, in.Extra) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Nil(t, in.Answer) + require.Nil(t, in.Extra) - require.Equal(t, dns.RcodeNameError, in.Rcode) + require.Equal(t, dns.RcodeNameError, in.Rcode) - question := in.Question[0] - require.Equal(t, qName, question.Name) - require.Equal(t, dns.TypeANY, question.Qtype) - require.Equal(t, uint16(dns.ClassINET), question.Qclass) + question := in.Question[0] + require.Equal(t, qName, question.Name) + require.Equal(t, dns.TypeANY, question.Qtype) + require.Equal(t, uint16(dns.ClassINET), question.Qclass) - soa, ok := in.Ns[0].(*dns.SOA) - require.True(t, ok) - require.Equal(t, "ns.consul.", soa.Ns) - require.Equal(t, "hostmaster.consul.", soa.Mbox) - }) - } + soa, ok := in.Ns[0].(*dns.SOA) + require.True(t, ok) + require.Equal(t, "ns.consul.", soa.Ns) + require.Equal(t, "hostmaster.consul.", soa.Mbox) } diff --git a/agent/dns_service_lookup_test.go b/agent/dns_service_lookup_test.go index 7905100aa6..34a48b75ba 100644 --- a/agent/dns_service_lookup_test.go +++ b/agent/dns_service_lookup_test.go @@ -26,61 +26,57 @@ func TestDNS_ServiceLookupNoMultiCNAME(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "198.18.0.1", - Service: &structs.NodeService{ - Service: "db", - Port: 12345, - Address: "foo.node.consul", - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "198.18.0.1", + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + Address: "foo.node.consul", + }, + } - var out struct{} - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - } - - // Register a second node node with the same service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "198.18.0.2", - Service: &structs.NodeService{ - Service: "db", - Port: 12345, - Address: "bar.node.consul", - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } - - m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeANY) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - - // expect a CNAME and an A RR - require.Len(t, in.Answer, 2) - require.IsType(t, &dns.CNAME{}, in.Answer[0]) - require.IsType(t, &dns.A{}, in.Answer[1]) - }) + var out struct{} + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) } + + // Register a second node node with the same service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "198.18.0.2", + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + Address: "bar.node.consul", + }, + } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + m := new(dns.Msg) + m.SetQuestion("db.service.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + + // expect a CNAME and an A RR + require.Len(t, in.Answer, 2) + require.IsType(t, &dns.CNAME{}, in.Answer[0]) + require.IsType(t, &dns.A{}, in.Answer[1]) } func TestDNS_ServiceLookupPreferNoCNAME(t *testing.T) { @@ -88,64 +84,60 @@ func TestDNS_ServiceLookupPreferNoCNAME(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "198.18.0.1", - Service: &structs.NodeService{ - Service: "db", - Port: 12345, - Address: "198.18.0.1", - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "198.18.0.1", + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + Address: "198.18.0.1", + }, + } - var out struct{} - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - } - - // Register a second node node with the same service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "198.18.0.2", - Service: &structs.NodeService{ - Service: "db", - Port: 12345, - Address: "bar.node.consul", - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } - - m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeANY) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - - // expect an A RR - require.Len(t, in.Answer, 1) - aRec, ok := in.Answer[0].(*dns.A) - require.Truef(t, ok, "Not an A RR") - - require.Equal(t, "db.service.consul.", aRec.Hdr.Name) - require.Equal(t, "198.18.0.1", aRec.A.String()) - }) + var out struct{} + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) } + + // Register a second node node with the same service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "198.18.0.2", + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + Address: "bar.node.consul", + }, + } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + m := new(dns.Msg) + m.SetQuestion("db.service.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + + // expect an A RR + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.A) + require.Truef(t, ok, "Not an A RR") + + require.Equal(t, "db.service.consul.", aRec.Hdr.Name) + require.Equal(t, "198.18.0.1", aRec.A.String()) } func TestDNS_ServiceLookupMultiAddrNoCNAME(t *testing.T) { @@ -153,90 +145,86 @@ func TestDNS_ServiceLookupMultiAddrNoCNAME(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "198.18.0.1", - Service: &structs.NodeService{ - Service: "db", - Port: 12345, - Address: "198.18.0.1", - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "198.18.0.1", + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + Address: "198.18.0.1", + }, + } - var out struct{} - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - } - - // Register a second node node with the same service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "198.18.0.2", - Service: &structs.NodeService{ - Service: "db", - Port: 12345, - Address: "bar.node.consul", - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Register a second node node with the same service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "baz", - Address: "198.18.0.3", - Service: &structs.NodeService{ - Service: "db", - Port: 12345, - Address: "198.18.0.3", - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } - - m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeANY) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - - // expect two A RRs - require.Len(t, in.Answer, 2) - require.IsType(t, &dns.A{}, in.Answer[0]) - require.Equal(t, "db.service.consul.", in.Answer[0].Header().Name) - isOneOfTheseIPs := func(ip net.IP) bool { - if ip.Equal(net.ParseIP("198.18.0.1")) || ip.Equal(net.ParseIP("198.18.0.3")) { - return true - } - return false - } - require.True(t, isOneOfTheseIPs(in.Answer[0].(*dns.A).A)) - require.IsType(t, &dns.A{}, in.Answer[1]) - require.Equal(t, "db.service.consul.", in.Answer[1].Header().Name) - require.True(t, isOneOfTheseIPs(in.Answer[1].(*dns.A).A)) - }) + var out struct{} + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) } + + // Register a second node node with the same service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "198.18.0.2", + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + Address: "bar.node.consul", + }, + } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Register a second node node with the same service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "baz", + Address: "198.18.0.3", + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + Address: "198.18.0.3", + }, + } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + m := new(dns.Msg) + m.SetQuestion("db.service.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + + // expect two A RRs + require.Len(t, in.Answer, 2) + require.IsType(t, &dns.A{}, in.Answer[0]) + require.Equal(t, "db.service.consul.", in.Answer[0].Header().Name) + isOneOfTheseIPs := func(ip net.IP) bool { + if ip.Equal(net.ParseIP("198.18.0.1")) || ip.Equal(net.ParseIP("198.18.0.3")) { + return true + } + return false + } + require.True(t, isOneOfTheseIPs(in.Answer[0].(*dns.A).A)) + require.IsType(t, &dns.A{}, in.Answer[1]) + require.Equal(t, "db.service.consul.", in.Answer[1].Header().Name) + require.True(t, isOneOfTheseIPs(in.Answer[1].(*dns.A).A)) } func TestDNS_ServiceLookup(t *testing.T) { @@ -244,140 +232,230 @@ func TestDNS_ServiceLookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + DNS: structs.QueryDNSOptions{ + TTL: "3s", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + + if strings.Contains(question, "query") { + // The query should have the TTL associated with the query registration. + if srvRec.Hdr.Ttl != 3 { + t.Fatalf("Bad: %#v", in.Answer[0]) } - - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, - DNS: structs.QueryDNSOptions{ - TTL: "3s", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } + if aRec.Hdr.Ttl != 3 { + t.Fatalf("Bad: %#v", in.Extra[0]) } - - // Look up the service directly and via prepared query. - questions := []string{ - "db.service.consul.", - id + ".query.consul.", + } else { + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } - - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", srvRec) - } - - aRec, ok := in.Extra[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Name != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - - if strings.Contains(question, "query") { - // The query should have the TTL associated with the query registration. - if srvRec.Hdr.Ttl != 3 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aRec.Hdr.Ttl != 3 { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - } else { - if srvRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - } - + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) } + } - // Lookup a non-existing service/query, we should receive an SOA. - questions = []string{ - "nodb.service.consul.", - "nope.query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + } - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + // Lookup a non-existing service/query, we should receive an SOA. + questions = []string{ + "nodb.service.consul.", + "nope.query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - if len(in.Ns) != 1 { - t.Fatalf("Bad: %#v", in) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - soaRec, ok := in.Ns[0].(*dns.SOA) - if !ok { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - if soaRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - } + if len(in.Ns) != 1 { + t.Fatalf("Bad: %#v", in) + } + + soaRec, ok := in.Ns[0].(*dns.SOA) + if !ok { + t.Fatalf("Bad: %#v", in.Ns[0]) + } + if soaRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Ns[0]) + } + } +} + +// TestDNS_ServiceAddressWithTagLookup tests some specific cases that Nomad would exercise, +// Like registering a service w/o a Node. https://github.com/hashicorp/nomad/blob/1174019676ff3d65b39323eb0c7234fb1e09b80c/command/agent/consul/service_client.go#L1366-L1381 +// Errors with this were reported in https://github.com/hashicorp/consul/issues/21325#issuecomment-2166845574 +// Also we test that only one tag is valid in the URL. +func TestDNS_ServiceAddressWithTagLookup(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + { + // This emulates a Nomad service registration. + // Using an internal RPC for Catalog.Register will not trigger the same condition. + err := a.Client().Agent().ServiceRegister(&api.AgentServiceRegistration{ + Kind: api.ServiceKindTypical, + ID: "db-1", + Name: "db", + Tags: []string{"primary"}, + Address: "127.0.0.1", + Port: 12345, + Checks: make([]*api.AgentServiceCheck, 0), }) + require.NoError(t, err) + } + + { + err := a.Client().Agent().ServiceRegister(&api.AgentServiceRegistration{ + Kind: api.ServiceKindTypical, + ID: "db-2", + Name: "db", + Tags: []string{"secondary"}, + Address: "127.0.0.2", // The address here has to be different, or the DNS server will dedupe it. + Port: 12345, + Checks: make([]*api.AgentServiceCheck, 0), + }) + require.NoError(t, err) + } + + // Query the service using a tag - this also checks that we're filtering correctly + questions := []string{ + "_db._primary.service.dc1.consul.", // w/ RFC 2782 style syntax + "primary.db.service.dc1.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Answer, 1, "Expected only one result in the Answer section") + + srvRec, ok := in.Answer[0].(*dns.SRV) + require.True(t, ok, "Expected an SRV record in the Answer section") + require.Equal(t, uint16(12345), srvRec.Port) + require.Equal(t, "7f000001.addr.dc1.consul.", srvRec.Target) + + aRec, ok := in.Extra[0].(*dns.A) + require.True(t, ok, "Expected an A record in the Extra section") + require.Equal(t, "7f000001.addr.dc1.consul.", aRec.Hdr.Name) + require.Equal(t, "127.0.0.1", aRec.A.String()) + + if strings.Contains(question, "query") { + // The query should have the TTL associated with the query registration. + require.Equal(t, uint32(3), srvRec.Hdr.Ttl) + require.Equal(t, uint32(3), aRec.Hdr.Ttl) + } else { + require.Equal(t, uint32(0), srvRec.Hdr.Ttl) + require.Equal(t, uint32(0), aRec.Hdr.Ttl) + } + } + + // Multiple tags are not supported in the legacy DNS server + questions = []string{ + "banana._db._primary.service.dc1.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Answer, 0, "Expected no results in the Answer section") + + // We combine the tags with a period, which results in NXDOMAIN + // The reported issue says that v2dns this is returning valid results. + require.Equal(t, dns.RcodeNameError, in.Rcode) } } @@ -386,63 +464,59 @@ func TestDNS_ServiceLookupWithInternalServiceAddress(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` node_name = "my.test-node" - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - // The service is using the consul DNS name as service address - // which triggers a lookup loop and a subsequent stack overflow - // crash. - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Address: "db.service.consul", - Port: 12345, - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - // Looking up the service should not trigger a loop - m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeSRV) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - wantAnswer := []dns.RR{ - &dns.SRV{ - Hdr: dns.RR_Header{Name: "db.service.consul.", Rrtype: 0x21, Class: 0x1, Rdlength: 0x1b}, - Priority: 0x1, - Weight: 0x1, - Port: 12345, - Target: "foo.node.dc1.consul.", - }, - } - require.Equal(t, wantAnswer, in.Answer, "answer") - wantExtra := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "foo.node.dc1.consul.", Rrtype: 0x1, Class: 0x1, Rdlength: 0x4}, - A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 - }, - } - require.Equal(t, wantExtra, in.Extra, "extra") - }) + // Register a node with a service. + // The service is using the consul DNS name as service address + // which triggers a lookup loop and a subsequent stack overflow + // crash. + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Address: "db.service.consul", + Port: 12345, + }, } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + // Looking up the service should not trigger a loop + m := new(dns.Msg) + m.SetQuestion("db.service.consul.", dns.TypeSRV) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + wantAnswer := []dns.RR{ + &dns.SRV{ + Hdr: dns.RR_Header{Name: "db.service.consul.", Rrtype: 0x21, Class: 0x1, Rdlength: 0x1b}, + Priority: 0x1, + Weight: 0x1, + Port: 12345, + Target: "foo.node.dc1.consul.", + }, + } + require.Equal(t, wantAnswer, in.Answer, "answer") + wantExtra := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "foo.node.dc1.consul.", Rrtype: 0x1, Class: 0x1, Rdlength: 0x4}, + A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 + }, + } + require.Equal(t, wantExtra, in.Extra, "extra") } func TestDNS_ConnectServiceLookup(t *testing.T) { @@ -450,50 +524,47 @@ func TestDNS_ConnectServiceLookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register - { - args := structs.TestRegisterRequestProxy(t) - args.Address = "127.0.0.55" - args.Service.Proxy.DestinationServiceName = "db" - args.Service.Address = "" - args.Service.Port = 12345 - var out struct{} - require.Nil(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - } - - // Look up the service - questions := []string{ - "db.connect.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.Nil(t, err) - require.Len(t, in.Answer, 1) - - srvRec, ok := in.Answer[0].(*dns.SRV) - require.True(t, ok) - require.Equal(t, uint16(12345), srvRec.Port) - require.Equal(t, "foo.node.dc1.consul.", srvRec.Target) - require.Equal(t, uint32(0), srvRec.Hdr.Ttl) - - cnameRec, ok := in.Extra[0].(*dns.A) - require.True(t, ok) - require.Equal(t, "foo.node.dc1.consul.", cnameRec.Hdr.Name) - require.Equal(t, uint32(0), srvRec.Hdr.Ttl) - require.Equal(t, "127.0.0.55", cnameRec.A.String()) - } - }) + // Register + { + args := structs.TestRegisterRequestProxy(t) + args.Address = "127.0.0.55" + args.Service.Proxy.DestinationServiceName = "db" + args.Service.Address = "" + args.Service.Port = 12345 + var out struct{} + require.Nil(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) } + + // Look up the service + questions := []string{ + "db.connect.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.Nil(t, err) + require.Len(t, in.Answer, 1) + + srvRec, ok := in.Answer[0].(*dns.SRV) + require.True(t, ok) + require.Equal(t, uint16(12345), srvRec.Port) + require.Equal(t, "foo.node.dc1.consul.", srvRec.Target) + require.Equal(t, uint32(0), srvRec.Hdr.Ttl) + + cnameRec, ok := in.Extra[0].(*dns.A) + require.True(t, ok) + require.Equal(t, "foo.node.dc1.consul.", cnameRec.Hdr.Name) + require.Equal(t, uint32(0), srvRec.Hdr.Ttl) + require.Equal(t, "127.0.0.55", cnameRec.A.String()) + } + } func TestDNS_IngressServiceLookup(t *testing.T) { @@ -501,106 +572,102 @@ func TestDNS_IngressServiceLookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register ingress-gateway service - { - args := structs.TestRegisterIngressGateway(t) - var out struct{} - require.Nil(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - } + // Register ingress-gateway service + { + args := structs.TestRegisterIngressGateway(t) + var out struct{} + require.Nil(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) + } - // Register db service - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Address: "", - Port: 80, + // Register db service + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Address: "", + Port: 80, + }, + } + + var out struct{} + require.Nil(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) + } + + // Register proxy-defaults with 'http' protocol + { + req := structs.ConfigEntryRequest{ + Op: structs.ConfigEntryUpsert, + Datacenter: "dc1", + Entry: &structs.ProxyConfigEntry{ + Kind: structs.ProxyDefaults, + Name: structs.ProxyConfigGlobal, + Config: map[string]interface{}{ + "protocol": "http", + }, + }, + WriteRequest: structs.WriteRequest{Token: "root"}, + } + var out bool + require.Nil(t, a.RPC(context.Background(), "ConfigEntry.Apply", req, &out)) + require.True(t, out) + } + + // Register ingress-gateway config entry + { + args := &structs.IngressGatewayConfigEntry{ + Name: "ingress-gateway", + Kind: structs.IngressGateway, + Listeners: []structs.IngressListener{ + { + Port: 8888, + Protocol: "http", + Services: []structs.IngressService{ + {Name: "db"}, + {Name: "api"}, }, - } + }, + }, + } - var out struct{} - require.Nil(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - } + req := structs.ConfigEntryRequest{ + Op: structs.ConfigEntryUpsert, + Datacenter: "dc1", + Entry: args, + } + var out bool + require.Nil(t, a.RPC(context.Background(), "ConfigEntry.Apply", req, &out)) + require.True(t, out) + } - // Register proxy-defaults with 'http' protocol - { - req := structs.ConfigEntryRequest{ - Op: structs.ConfigEntryUpsert, - Datacenter: "dc1", - Entry: &structs.ProxyConfigEntry{ - Kind: structs.ProxyDefaults, - Name: structs.ProxyConfigGlobal, - Config: map[string]interface{}{ - "protocol": "http", - }, - }, - WriteRequest: structs.WriteRequest{Token: "root"}, - } - var out bool - require.Nil(t, a.RPC(context.Background(), "ConfigEntry.Apply", req, &out)) - require.True(t, out) - } + // Look up the service + questions := []string{ + "api.ingress.consul.", + "api.ingress.dc1.consul.", + "db.ingress.consul.", + "db.ingress.dc1.consul.", + } + for _, question := range questions { + t.Run(question, func(t *testing.T) { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) - // Register ingress-gateway config entry - { - args := &structs.IngressGatewayConfigEntry{ - Name: "ingress-gateway", - Kind: structs.IngressGateway, - Listeners: []structs.IngressListener{ - { - Port: 8888, - Protocol: "http", - Services: []structs.IngressService{ - {Name: "db"}, - {Name: "api"}, - }, - }, - }, - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.Nil(t, err) + require.Len(t, in.Answer, 1) - req := structs.ConfigEntryRequest{ - Op: structs.ConfigEntryUpsert, - Datacenter: "dc1", - Entry: args, - } - var out bool - require.Nil(t, a.RPC(context.Background(), "ConfigEntry.Apply", req, &out)) - require.True(t, out) - } - - // Look up the service - questions := []string{ - "api.ingress.consul.", - "api.ingress.dc1.consul.", - "db.ingress.consul.", - "db.ingress.dc1.consul.", - } - for _, question := range questions { - t.Run(question, func(t *testing.T) { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeA) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.Nil(t, err) - require.Len(t, in.Answer, 1) - - cnameRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok) - require.Equal(t, question, cnameRec.Hdr.Name) - require.Equal(t, uint32(0), cnameRec.Hdr.Ttl) - require.Equal(t, "127.0.0.1", cnameRec.A.String()) - }) - } + cnameRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok) + require.Equal(t, question, cnameRec.Hdr.Name) + require.Equal(t, uint32(0), cnameRec.Hdr.Ttl) + require.Equal(t, "127.0.0.1", cnameRec.A.String()) }) } } @@ -610,63 +677,59 @@ func TestDNS_ExternalServiceLookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with an external service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "www.google.com", - Service: &structs.NodeService{ - Service: "db", - Port: 12345, - }, - } + // Register a node with an external service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "www.google.com", + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service - questions := []string{ - "db.service.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + // Look up the service + questions := []string{ + "db.service.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 || len(in.Extra) > 0 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 || len(in.Extra) > 0 { + t.Fatalf("Bad: %#v", in) + } - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "www.google.com." { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - } - }) + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "www.google.com." { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } } } @@ -675,104 +738,101 @@ func TestDNS_ExternalServiceToConsulCNAMELookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` domain = "CONSUL." node_name = "test node" - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register the initial node with a service - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "web", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "web", - Port: 12345, - }, - } + // Register the initial node with a service + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "web", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "web", + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Register an external service pointing to the 'web' service - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "alias", - Address: "web.service.consul", - Service: &structs.NodeService{ - Service: "alias", - Port: 12345, - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Look up the service directly - questions := []string{ - "alias.service.consul.", - "alias.service.CoNsUl.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } - - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "web.service.consul." { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - - if len(in.Extra) != 1 { - t.Fatalf("Bad: %#v", in) - } - - aRec, ok := in.Extra[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Name != "web.service.consul." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - } - }) + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } } + + // Register an external service pointing to the 'web' service + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "alias", + Address: "web.service.consul", + Service: &structs.NodeService{ + Service: "alias", + Port: 12345, + }, + } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Look up the service directly + questions := []string{ + "alias.service.consul.", + "alias.service.CoNsUl.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "web.service.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + + if len(in.Extra) != 1 { + t.Fatalf("Bad: %#v", in) + } + + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "web.service.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + } + } func TestDNS_ExternalServiceToConsulCNAMENestedLookup(t *testing.T) { @@ -780,132 +840,128 @@ func TestDNS_ExternalServiceToConsulCNAMENestedLookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` node_name = "test-node" - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register the initial node with a service - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "web", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "web", - Port: 12345, - }, - } + // Register the initial node with a service + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "web", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "web", + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an external service pointing to the 'web' service - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "alias", - Address: "web.service.consul", - Service: &structs.NodeService{ - Service: "alias", - Port: 12345, - }, - } + // Register an external service pointing to the 'web' service + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "alias", + Address: "web.service.consul", + Service: &structs.NodeService{ + Service: "alias", + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an external service pointing to the 'alias' service - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "alias2", - Address: "alias.service.consul", - Service: &structs.NodeService{ - Service: "alias2", - Port: 12345, - }, - } + // Register an external service pointing to the 'alias' service + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "alias2", + Address: "alias.service.consul", + Service: &structs.NodeService{ + Service: "alias2", + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly - questions := []string{ - "alias2.service.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + // Look up the service directly + questions := []string{ + "alias2.service.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "alias.service.consul." { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if len(in.Extra) != 2 { - t.Fatalf("Bad: %#v", in) - } + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "alias.service.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if len(in.Extra) != 2 { + t.Fatalf("Bad: %#v", in) + } - cnameRec, ok := in.Extra[0].(*dns.CNAME) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if cnameRec.Hdr.Name != "alias.service.consul." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if cnameRec.Target != "web.service.consul." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if cnameRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Extra[0]) - } + cnameRec, ok := in.Extra[0].(*dns.CNAME) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if cnameRec.Hdr.Name != "alias.service.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if cnameRec.Target != "web.service.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if cnameRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } - aRec, ok := in.Extra[1].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[1]) - } - if aRec.Hdr.Name != "web.service.consul." { - t.Fatalf("Bad: %#v", in.Extra[1]) - } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Extra[1]) - } - if aRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Extra[1]) - } - } - }) + aRec, ok := in.Extra[1].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[1]) + } + if aRec.Hdr.Name != "web.service.consul." { + t.Fatalf("Bad: %#v", in.Extra[1]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Extra[1]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[1]) + } } } @@ -914,99 +970,96 @@ func TestDNS_ServiceLookup_ServiceAddress_A(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Address: "127.0.0.2", - Port: 12345, - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Address: "127.0.0.2", + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Look up the service directly and via prepared query. - questions := []string{ - "db.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } - - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "7f000002.addr.dc1.consul." { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - - aRec, ok := in.Extra[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Name != "7f000002.addr.dc1.consul." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.A.String() != "127.0.0.2" { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - } - }) + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } } + + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "7f000002.addr.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "7f000002.addr.dc1.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.A.String() != "127.0.0.2" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + } + } func TestDNS_AltDomain_ServiceLookup_ServiceAddress_A(t *testing.T) { @@ -1014,105 +1067,101 @@ func TestDNS_AltDomain_ServiceLookup_ServiceAddress_A(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` alt_domain = "test-domain" - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Address: "127.0.0.2", - Port: 12345, - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Address: "127.0.0.2", + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly and via prepared query. - questions := []struct { - ask string - wantDomain string - }{ - {"db.service.consul.", "consul."}, - {id + ".query.consul.", "consul."}, - {"db.service.test-domain.", "test-domain."}, - {id + ".query.test-domain.", "test-domain."}, - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question.ask, dns.TypeSRV) + // Look up the service directly and via prepared query. + questions := []struct { + ask string + wantDomain string + }{ + {"db.service.consul.", "consul."}, + {id + ".query.consul.", "consul."}, + {"db.service.test-domain.", "test-domain."}, + {id + ".query.test-domain.", "test-domain."}, + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question.ask, dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "7f000002.addr.dc1."+question.wantDomain { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "7f000002.addr.dc1."+question.wantDomain { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - aRec, ok := in.Extra[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Name != "7f000002.addr.dc1."+question.wantDomain { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.A.String() != "127.0.0.2" { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - } - }) + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "7f000002.addr.dc1."+question.wantDomain { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.A.String() != "127.0.0.2" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } } } @@ -1129,110 +1178,106 @@ func TestDNS_ServiceLookup_ServiceAddress_SRV(t *testing.T) { }) defer recursor.Shutdown() - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` recursors = ["`+recursor.Addr+`"] - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service whose address isn't an IP. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Address: "www.google.com", - Port: 12345, - }, - } + // Register a node with a service whose address isn't an IP. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Address: "www.google.com", + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an equivalent prepared query. - // Specify prepared query name containing "." to test - // since that is technically supported (though atypical). - var id string - preparedQueryName := "query.name.with.dots" - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: preparedQueryName, - Service: structs.ServiceQuery{ - Service: "db", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + // Register an equivalent prepared query. + // Specify prepared query name containing "." to test + // since that is technically supported (though atypical). + var id string + preparedQueryName := "query.name.with.dots" + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: preparedQueryName, + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly and via prepared query. - questions := []string{ - "db.service.consul.", - id + ".query.consul.", - preparedQueryName + ".query.consul.", - fmt.Sprintf("_%s._tcp.query.consul.", id), - fmt.Sprintf("_%s._tcp.query.consul.", preparedQueryName), - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + preparedQueryName + ".query.consul.", + fmt.Sprintf("_%s._tcp.query.consul.", id), + fmt.Sprintf("_%s._tcp.query.consul.", preparedQueryName), + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "www.google.com." { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "www.google.com." { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - // Should have google CNAME - cnRec, ok := in.Extra[0].(*dns.CNAME) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if cnRec.Target != "google.com." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } + // Should have google CNAME + cnRec, ok := in.Extra[0].(*dns.CNAME) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if cnRec.Target != "google.com." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } - // Check we recursively resolve - aRec, ok := in.Extra[1].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[1]) - } - if aRec.A.String() != "1.2.3.4" { - t.Fatalf("Bad: %s", aRec.A.String()) - } - } - }) + // Check we recursively resolve + aRec, ok := in.Extra[1].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[1]) + } + if aRec.A.String() != "1.2.3.4" { + t.Fatalf("Bad: %s", aRec.A.String()) + } } } @@ -1241,98 +1286,94 @@ func TestDNS_ServiceLookup_ServiceAddressIPV6(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Address: "2607:20:4005:808::200e", - Port: 12345, - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Address: "2607:20:4005:808::200e", + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly and via prepared query. - questions := []string{ - "db.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "2607002040050808000000000000200e.addr.dc1.consul." { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "2607002040050808000000000000200e.addr.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - aRec, ok := in.Extra[0].(*dns.AAAA) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Name != "2607002040050808000000000000200e.addr.dc1.consul." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.AAAA.String() != "2607:20:4005:808::200e" { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - } - }) + aRec, ok := in.Extra[0].(*dns.AAAA) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "2607002040050808000000000000200e.addr.dc1.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.AAAA.String() != "2607:20:4005:808::200e" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } } } @@ -1341,105 +1382,101 @@ func TestDNS_AltDomain_ServiceLookup_ServiceAddressIPV6(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` alt_domain = "test-domain" - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Address: "2607:20:4005:808::200e", - Port: 12345, - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Address: "2607:20:4005:808::200e", + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly and via prepared query. - questions := []struct { - ask string - want string - }{ - {"db.service.consul.", "2607002040050808000000000000200e.addr.dc1.consul."}, - {"db.service.test-domain.", "2607002040050808000000000000200e.addr.dc1.test-domain."}, - {id + ".query.consul.", "2607002040050808000000000000200e.addr.dc1.consul."}, - {id + ".query.test-domain.", "2607002040050808000000000000200e.addr.dc1.test-domain."}, - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question.ask, dns.TypeSRV) + // Look up the service directly and via prepared query. + questions := []struct { + ask string + want string + }{ + {"db.service.consul.", "2607002040050808000000000000200e.addr.dc1.consul."}, + {"db.service.test-domain.", "2607002040050808000000000000200e.addr.dc1.test-domain."}, + {id + ".query.consul.", "2607002040050808000000000000200e.addr.dc1.consul."}, + {id + ".query.test-domain.", "2607002040050808000000000000200e.addr.dc1.test-domain."}, + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question.ask, dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != question.want { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != question.want { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - aRec, ok := in.Extra[0].(*dns.AAAA) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Name != question.want { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.AAAA.String() != "2607:20:4005:808::200e" { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - } - }) + aRec, ok := in.Extra[0].(*dns.AAAA) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != question.want { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.AAAA.String() != "2607:20:4005:808::200e" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } } } @@ -1448,211 +1485,207 @@ func TestDNS_ServiceLookup_WanTranslation(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a1 := NewTestAgent(t, ` + a1 := NewTestAgent(t, ` datacenter = "dc1" translate_wan_addrs = true acl_datacenter = "" - `+experimentsHCL) - defer a1.Shutdown() + `) + defer a1.Shutdown() - a2 := NewTestAgent(t, ` + a2 := NewTestAgent(t, ` datacenter = "dc2" translate_wan_addrs = true acl_datacenter = "" - `+experimentsHCL) - defer a2.Shutdown() + `) + defer a2.Shutdown() - // Join WAN cluster - addr := fmt.Sprintf("127.0.0.1:%d", a1.Config.SerfPortWAN) - _, err := a2.JoinWAN([]string{addr}) - require.NoError(t, err) + // Join WAN cluster + addr := fmt.Sprintf("127.0.0.1:%d", a1.Config.SerfPortWAN) + _, err := a2.JoinWAN([]string{addr}) + require.NoError(t, err) + retry.Run(t, func(r *retry.R) { + require.Len(r, a1.WANMembers(), 2) + require.Len(r, a2.WANMembers(), 2) + }) + + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc2", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + require.NoError(t, a2.RPC(context.Background(), "PreparedQuery.Apply", args, &id)) + } + + type testCase struct { + nodeTaggedAddresses map[string]string + serviceAddress string + serviceTaggedAddresses map[string]structs.ServiceAddress + + dnsAddr string + + expectedPort uint16 + expectedAddress string + expectedARRName string + } + + cases := map[string]testCase{ + "node-addr-from-dc1": { + dnsAddr: a1.config.DNSAddrs[0].String(), + expectedPort: 8080, + expectedAddress: "127.0.0.1", + expectedARRName: "foo.node.dc2.consul.", + }, + "node-wan-from-dc1": { + dnsAddr: a1.config.DNSAddrs[0].String(), + nodeTaggedAddresses: map[string]string{ + "wan": "127.0.0.2", + }, + expectedPort: 8080, + expectedAddress: "127.0.0.2", + expectedARRName: "7f000002.addr.dc2.consul.", + }, + "service-addr-from-dc1": { + dnsAddr: a1.config.DNSAddrs[0].String(), + nodeTaggedAddresses: map[string]string{ + "wan": "127.0.0.2", + }, + serviceAddress: "10.0.1.1", + expectedPort: 8080, + expectedAddress: "10.0.1.1", + expectedARRName: "0a000101.addr.dc2.consul.", + }, + "service-wan-from-dc1": { + dnsAddr: a1.config.DNSAddrs[0].String(), + nodeTaggedAddresses: map[string]string{ + "wan": "127.0.0.2", + }, + serviceAddress: "10.0.1.1", + serviceTaggedAddresses: map[string]structs.ServiceAddress{ + "wan": { + Address: "198.18.0.1", + Port: 80, + }, + }, + expectedPort: 80, + expectedAddress: "198.18.0.1", + expectedARRName: "c6120001.addr.dc2.consul.", + }, + "node-addr-from-dc2": { + dnsAddr: a2.config.DNSAddrs[0].String(), + expectedPort: 8080, + expectedAddress: "127.0.0.1", + expectedARRName: "foo.node.dc2.consul.", + }, + "node-wan-from-dc2": { + dnsAddr: a2.config.DNSAddrs[0].String(), + nodeTaggedAddresses: map[string]string{ + "wan": "127.0.0.2", + }, + expectedPort: 8080, + expectedAddress: "127.0.0.1", + expectedARRName: "foo.node.dc2.consul.", + }, + "service-addr-from-dc2": { + dnsAddr: a2.config.DNSAddrs[0].String(), + nodeTaggedAddresses: map[string]string{ + "wan": "127.0.0.2", + }, + serviceAddress: "10.0.1.1", + expectedPort: 8080, + expectedAddress: "10.0.1.1", + expectedARRName: "0a000101.addr.dc2.consul.", + }, + "service-wan-from-dc2": { + dnsAddr: a2.config.DNSAddrs[0].String(), + nodeTaggedAddresses: map[string]string{ + "wan": "127.0.0.2", + }, + serviceAddress: "10.0.1.1", + serviceTaggedAddresses: map[string]structs.ServiceAddress{ + "wan": { + Address: "198.18.0.1", + Port: 80, + }, + }, + expectedPort: 8080, + expectedAddress: "10.0.1.1", + expectedARRName: "0a000101.addr.dc2.consul.", + }, + } + + for name, tc := range cases { + name := name + tc := tc + t.Run(name, func(t *testing.T) { + // Register a remote node with a service. This is in a retry since we + // need the datacenter to have a route which takes a little more time + // beyond the join, and we don't have direct access to the router here. retry.Run(t, func(r *retry.R) { - require.Len(r, a1.WANMembers(), 2) - require.Len(r, a2.WANMembers(), 2) - }) - - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc2", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, + args := &structs.RegisterRequest{ + Datacenter: "dc2", + Node: "foo", + Address: "127.0.0.1", + TaggedAddresses: tc.nodeTaggedAddresses, + Service: &structs.NodeService{ + Service: "db", + Address: tc.serviceAddress, + Port: 8080, + TaggedAddresses: tc.serviceTaggedAddresses, }, } - require.NoError(t, a2.RPC(context.Background(), "PreparedQuery.Apply", args, &id)) + + var out struct{} + require.NoError(r, a2.RPC(context.Background(), "Catalog.Register", args, &out)) + }) + + // Look up the SRV record via service and prepared query. + questions := []string{ + "db.service.dc2.consul.", + id + ".query.dc2.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) + + c := new(dns.Client) + + addr := tc.dnsAddr + in, _, err := c.Exchange(m, addr) + require.NoError(t, err) + require.Len(t, in.Answer, 1) + srvRec, ok := in.Answer[0].(*dns.SRV) + require.True(t, ok, "Bad: %#v", in.Answer[0]) + require.Equal(t, tc.expectedPort, srvRec.Port) + + aRec, ok := in.Extra[0].(*dns.A) + require.True(t, ok, "Bad: %#v", in.Extra[0]) + require.Equal(t, tc.expectedARRName, aRec.Hdr.Name) + require.Equal(t, tc.expectedAddress, aRec.A.String()) } - type testCase struct { - nodeTaggedAddresses map[string]string - serviceAddress string - serviceTaggedAddresses map[string]structs.ServiceAddress + // Also check the A record directly + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) - dnsAddr string + c := new(dns.Client) + addr := tc.dnsAddr + in, _, err := c.Exchange(m, addr) + require.NoError(t, err) + require.Len(t, in.Answer, 1) - expectedPort uint16 - expectedAddress string - expectedARRName string - } - - cases := map[string]testCase{ - "node-addr-from-dc1": { - dnsAddr: a1.config.DNSAddrs[0].String(), - expectedPort: 8080, - expectedAddress: "127.0.0.1", - expectedARRName: "foo.node.dc2.consul.", - }, - "node-wan-from-dc1": { - dnsAddr: a1.config.DNSAddrs[0].String(), - nodeTaggedAddresses: map[string]string{ - "wan": "127.0.0.2", - }, - expectedPort: 8080, - expectedAddress: "127.0.0.2", - expectedARRName: "7f000002.addr.dc2.consul.", - }, - "service-addr-from-dc1": { - dnsAddr: a1.config.DNSAddrs[0].String(), - nodeTaggedAddresses: map[string]string{ - "wan": "127.0.0.2", - }, - serviceAddress: "10.0.1.1", - expectedPort: 8080, - expectedAddress: "10.0.1.1", - expectedARRName: "0a000101.addr.dc2.consul.", - }, - "service-wan-from-dc1": { - dnsAddr: a1.config.DNSAddrs[0].String(), - nodeTaggedAddresses: map[string]string{ - "wan": "127.0.0.2", - }, - serviceAddress: "10.0.1.1", - serviceTaggedAddresses: map[string]structs.ServiceAddress{ - "wan": { - Address: "198.18.0.1", - Port: 80, - }, - }, - expectedPort: 80, - expectedAddress: "198.18.0.1", - expectedARRName: "c6120001.addr.dc2.consul.", - }, - "node-addr-from-dc2": { - dnsAddr: a2.config.DNSAddrs[0].String(), - expectedPort: 8080, - expectedAddress: "127.0.0.1", - expectedARRName: "foo.node.dc2.consul.", - }, - "node-wan-from-dc2": { - dnsAddr: a2.config.DNSAddrs[0].String(), - nodeTaggedAddresses: map[string]string{ - "wan": "127.0.0.2", - }, - expectedPort: 8080, - expectedAddress: "127.0.0.1", - expectedARRName: "foo.node.dc2.consul.", - }, - "service-addr-from-dc2": { - dnsAddr: a2.config.DNSAddrs[0].String(), - nodeTaggedAddresses: map[string]string{ - "wan": "127.0.0.2", - }, - serviceAddress: "10.0.1.1", - expectedPort: 8080, - expectedAddress: "10.0.1.1", - expectedARRName: "0a000101.addr.dc2.consul.", - }, - "service-wan-from-dc2": { - dnsAddr: a2.config.DNSAddrs[0].String(), - nodeTaggedAddresses: map[string]string{ - "wan": "127.0.0.2", - }, - serviceAddress: "10.0.1.1", - serviceTaggedAddresses: map[string]structs.ServiceAddress{ - "wan": { - Address: "198.18.0.1", - Port: 80, - }, - }, - expectedPort: 8080, - expectedAddress: "10.0.1.1", - expectedARRName: "0a000101.addr.dc2.consul.", - }, - } - - for name, tc := range cases { - name := name - tc := tc - t.Run(name, func(t *testing.T) { - // Register a remote node with a service. This is in a retry since we - // need the datacenter to have a route which takes a little more time - // beyond the join, and we don't have direct access to the router here. - retry.Run(t, func(r *retry.R) { - args := &structs.RegisterRequest{ - Datacenter: "dc2", - Node: "foo", - Address: "127.0.0.1", - TaggedAddresses: tc.nodeTaggedAddresses, - Service: &structs.NodeService{ - Service: "db", - Address: tc.serviceAddress, - Port: 8080, - TaggedAddresses: tc.serviceTaggedAddresses, - }, - } - - var out struct{} - require.NoError(r, a2.RPC(context.Background(), "Catalog.Register", args, &out)) - }) - - // Look up the SRV record via service and prepared query. - questions := []string{ - "db.service.dc2.consul.", - id + ".query.dc2.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) - - c := new(dns.Client) - - addr := tc.dnsAddr - in, _, err := c.Exchange(m, addr) - require.NoError(t, err) - require.Len(t, in.Answer, 1) - srvRec, ok := in.Answer[0].(*dns.SRV) - require.True(t, ok, "Bad: %#v", in.Answer[0]) - require.Equal(t, tc.expectedPort, srvRec.Port) - - aRec, ok := in.Extra[0].(*dns.A) - require.True(t, ok, "Bad: %#v", in.Extra[0]) - require.Equal(t, tc.expectedARRName, aRec.Hdr.Name) - require.Equal(t, tc.expectedAddress, aRec.A.String()) - } - - // Also check the A record directly - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeA) - - c := new(dns.Client) - addr := tc.dnsAddr - in, _, err := c.Exchange(m, addr) - require.NoError(t, err) - require.Len(t, in.Answer, 1) - - aRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok, "Bad: %#v", in.Answer[0]) - require.Equal(t, question, aRec.Hdr.Name) - require.Equal(t, tc.expectedAddress, aRec.A.String()) - } - }) + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok, "Bad: %#v", in.Answer[0]) + require.Equal(t, question, aRec.Hdr.Name) + require.Equal(t, tc.expectedAddress, aRec.A.String()) } }) } @@ -1680,173 +1713,8 @@ func TestDNS_ServiceLookup_CaseInsensitive(t *testing.T) { } for _, tst := range tests { t.Run(fmt.Sprintf("A lookup %v", tst.name), func(t *testing.T) { - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, fmt.Sprintf("%s %s", tst.config, experimentsHCL)) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "Db", - Tags: []string{"Primary"}, - Port: 12345, - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Register an equivalent prepared query, as well as a name. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "somequery", - Service: structs.ServiceQuery{ - Service: "db", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Try some variations to make sure case doesn't matter. - questions := []string{ - "primary.Db.service.consul.", - "primary.db.service.consul.", - "pRIMARY.dB.service.consul.", - "PRIMARY.dB.service.consul.", - "db.service.consul.", - "DB.service.consul.", - "Db.service.consul.", - "somequery.query.consul.", - "SomeQuery.query.consul.", - "SOMEQUERY.query.consul.", - } - - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) - - c := new(dns.Client) - retry.Run(t, func(r *retry.R) { - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - r.Fatalf("err: %v", err) - } - - if len(in.Answer) != 1 { - r.Fatalf("question %v, empty lookup: %#v", question, in) - } - }) - } - }) - } - }) - } -} - -// V2 DNS: we have deprecated support for service tags w/ periods -func TestDNS_ServiceLookup_TagPeriod(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - for name, experimentsHCL := range getVersionHCL(false) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"v1.primary"}, - Port: 12345, - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - m1 := new(dns.Msg) - m1.SetQuestion("v1.primary2.db.service.consul.", dns.TypeSRV) - - c1 := new(dns.Client) - in, _, err := c1.Exchange(m1, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(in.Answer) != 0 { - t.Fatalf("Bad: %#v", in) - } - - m := new(dns.Msg) - m.SetQuestion("v1.primary.db.service.consul.", dns.TypeSRV) - - c := new(dns.Client) - in, _, err = c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } - - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", srvRec) - } - - aRec, ok := in.Extra[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Name != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - }) - } -} - -func TestDNS_ServiceLookup_PreparedQueryNamePeriod(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) + a := NewTestAgent(t, tst.config) defer a.Shutdown() testrpc.WaitForLeader(t, a.RPC, "dc1") @@ -1857,7 +1725,8 @@ func TestDNS_ServiceLookup_PreparedQueryNamePeriod(t *testing.T) { Node: "foo", Address: "127.0.0.1", Service: &structs.NodeService{ - Service: "db", + Service: "Db", + Tags: []string{"Primary"}, Port: 12345, }, } @@ -1868,170 +1737,355 @@ func TestDNS_ServiceLookup_PreparedQueryNamePeriod(t *testing.T) { } } - // Register a prepared query with a period in the name. + // Register an equivalent prepared query, as well as a name. + var id string { args := &structs.PreparedQueryRequest{ Datacenter: "dc1", Op: structs.PreparedQueryCreate, Query: &structs.PreparedQuery{ - Name: "some.query.we.like", + Name: "somequery", Service: structs.ServiceQuery{ Service: "db", }, }, } - - var id string if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { t.Fatalf("err: %v", err) } } - m := new(dns.Msg) - m.SetQuestion("some.query.we.like.query.consul.", dns.TypeSRV) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) + // Try some variations to make sure case doesn't matter. + questions := []string{ + "primary.Db.service.consul.", + "primary.db.service.consul.", + "pRIMARY.dB.service.consul.", + "PRIMARY.dB.service.consul.", + "db.service.consul.", + "DB.service.consul.", + "Db.service.consul.", + "somequery.query.consul.", + "SomeQuery.query.consul.", + "SOMEQUERY.query.consul.", } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", srvRec) - } + c := new(dns.Client) + retry.Run(t, func(r *retry.R) { + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + r.Fatalf("err: %v", err) + } - aRec, ok := in.Extra[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Name != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Extra[0]) + if len(in.Answer) != 1 { + r.Fatalf("question %v, empty lookup: %#v", question, in) + } + }) } }) } } +// We have deprecated support for service tags w/ periods +func TestDNS_ServiceLookup_TagPeriod(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"v1.primary"}, + Port: 12345, + }, + } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + m1 := new(dns.Msg) + m1.SetQuestion("v1.primary2.db.service.consul.", dns.TypeSRV) + + c1 := new(dns.Client) + in, _, err := c1.Exchange(m1, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 0 { + t.Fatalf("Bad: %#v", in) + } + + m := new(dns.Msg) + m.SetQuestion("v1.primary.db.service.consul.", dns.TypeSRV) + + c := new(dns.Client) + in, _, err = c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } +} + +// TestDNS_ServiceLookup_ExtraTags tests tag behavior. +func TestDNS_ServiceLookup_ExtraTags(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + m1 := new(dns.Msg) + m1.SetQuestion("dummy.primary.db.service.consul.", dns.TypeSRV) + + c1 := new(dns.Client) + in, _, err := c1.Exchange(m1, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Answer, 0, "Expected no answer") + require.Equal(t, dns.RcodeNameError, in.Rcode) +} + +func TestDNS_ServiceLookup_PreparedQueryNamePeriod(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + }, + } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Register a prepared query with a period in the name. + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "some.query.we.like", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + + var id string + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } + + m := new(dns.Msg) + m.SetQuestion("some.query.we.like.query.consul.", dns.TypeSRV) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } +} + func TestDNS_ServiceLookup_Dedup(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a single node with multiple instances of a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - } + // Register a single node with multiple instances of a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - ID: "db2", - Service: "db", - Tags: []string{"replica"}, - Port: 12345, - }, - } - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + ID: "db2", + Service: "db", + Tags: []string{"replica"}, + Port: 12345, + }, + } + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - ID: "db3", - Service: "db", - Tags: []string{"replica"}, - Port: 12346, - }, - } - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + ID: "db3", + Service: "db", + Tags: []string{"replica"}, + Port: 12346, + }, + } + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly and via prepared query, make sure only - // one IP is returned. - questions := []string{ - "db.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeANY) + // Look up the service directly and via prepared query, make sure only + // one IP is returned. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - aRec, ok := in.Answer[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - } - }) + aRec, ok := in.Answer[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Answer[0]) + } } } @@ -2040,136 +2094,132 @@ func TestDNS_ServiceLookup_Dedup_SRV(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a single node with multiple instances of a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - } + // Register a single node with multiple instances of a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - ID: "db2", - Service: "db", - Tags: []string{"replica"}, - Port: 12345, - }, - } - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + ID: "db2", + Service: "db", + Tags: []string{"replica"}, + Port: 12345, + }, + } + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - ID: "db3", - Service: "db", - Tags: []string{"replica"}, - Port: 12346, - }, - } - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + ID: "db3", + Service: "db", + Tags: []string{"replica"}, + Port: 12346, + }, + } + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly and via prepared query, make sure only - // one IP is returned and two unique ports are returned. - questions := []string{ - "db.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + // Look up the service directly and via prepared query, make sure only + // one IP is returned and two unique ports are returned. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 2 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 2 { + t.Fatalf("Bad: %#v", in) + } - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 && srvRec.Port != 12346 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", srvRec) - } + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 && srvRec.Port != 12346 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } - srvRec, ok = in.Answer[1].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[1]) - } - if srvRec.Port != 12346 && srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Port == in.Answer[0].(*dns.SRV).Port { - t.Fatalf("should be a different port") - } - if srvRec.Target != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", srvRec) - } + srvRec, ok = in.Answer[1].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[1]) + } + if srvRec.Port != 12346 && srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Port == in.Answer[0].(*dns.SRV).Port { + t.Fatalf("should be a different port") + } + if srvRec.Target != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } - aRec, ok := in.Extra[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Name != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - } - }) + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } } } @@ -2178,161 +2228,157 @@ func TestDNS_ServiceLookup_FilterCritical(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register nodes with health checks in various states. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "serf", - Name: "serf", - Status: api.HealthCritical, - }, - } + // Register nodes with health checks in various states. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "serf", + Name: "serf", + Status: api.HealthCritical, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - args2 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.2", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "serf", - Name: "serf", - Status: api.HealthCritical, - }, - } - if err := a.RPC(context.Background(), "Catalog.Register", args2, &out); err != nil { - t.Fatalf("err: %v", err) - } + args2 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.2", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "serf", + Name: "serf", + Status: api.HealthCritical, + }, + } + if err := a.RPC(context.Background(), "Catalog.Register", args2, &out); err != nil { + t.Fatalf("err: %v", err) + } - args3 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.2", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: api.HealthCritical, - }, - } - if err := a.RPC(context.Background(), "Catalog.Register", args3, &out); err != nil { - t.Fatalf("err: %v", err) - } + args3 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.2", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: api.HealthCritical, + }, + } + if err := a.RPC(context.Background(), "Catalog.Register", args3, &out); err != nil { + t.Fatalf("err: %v", err) + } - args4 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "baz", - Address: "127.0.0.3", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - } - if err := a.RPC(context.Background(), "Catalog.Register", args4, &out); err != nil { - t.Fatalf("err: %v", err) - } + args4 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "baz", + Address: "127.0.0.3", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + } + if err := a.RPC(context.Background(), "Catalog.Register", args4, &out); err != nil { + t.Fatalf("err: %v", err) + } - args5 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "quux", - Address: "127.0.0.4", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: api.HealthWarning, - }, - } - if err := a.RPC(context.Background(), "Catalog.Register", args5, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + args5 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "quux", + Address: "127.0.0.4", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: api.HealthWarning, + }, + } + if err := a.RPC(context.Background(), "Catalog.Register", args5, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly and via prepared query. - questions := []string{ - "db.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeANY) + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - // Only 4 and 5 are not failing, so we should get 2 answers - if len(in.Answer) != 2 { - t.Fatalf("Bad: %#v", in) - } + // Only 4 and 5 are not failing, so we should get 2 answers + if len(in.Answer) != 2 { + t.Fatalf("Bad: %#v", in) + } - ips := make(map[string]bool) - for _, resp := range in.Answer { - aRec := resp.(*dns.A) - ips[aRec.A.String()] = true - } + ips := make(map[string]bool) + for _, resp := range in.Answer { + aRec := resp.(*dns.A) + ips[aRec.A.String()] = true + } - if !ips["127.0.0.3"] { - t.Fatalf("Bad: %#v should contain 127.0.0.3 (state healthy)", in) - } - if !ips["127.0.0.4"] { - t.Fatalf("Bad: %#v should contain 127.0.0.4 (state warning)", in) - } - } - }) + if !ips["127.0.0.3"] { + t.Fatalf("Bad: %#v should contain 127.0.0.3 (state healthy)", in) + } + if !ips["127.0.0.4"] { + t.Fatalf("Bad: %#v should contain 127.0.0.4 (state warning)", in) + } } } @@ -2341,118 +2387,114 @@ func TestDNS_ServiceLookup_OnlyFailing(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register nodes with all health checks in a critical state. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "serf", - Name: "serf", - Status: api.HealthCritical, - }, - } + // Register nodes with all health checks in a critical state. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "serf", + Name: "serf", + Status: api.HealthCritical, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - args2 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.2", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "serf", - Name: "serf", - Status: api.HealthCritical, - }, - } - if err := a.RPC(context.Background(), "Catalog.Register", args2, &out); err != nil { - t.Fatalf("err: %v", err) - } + args2 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.2", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "serf", + Name: "serf", + Status: api.HealthCritical, + }, + } + if err := a.RPC(context.Background(), "Catalog.Register", args2, &out); err != nil { + t.Fatalf("err: %v", err) + } - args3 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.2", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: api.HealthCritical, - }, - } - if err := a.RPC(context.Background(), "Catalog.Register", args3, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + args3 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.2", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: api.HealthCritical, + }, + } + if err := a.RPC(context.Background(), "Catalog.Register", args3, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly and via prepared query. - questions := []string{ - "db.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeANY) + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - // All 3 are failing, so we should get 0 answers and an NXDOMAIN response - if len(in.Answer) != 0 { - t.Fatalf("Bad: %#v", in) - } + // All 3 are failing, so we should get 0 answers and an NXDOMAIN response + if len(in.Answer) != 0 { + t.Fatalf("Bad: %#v", in) + } - if in.Rcode != dns.RcodeNameError { - t.Fatalf("Bad: %#v", in) - } - } - }) + if in.Rcode != dns.RcodeNameError { + t.Fatalf("Bad: %#v", in) + } } } @@ -2461,149 +2503,145 @@ func TestDNS_ServiceLookup_OnlyPassing(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` dns_config { only_passing = true } - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register nodes with health checks in various states. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: api.HealthPassing, - }, - } + // Register nodes with health checks in various states. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: api.HealthPassing, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - args2 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.2", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: api.HealthWarning, - }, - } + args2 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.2", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: api.HealthWarning, + }, + } - if err := a.RPC(context.Background(), "Catalog.Register", args2, &out); err != nil { - t.Fatalf("err: %v", err) - } + if err := a.RPC(context.Background(), "Catalog.Register", args2, &out); err != nil { + t.Fatalf("err: %v", err) + } - args3 := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "baz", - Address: "127.0.0.3", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - Check: &structs.HealthCheck{ - CheckID: "db", - Name: "db", - ServiceID: "db", - Status: api.HealthCritical, - }, - } + args3 := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "baz", + Address: "127.0.0.3", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + Check: &structs.HealthCheck{ + CheckID: "db", + Name: "db", + ServiceID: "db", + Status: api.HealthCritical, + }, + } - if err := a.RPC(context.Background(), "Catalog.Register", args3, &out); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - OnlyPassing: true, - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Look up the service directly and via prepared query. - questions := []string{ - "db.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeANY) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Only 1 is passing, so we should only get 1 answer - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } - - resp := in.Answer[0] - aRec := resp.(*dns.A) - - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - } - - newCfg := *a.Config - newCfg.DNSOnlyPassing = false - err := a.reloadConfigInternal(&newCfg) - require.NoError(t, err) - - // only_passing is now false. we should now get two nodes - m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeANY) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - - require.Equal(t, 2, len(in.Answer)) - ips := []string{in.Answer[0].(*dns.A).A.String(), in.Answer[1].(*dns.A).A.String()} - sort.Strings(ips) - require.Equal(t, []string{"127.0.0.1", "127.0.0.2"}, ips) - }) + if err := a.RPC(context.Background(), "Catalog.Register", args3, &out); err != nil { + t.Fatalf("err: %v", err) + } } + + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + OnlyPassing: true, + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + // Only 1 is passing, so we should only get 1 answer + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } + + resp := in.Answer[0] + aRec := resp.(*dns.A) + + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + } + + newCfg := *a.Config + newCfg.DNSOnlyPassing = false + err := a.reloadConfigInternal(&newCfg) + require.NoError(t, err) + + // only_passing is now false. we should now get two nodes + m := new(dns.Msg) + m.SetQuestion("db.service.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + + require.Equal(t, 2, len(in.Answer)) + ips := []string{in.Answer[0].(*dns.A).A.String(), in.Answer[1].(*dns.A).A.String()} + sort.Strings(ips) + require.Equal(t, []string{"127.0.0.1", "127.0.0.2"}, ips) } func TestDNS_ServiceLookup_Randomize(t *testing.T) { @@ -2611,96 +2649,92 @@ func TestDNS_ServiceLookup_Randomize(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a large number of nodes. - for i := 0; i < generateNumNodes; i++ { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: fmt.Sprintf("foo%d", i), - Address: fmt.Sprintf("127.0.0.%d", i+1), - Service: &structs.NodeService{ - Service: "web", - Port: 8000, - }, - } + // Register a large number of nodes. + for i := 0; i < generateNumNodes; i++ { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: fmt.Sprintf("foo%d", i), + Address: fmt.Sprintf("127.0.0.%d", i+1), + Service: &structs.NodeService{ + Service: "web", + Port: 8000, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "web", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Look up the service directly and via prepared query. Ensure the + // response is randomized each time. + questions := []string{ + "web.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + uniques := map[string]struct{}{} + for i := 0; i < 10; i++ { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) + + c := &dns.Client{Net: "udp"} + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "web", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } + // Response length should be truncated and we should get + // an A record for each response. + if len(in.Answer) != defaultNumUDPResponses { + t.Fatalf("Bad: %#v", len(in.Answer)) } - // Look up the service directly and via prepared query. Ensure the - // response is randomized each time. - questions := []string{ - "web.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - uniques := map[string]struct{}{} - for i := 0; i < 10; i++ { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeANY) - - c := &dns.Client{Net: "udp"} - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - // Response length should be truncated and we should get - // an A record for each response. - if len(in.Answer) != defaultNumUDPResponses { - t.Fatalf("Bad: %#v", len(in.Answer)) - } - - // Collect all the names. - var names []string - for _, rec := range in.Answer { - switch v := rec.(type) { - case *dns.SRV: - names = append(names, v.Target) - case *dns.A: - names = append(names, v.A.String()) - } - } - nameS := strings.Join(names, "|") - - // Tally the results. - uniques[nameS] = struct{}{} - } - - // Give some wiggle room. Since the responses are randomized and - // there is a finite number of combinations, requiring 0 - // duplicates every test run eventually gives us failures. - if len(uniques) < 2 { - t.Fatalf("unique response ratio too low: %d/10\n%v", len(uniques), uniques) + // Collect all the names. + var names []string + for _, rec := range in.Answer { + switch v := rec.(type) { + case *dns.SRV: + names = append(names, v.Target) + case *dns.A: + names = append(names, v.A.String()) } } - }) + nameS := strings.Join(names, "|") + + // Tally the results. + uniques[nameS] = struct{}{} + } + + // Give some wiggle room. Since the responses are randomized and + // there is a finite number of combinations, requiring 0 + // duplicates every test run eventually gives us failures. + if len(uniques) < 2 { + t.Fatalf("unique response ratio too low: %d/10\n%v", len(uniques), uniques) + } } } @@ -2709,74 +2743,70 @@ func TestDNS_ServiceLookup_Truncate(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` dns_config { enable_truncate = true } - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a large number of nodes. - for i := 0; i < generateNumNodes; i++ { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: fmt.Sprintf("foo%d", i), - Address: fmt.Sprintf("127.0.0.%d", i+1), - Service: &structs.NodeService{ - Service: "web", - Port: 8000, - }, - } + // Register a large number of nodes. + for i := 0; i < generateNumNodes; i++ { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: fmt.Sprintf("foo%d", i), + Address: fmt.Sprintf("127.0.0.%d", i+1), + Service: &structs.NodeService{ + Service: "web", + Port: 8000, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "web", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "web", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly and via prepared query. Ensure the - // response is truncated each time. - questions := []string{ - "web.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeANY) + // Look up the service directly and via prepared query. Ensure the + // response is truncated each time. + questions := []string{ + "web.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - // Check for the truncate bit - if !in.Truncated { - t.Fatalf("should have truncate bit") - } - } - }) + // Check for the truncate bit + if !in.Truncated { + t.Fatalf("should have truncate bit") + } } } @@ -2785,170 +2815,112 @@ func TestDNS_ServiceLookup_LargeResponses(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` dns_config { enable_truncate = true } - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - longServiceName := "this-is-a-very-very-very-very-very-long-name-for-a-service" - - // Register a lot of nodes. - for i := 0; i < 4; i++ { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: fmt.Sprintf("foo%d", i), - Address: fmt.Sprintf("127.0.0.%d", i+1), - Service: &structs.NodeService{ - Service: longServiceName, - Tags: []string{"primary"}, - Port: 12345, - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Register an equivalent prepared query. - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: longServiceName, - Service: structs.ServiceQuery{ - Service: longServiceName, - Tags: []string{"primary"}, - }, - }, - } - var id string - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Look up the service directly and via prepared query. - questions := []string{ - "_" + longServiceName + "._primary.service.consul.", - longServiceName + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - if !in.Truncated { - t.Fatalf("should have truncate bit") - } - - // Make sure the response size is RFC 1035-compliant for UDP messages - if in.Len() > 512 { - t.Fatalf("Bad: %d", in.Len()) - } - - // We should only have two answers now - if len(in.Answer) != 2 { - t.Fatalf("Bad: %d", len(in.Answer)) - } - - // Make sure the ADDITIONAL section matches the ANSWER section. - if len(in.Answer) != len(in.Extra) { - t.Fatalf("Bad: %d vs. %d", len(in.Answer), len(in.Extra)) - } - for i := 0; i < len(in.Answer); i++ { - srv, ok := in.Answer[i].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[i]) - } - - a, ok := in.Extra[i].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[i]) - } - - if srv.Target != a.Hdr.Name { - t.Fatalf("Bad: %#v %#v", srv, a) - } - } - - // Check for the truncate bit - if !in.Truncated { - t.Fatalf("should have truncate bit") - } - } - }) - } -} - -func testDNSServiceLookupResponseLimits(t *testing.T, answerLimit int, qType uint16, - expectedService, expectedQuery, expectedQueryID int, additionalHCL string) (bool, error) { - a := NewTestAgent(t, ` - node_name = "test-node" - dns_config { - udp_answer_limit = `+fmt.Sprintf("%d", answerLimit)+` - } - `+additionalHCL) + `) defer a.Shutdown() - testrpc.WaitForTestAgent(t, a.RPC, "dc1") + testrpc.WaitForLeader(t, a.RPC, "dc1") - choices := perfectlyRandomChoices(generateNumNodes, pctNodesWithIPv6) - for i := 0; i < generateNumNodes; i++ { - nodeAddress := fmt.Sprintf("127.0.0.%d", i+1) - if choices[i] { - nodeAddress = fmt.Sprintf("fe80::%d", i+1) - } + longServiceName := "this-is-a-very-very-very-very-very-long-name-for-a-service" + + // Register a lot of nodes. + for i := 0; i < 4; i++ { args := &structs.RegisterRequest{ Datacenter: "dc1", Node: fmt.Sprintf("foo%d", i), - Address: nodeAddress, + Address: fmt.Sprintf("127.0.0.%d", i+1), Service: &structs.NodeService{ - Service: "api-tier", - Port: 8080, + Service: longServiceName, + Tags: []string{"primary"}, + Port: 12345, }, } var out struct{} if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - return false, fmt.Errorf("err: %v", err) + t.Fatalf("err: %v", err) } } - var id string + + // Register an equivalent prepared query. { args := &structs.PreparedQueryRequest{ Datacenter: "dc1", Op: structs.PreparedQueryCreate, Query: &structs.PreparedQuery{ - Name: "api-tier", + Name: longServiceName, Service: structs.ServiceQuery{ - Service: "api-tier", + Service: longServiceName, + Tags: []string{"primary"}, }, }, } - + var id string if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - return false, fmt.Errorf("err: %v", err) + t.Fatalf("err: %v", err) } } // Look up the service directly and via prepared query. questions := []string{ - "api-tier.service.consul.", - "api-tier.query.consul.", - id + ".query.consul.", + "_" + longServiceName + "._primary.service.consul.", + longServiceName + ".query.consul.", } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if !in.Truncated { + t.Fatalf("should have truncate bit") + } + + // Make sure the response size is RFC 1035-compliant for UDP messages + if in.Len() > 512 { + t.Fatalf("Bad: %d", in.Len()) + } + + // We should only have two answers now + if len(in.Answer) != 2 { + t.Fatalf("Bad: %d", len(in.Answer)) + } + + // Make sure the ADDITIONAL section matches the ANSWER section. + if len(in.Answer) != len(in.Extra) { + t.Fatalf("Bad: %d vs. %d", len(in.Answer), len(in.Extra)) + } + for i := 0; i < len(in.Answer); i++ { + srv, ok := in.Answer[i].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[i]) + } + + a, ok := in.Extra[i].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[i]) + } + + if srv.Target != a.Hdr.Name { + t.Fatalf("Bad: %#v %#v", srv, a) + } + } + + // Check for the truncate bit + if !in.Truncated { + t.Fatalf("should have truncate bit") + } + } +} + +func testDNSServiceLookupResponseLimits(questions []string, answerLimit int, qType uint16, + expectedService, expectedQuery, expectedQueryID int, a *TestAgent) (bool, error) { for idx, question := range questions { m := new(dns.Msg) m.SetQuestion(question, qType) @@ -2985,169 +2957,183 @@ func testDNSServiceLookupResponseLimits(t *testing.T, answerLimit int, qType uin func checkDNSService( t *testing.T, - generateNumNodes int, - aRecordLimit int, + protocol string, + questions []string, + a *TestAgent, qType uint16, expectedResultsCount int, udpSize uint16, + setEDNS0 bool, ) { - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` - node_name = "test-node" - dns_config { - a_record_limit = `+fmt.Sprintf("%d", aRecordLimit)+` - udp_answer_limit = `+fmt.Sprintf("%d", aRecordLimit)+` - } - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForTestAgent(t, a.RPC, "dc1") + for _, question := range questions { + t.Run("question: "+question, func(t *testing.T) { - choices := perfectlyRandomChoices(generateNumNodes, pctNodesWithIPv6) - for i := 0; i < generateNumNodes; i++ { - nodeAddress := fmt.Sprintf("127.0.0.%d", i+1) - if choices[i] { - nodeAddress = fmt.Sprintf("fe80::%d", i+1) - } - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: fmt.Sprintf("foo%d", i), - Address: nodeAddress, - Service: &structs.NodeService{ - Service: "api-tier", - Port: 8080, - }, - } + m := new(dns.Msg) - var out struct{} - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) + m.SetQuestion(question, qType) + if setEDNS0 { + m.SetEdns0(udpSize, true) } - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "api-tier", - Service: structs.ServiceQuery{ - Service: "api-tier", - }, - }, - } + c := &dns.Client{Net: protocol, UDPSize: udpSize} + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) - require.NoError(t, a.RPC(context.Background(), "PreparedQuery.Apply", args, &id)) - } + t.Logf("DNS Response for %+v - %+v", m, in) - // Look up the service directly and via prepared query. - questions := []string{ - "api-tier.service.consul.", - "api-tier.query.consul.", - id + ".query.consul.", - } - for _, question := range questions { - question := question - t.Run("question: "+question, func(t *testing.T) { - - m := new(dns.Msg) - - m.SetQuestion(question, qType) - protocol := "tcp" - if udpSize > 0 { - protocol = "udp" - } - if udpSize > 512 { - m.SetEdns0(udpSize, true) - } - c := &dns.Client{Net: protocol, UDPSize: 8192} - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - - t.Logf("DNS Response for %+v - %+v", m, in) - - require.Equal(t, expectedResultsCount, len(in.Answer), - "%d/%d answers received for type %v for %s (%s)", len(in.Answer), expectedResultsCount, qType, question, protocol) - }) - } + require.Equal(t, expectedResultsCount, len(in.Answer), + "%d/%d answers received for type %v for %s (%s)", len(in.Answer), expectedResultsCount, qType, question, protocol) }) } } +func registerServicesAndPreparedQuery(t *testing.T, generateNumNodes int, a *TestAgent, serviceUniquenessKey string) []string { + choices := perfectlyRandomChoices(generateNumNodes, pctNodesWithIPv6) + serviceName := fmt.Sprintf("api-tier-%s", serviceUniquenessKey) + for i := 0; i < generateNumNodes; i++ { + nodeAddress := fmt.Sprintf("127.0.0.%d", i+1) + if choices[i] { + nodeAddress = fmt.Sprintf("fe80::%d", i+1) + } + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: fmt.Sprintf("foo%d", i), + Address: nodeAddress, + Service: &structs.NodeService{ + Service: serviceName, + Port: 8080, + }, + } + + var out struct{} + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) + } + var preparedQueryID string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: serviceName, + Service: structs.ServiceQuery{ + Service: serviceName, + }, + }, + } + + require.NoError(t, a.RPC(context.Background(), "PreparedQuery.Apply", args, &preparedQueryID)) + } + + // Look up the service directly and via prepared query. + questions := []string{ + fmt.Sprintf("%s.service.consul.", serviceName), + fmt.Sprintf("%s.query.consul.", serviceName), + preparedQueryID + ".query.consul.", + } + return questions +} + func TestDNS_ServiceLookup_ARecordLimits(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } + const ( + UDP = "udp" + TCP = "tcp" + ) - tests := []struct { - name string - aRecordLimit int - expectedAResults int - expectedAAAAResults int - expectedANYResults int - expectedSRVResults int - numNodesTotal int - udpSize uint16 - _unused_udpAnswerLimit int // NOTE: this field is not used - }{ + type testCase struct { + protocol string + aRecordLimit int + expectedAResults int + expectedAAAAResults int + expectedANYResults int + expectedSRVResults int + numNodesTotal int + udpSize uint16 + setEDNS0 bool + } + + type aRecordLimit struct { + name string + limit int + } + + tests := map[string]testCase{ // UDP + EDNS - {"udp-edns-1", 1, 1, 1, 1, 30, 30, 8192, 3}, - {"udp-edns-2", 2, 2, 2, 2, 30, 30, 8192, 3}, - {"udp-edns-3", 3, 3, 3, 3, 30, 30, 8192, 3}, - {"udp-edns-4", 4, 4, 4, 4, 30, 30, 8192, 3}, - {"udp-edns-5", 5, 5, 5, 5, 30, 30, 8192, 3}, - {"udp-edns-6", 6, 6, 6, 6, 30, 30, 8192, 3}, - {"udp-edns-max", 6, 2, 1, 3, 3, 3, 8192, 3}, - // All UDP without EDNS have a limit of 2 answers due to udpAnswerLimit - // Even SRV records are limit to 2 records - {"udp-limit-1", 1, 1, 0, 1, 1, 1, 512, 2}, - {"udp-limit-2", 2, 1, 1, 2, 2, 2, 512, 2}, - // AAAA results limited by size of payload - {"udp-limit-3", 3, 1, 1, 2, 2, 2, 512, 2}, - {"udp-limit-4", 4, 1, 1, 2, 2, 2, 512, 2}, - {"udp-limit-5", 5, 1, 1, 2, 2, 2, 512, 2}, - {"udp-limit-6", 6, 1, 1, 2, 2, 2, 512, 2}, - {"udp-limit-max", 6, 1, 1, 2, 2, 2, 512, 2}, + "udp-edns-1": {UDP, 1, 1, 1, 1, 30, 30, 8192, true}, + "udp-edns-2": {UDP, 2, 2, 2, 2, 30, 30, 8192, true}, + "udp-edns-3": {UDP, 3, 3, 3, 3, 30, 30, 8192, true}, + "udp-edns-4": {UDP, 4, 4, 4, 4, 30, 30, 8192, true}, + "udp-edns-5": {UDP, 5, 5, 5, 5, 30, 30, 8192, true}, + "udp-edns-6": {UDP, 6, 6, 6, 6, 30, 30, 8192, true}, + "udp-edns-max": {UDP, 6, 2, 1, 3, 3, 3, 8192, true}, // All UDP without EDNS and no udpAnswerLimit // Size of records is limited by UDP payload - {"udp-1", 1, 1, 0, 1, 1, 1, 512, 0}, - {"udp-2", 2, 1, 1, 2, 2, 2, 512, 0}, - {"udp-3", 3, 1, 1, 2, 2, 2, 512, 0}, - {"udp-4", 4, 1, 1, 2, 2, 2, 512, 0}, - {"udp-5", 5, 1, 1, 2, 2, 2, 512, 0}, - {"udp-6", 6, 1, 1, 2, 2, 2, 512, 0}, + "udp-1": {UDP, 1, 1, 0, 1, 1, 1, 512, false}, + "udp-2": {UDP, 2, 1, 1, 2, 2, 2, 512, false}, + "udp-3": {UDP, 3, 1, 1, 2, 2, 2, 512, false}, + "udp-4": {UDP, 4, 1, 1, 2, 2, 2, 512, false}, + "udp-5": {UDP, 5, 1, 1, 2, 2, 2, 512, false}, + "udp-6": {UDP, 6, 1, 1, 2, 2, 2, 512, false}, // Only 3 A and 3 SRV records on 512 bytes - {"udp-max", 6, 1, 1, 2, 2, 2, 512, 0}, + "udp-max": {UDP, 6, 1, 1, 2, 2, 2, 512, false}, - {"tcp-1", 1, 1, 1, 1, 30, 30, 0, 0}, - {"tcp-2", 2, 2, 2, 2, 30, 30, 0, 0}, - {"tcp-3", 3, 3, 3, 3, 30, 30, 0, 0}, - {"tcp-4", 4, 4, 4, 4, 30, 30, 0, 0}, - {"tcp-5", 5, 5, 5, 5, 30, 30, 0, 0}, - {"tcp-6", 6, 6, 6, 6, 30, 30, 0, 0}, - {"tcp-max", 6, 1, 1, 2, 2, 2, 0, 0}, + "tcp-1": {TCP, 1, 1, 1, 1, 30, 30, 0, false}, + "tcp-2": {TCP, 2, 2, 2, 2, 30, 30, 0, false}, + "tcp-3": {TCP, 3, 3, 3, 3, 30, 30, 0, false}, + "tcp-4": {TCP, 4, 4, 4, 4, 30, 30, 0, false}, + "tcp-5": {TCP, 5, 5, 5, 5, 30, 30, 0, false}, + "tcp-6": {TCP, 6, 6, 6, 6, 30, 30, 0, false}, + "tcp-max": {TCP, 6, 1, 1, 2, 2, 2, 0, false}, } - for _, test := range tests { - test := test // capture loop var + for _, recordLimit := range []aRecordLimit{ + {"1", 1}, + {"2", 2}, + {"3", 3}, + {"4", 4}, + {"5", 5}, + {"6", 6}, + {"max", 6}, + } { + t.Run("record-limit-"+recordLimit.name, func(t *testing.T) { + a := NewTestAgent(t, ` + node_name = "test-node" + dns_config { + a_record_limit = `+fmt.Sprintf("%d", recordLimit.limit)+` + udp_answer_limit = `+fmt.Sprintf("%d", recordLimit.limit)+` + } + `) - t.Run(test.name, func(t *testing.T) { + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") - // All those queries should have at max queriesLimited elements + for _, testName := range []string{ + fmt.Sprintf("udp-edns-%s", recordLimit.name), + fmt.Sprintf("udp-%s", recordLimit.name), + fmt.Sprintf("tcp-%s", recordLimit.name), + } { + t.Run(testName, func(t *testing.T) { + test := tests[testName] + questions := registerServicesAndPreparedQuery(t, test.numNodesTotal, a, testName) - t.Run("A", func(t *testing.T) { - checkDNSService(t, test.numNodesTotal, test.aRecordLimit, dns.TypeA, test.expectedAResults, test.udpSize) - }) + t.Run("A", func(t *testing.T) { + checkDNSService(t, test.protocol, questions, a, dns.TypeA, test.expectedAResults, test.udpSize, test.setEDNS0) + }) - t.Run("AAAA", func(t *testing.T) { - checkDNSService(t, test.numNodesTotal, test.aRecordLimit, dns.TypeAAAA, test.expectedAAAAResults, test.udpSize) - }) + t.Run("AAAA", func(t *testing.T) { + checkDNSService(t, test.protocol, questions, a, dns.TypeAAAA, test.expectedAAAAResults, test.udpSize, test.setEDNS0) + }) - t.Run("ANY", func(t *testing.T) { - checkDNSService(t, test.numNodesTotal, test.aRecordLimit, dns.TypeANY, test.expectedANYResults, test.udpSize) - }) + t.Run("ANY", func(t *testing.T) { + checkDNSService(t, test.protocol, questions, a, dns.TypeANY, test.expectedANYResults, test.udpSize, test.setEDNS0) + }) - // No limits but the size of records for SRV records, since not subject to randomization issues - t.Run("SRV", func(t *testing.T) { - checkDNSService(t, test.expectedSRVResults, test.aRecordLimit, dns.TypeSRV, test.numNodesTotal, test.udpSize) - }) + // No limits but the size of records for SRV records, since not subject to randomization issues + t.Run("SRV", func(t *testing.T) { + checkDNSService(t, test.protocol, questions, a, dns.TypeSRV, test.numNodesTotal, test.udpSize, test.setEDNS0) + }) + }) + } }) } } @@ -3157,68 +3143,74 @@ func TestDNS_ServiceLookup_AnswerLimits(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { + // Build a matrix of config parameters (udpAnswerLimit), and the + // length of the response per query type and question. Negative + // values imply the test must return at least the abs(value) number + // of records in the answer section. This is required because, for + // example, on OS-X and Linux, the number of answers returned in a + // 512B response is different even though both platforms are x86_64 + // and using the same version of Go. + // + // TODO(sean@): Why is it not identical everywhere when using the + // same compiler? + tests := []struct { + name string + udpAnswerLimit int + expectedAService int + expectedAQuery int + expectedAQueryID int + expectedAAAAService int + expectedAAAAQuery int + expectedAAAAQueryID int + expectedANYService int + expectedANYQuery int + expectedANYQueryID int + }{ + {"0", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, + {"1", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, + {"2", 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, + {"3", 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, + {"4", 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, + {"5", 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}, + {"6", 6, 6, 6, 6, 6, 6, 5, 6, 6, -5}, + {"7", 7, 7, 7, 6, 7, 7, 5, 7, 7, -5}, + {"8", 8, 8, 8, 6, 8, 8, 5, 8, 8, -5}, + {"9", 9, 8, 8, 6, 8, 8, 5, 8, 8, -5}, + {"20", 20, 8, 8, 6, 8, 8, 5, 8, -5, -5}, + {"30", 30, 8, 8, 6, 8, 8, 5, 8, -5, -5}, + } + for _, test := range tests { + test := test // capture loop var + t.Run(fmt.Sprintf("answer-limit-%s", test.name), func(t *testing.T) { + a := NewTestAgent(t, fmt.Sprintf(` + node_name = "test-node" + dns_config { + udp_answer_limit = %d + }`, test.udpAnswerLimit)) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + questions := registerServicesAndPreparedQuery(t, generateNumNodes, a, test.name) - // Build a matrix of config parameters (udpAnswerLimit), and the - // length of the response per query type and question. Negative - // values imply the test must return at least the abs(value) number - // of records in the answer section. This is required because, for - // example, on OS-X and Linux, the number of answers returned in a - // 512B response is different even though both platforms are x86_64 - // and using the same version of Go. - // - // TODO(sean@): Why is it not identical everywhere when using the - // same compiler? - tests := []struct { - name string - udpAnswerLimit int - expectedAService int - expectedAQuery int - expectedAQueryID int - expectedAAAAService int - expectedAAAAQuery int - expectedAAAAQueryID int - expectedANYService int - expectedANYQuery int - expectedANYQueryID int - }{ - {"0", 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, - {"1", 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}, - {"2", 2, 2, 2, 2, 2, 2, 2, 2, 2, 2}, - {"3", 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}, - {"4", 4, 4, 4, 4, 4, 4, 4, 4, 4, 4}, - {"5", 5, 5, 5, 5, 5, 5, 5, 5, 5, 5}, - {"6", 6, 6, 6, 6, 6, 6, 5, 6, 6, -5}, - {"7", 7, 7, 7, 6, 7, 7, 5, 7, 7, -5}, - {"8", 8, 8, 8, 6, 8, 8, 5, 8, 8, -5}, - {"9", 9, 8, 8, 6, 8, 8, 5, 8, 8, -5}, - {"20", 20, 8, 8, 6, 8, 8, 5, 8, -5, -5}, - {"30", 30, 8, 8, 6, 8, 8, 5, 8, -5, -5}, - } - for _, test := range tests { - test := test // capture loop var - t.Run(fmt.Sprintf("A lookup %v", test), func(t *testing.T) { - ok, err := testDNSServiceLookupResponseLimits(t, test.udpAnswerLimit, dns.TypeA, test.expectedAService, test.expectedAQuery, test.expectedAQueryID, experimentsHCL) - if !ok { - t.Fatalf("Expected service A lookup %s to pass: %v", test.name, err) - } - }) + t.Run(fmt.Sprintf("A lookup %v", test), func(t *testing.T) { + ok, err := testDNSServiceLookupResponseLimits(questions, test.udpAnswerLimit, dns.TypeA, test.expectedAService, test.expectedAQuery, test.expectedAQueryID, a) + if !ok { + t.Fatalf("Expected service A lookup %s to pass: %v", test.name, err) + } + }) - t.Run(fmt.Sprintf("AAAA lookup %v", test), func(t *testing.T) { - ok, err := testDNSServiceLookupResponseLimits(t, test.udpAnswerLimit, dns.TypeAAAA, test.expectedAAAAService, test.expectedAAAAQuery, test.expectedAAAAQueryID, experimentsHCL) - if !ok { - t.Fatalf("Expected service AAAA lookup %s to pass: %v", test.name, err) - } - }) + t.Run(fmt.Sprintf("AAAA lookup %v", test), func(t *testing.T) { + ok, err := testDNSServiceLookupResponseLimits(questions, test.udpAnswerLimit, dns.TypeAAAA, test.expectedAAAAService, test.expectedAAAAQuery, test.expectedAAAAQueryID, a) + if !ok { + t.Fatalf("Expected service AAAA lookup %s to pass: %v", test.name, err) + } + }) - t.Run(fmt.Sprintf("ANY lookup %v", test), func(t *testing.T) { - ok, err := testDNSServiceLookupResponseLimits(t, test.udpAnswerLimit, dns.TypeANY, test.expectedANYService, test.expectedANYQuery, test.expectedANYQueryID, experimentsHCL) - if !ok { - t.Fatalf("Expected service ANY lookup %s to pass: %v", test.name, err) - } - }) - } + t.Run(fmt.Sprintf("ANY lookup %v", test), func(t *testing.T) { + ok, err := testDNSServiceLookupResponseLimits(questions, test.udpAnswerLimit, dns.TypeANY, test.expectedANYService, test.expectedANYQuery, test.expectedANYQueryID, a) + if !ok { + t.Fatalf("Expected service ANY lookup %s to pass: %v", test.name, err) + } + }) }) } } @@ -3236,94 +3228,90 @@ func TestDNS_ServiceLookup_CNAME(t *testing.T) { }) defer recursor.Shutdown() - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` recursors = ["`+recursor.Addr+`"] - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a name for an address. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "google", - Address: "www.google.com", - Service: &structs.NodeService{ - Service: "search", - Port: 80, - }, - } + // Register a node with a name for an address. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "google", + Address: "www.google.com", + Service: &structs.NodeService{ + Service: "search", + Port: 80, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "search", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "search", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly and via prepared query. - questions := []string{ - "search.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeANY) + // Look up the service directly and via prepared query. + questions := []string{ + "search.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - // Service CNAME, google CNAME, google A record - if len(in.Answer) != 3 { - t.Fatalf("Bad: %#v", in) - } + // Service CNAME, google CNAME, google A record + if len(in.Answer) != 3 { + t.Fatalf("Bad: %#v", in) + } - // Should have service CNAME - cnRec, ok := in.Answer[0].(*dns.CNAME) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if cnRec.Target != "www.google.com." { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + // Should have service CNAME + cnRec, ok := in.Answer[0].(*dns.CNAME) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if cnRec.Target != "www.google.com." { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - // Should have google CNAME - cnRec, ok = in.Answer[1].(*dns.CNAME) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[1]) - } - if cnRec.Target != "google.com." { - t.Fatalf("Bad: %#v", in.Answer[1]) - } + // Should have google CNAME + cnRec, ok = in.Answer[1].(*dns.CNAME) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[1]) + } + if cnRec.Target != "google.com." { + t.Fatalf("Bad: %#v", in.Answer[1]) + } - // Check we recursively resolve - if _, ok := in.Answer[2].(*dns.A); !ok { - t.Fatalf("Bad: %#v", in.Answer[2]) - } - } - }) + // Check we recursively resolve + if _, ok := in.Answer[2].(*dns.A); !ok { + t.Fatalf("Bad: %#v", in.Answer[2]) + } } } @@ -3340,95 +3328,91 @@ func TestDNS_ServiceLookup_ServiceAddress_CNAME(t *testing.T) { }) defer recursor.Shutdown() - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` recursors = ["`+recursor.Addr+`"] - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a name for an address. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "google", - Address: "1.2.3.4", - Service: &structs.NodeService{ - Service: "search", - Port: 80, - Address: "www.google.com", - }, - } + // Register a node with a name for an address. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "google", + Address: "1.2.3.4", + Service: &structs.NodeService{ + Service: "search", + Port: 80, + Address: "www.google.com", + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "search", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "search", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly and via prepared query. - questions := []string{ - "search.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeANY) + // Look up the service directly and via prepared query. + questions := []string{ + "search.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - // Service CNAME, google CNAME, google A record - if len(in.Answer) != 3 { - t.Fatalf("Bad: %#v", in) - } + // Service CNAME, google CNAME, google A record + if len(in.Answer) != 3 { + t.Fatalf("Bad: %#v", in) + } - // Should have service CNAME - cnRec, ok := in.Answer[0].(*dns.CNAME) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if cnRec.Target != "www.google.com." { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + // Should have service CNAME + cnRec, ok := in.Answer[0].(*dns.CNAME) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if cnRec.Target != "www.google.com." { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - // Should have google CNAME - cnRec, ok = in.Answer[1].(*dns.CNAME) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[1]) - } - if cnRec.Target != "google.com." { - t.Fatalf("Bad: %#v", in.Answer[1]) - } + // Should have google CNAME + cnRec, ok = in.Answer[1].(*dns.CNAME) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[1]) + } + if cnRec.Target != "google.com." { + t.Fatalf("Bad: %#v", in.Answer[1]) + } - // Check we recursively resolve - if _, ok := in.Answer[2].(*dns.A); !ok { - t.Fatalf("Bad: %#v", in.Answer[2]) - } - } - }) + // Check we recursively resolve + if _, ok := in.Answer[2].(*dns.A); !ok { + t.Fatalf("Bad: %#v", in.Answer[2]) + } } } @@ -3437,9 +3421,7 @@ func TestDNS_ServiceLookup_TTL(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` dns_config { service_ttl = { "d*" = "42s" @@ -3450,71 +3432,69 @@ func TestDNS_ServiceLookup_TTL(t *testing.T) { allow_stale = true max_stale = "1s" } - `+experimentsHCL) - defer a.Shutdown() + `) + defer a.Shutdown() - for idx, service := range []string{"db", "dblb", "dk", "api"} { - nodeName := fmt.Sprintf("foo%d", idx) - address := fmt.Sprintf("127.0.0.%d", idx) - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: nodeName, - Address: address, - Service: &structs.NodeService{ - Service: service, - Tags: []string{"primary"}, - Port: 12345 + idx, - }, - } + for idx, service := range []string{"db", "dblb", "dk", "api"} { + nodeName := fmt.Sprintf("foo%d", idx) + address := fmt.Sprintf("127.0.0.%d", idx) + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: nodeName, + Address: address, + Service: &structs.NodeService{ + Service: service, + Tags: []string{"primary"}, + Port: 12345 + idx, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + c := new(dns.Client) + expectResult := func(dnsQuery string, expectedTTL uint32) { + t.Run(dnsQuery, func(t *testing.T) { + m := new(dns.Msg) + m.SetQuestion(dnsQuery, dns.TypeSRV) + + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) } - c := new(dns.Client) - expectResult := func(dnsQuery string, expectedTTL uint32) { - t.Run(dnsQuery, func(t *testing.T) { - m := new(dns.Msg) - m.SetQuestion(dnsQuery, dns.TypeSRV) - - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v, len is %d", in, len(in.Answer)) - } - - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Hdr.Ttl != expectedTTL { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - - aRec, ok := in.Extra[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Ttl != expectedTTL { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - }) + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v, len is %d", in, len(in.Answer)) + } + + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Hdr.Ttl != expectedTTL { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != expectedTTL { + t.Fatalf("Bad: %#v", in.Extra[0]) } - // Should have its exact TTL - expectResult("db.service.consul.", 10) - // Should match db* - expectResult("dblb.service.consul.", 66) - // Should match d* - expectResult("dk.service.consul.", 42) - // Should match * - expectResult("api.service.consul.", 5) }) } + // Should have its exact TTL + expectResult("db.service.consul.", 10) + // Should match db* + expectResult("dblb.service.consul.", 66) + // Should match d* + expectResult("dk.service.consul.", 42) + // Should match * + expectResult("api.service.consul.", 5) } func TestDNS_ServiceLookup_SRV_RFC(t *testing.T) { @@ -3522,79 +3502,75 @@ func TestDNS_ServiceLookup_SRV_RFC(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - } + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - questions := []string{ - "_db._primary.service.dc1.consul.", - "_db._primary.service.consul.", - "_db._primary.dc1.consul.", - "_db._primary.consul.", - } + questions := []string{ + "_db._primary.service.dc1.consul.", + "_db._primary.service.consul.", + "_db._primary.dc1.consul.", + "_db._primary.consul.", + } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - aRec, ok := in.Extra[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Name != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - } - }) + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) + } } } @@ -3603,83 +3579,78 @@ func TestDNS_ServiceLookup_SRV_RFC_TCP_Default(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - } + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + questions := []string{ + "_db._tcp.service.dc1.consul.", + "_db._tcp.service.consul.", + "_db._tcp.dc1.consul.", + "_db._tcp.consul.", + } + + for _, question := range questions { + t.Run(question, func(t *testing.T) { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { t.Fatalf("err: %v", err) } - questions := []string{ - "_db._tcp.service.dc1.consul.", - "_db._tcp.service.consul.", - "_db._tcp.dc1.consul.", - "_db._tcp.consul.", + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) } - for _, question := range questions { - t.Run(question, func(t *testing.T) { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Target != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", srvRec) + } + if srvRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } - - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Target != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", srvRec) - } - if srvRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - - aRec, ok := in.Extra[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Name != "foo.node.dc1.consul." { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - if aRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Extra[0]) - } - }) + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Name != "foo.node.dc1.consul." { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } + if aRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Extra[0]) } }) } - } func initDNSToken(t *testing.T, rpc RPC) { @@ -3743,45 +3714,41 @@ func TestDNS_ServiceLookup_FilterACL(t *testing.T) { } ` - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, hcl+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, hcl) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - if tt.token == "dns" { - initDNSToken(t, a) - } + if tt.token == "dns" { + initDNSToken(t, a) + } - // Register a service - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "foo", - Port: 12345, - }, - WriteRequest: structs.WriteRequest{Token: "root"}, - } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + // Register a service + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "foo", + Port: 12345, + }, + WriteRequest: structs.WriteRequest{Token: "root"}, + } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - // Set up the DNS query - c := new(dns.Client) - m := new(dns.Msg) - m.SetQuestion("foo.service.consul.", dns.TypeA) + // Set up the DNS query + c := new(dns.Client) + m := new(dns.Msg) + m.SetQuestion("foo.service.consul.", dns.TypeA) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - if len(in.Answer) != tt.results { - t.Fatalf("Bad: %#v", in) - } - }) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + if len(in.Answer) != tt.results { + t.Fatalf("Bad: %#v", in) } }) } @@ -3792,53 +3759,49 @@ func TestDNS_ServiceLookup_MetaTXT(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = true } `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = true } `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.1", - NodeMeta: map[string]string{ - "key": "value", - }, - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeSRV) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - wantAdditional := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "bar.node.dc1.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, - A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 - }, - &dns.TXT{ - Hdr: dns.RR_Header{Name: "bar.node.dc1.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa}, - Txt: []string{"key=value"}, - }, - } - require.Equal(t, wantAdditional, in.Extra) - }) + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.1", + NodeMeta: map[string]string{ + "key": "value", + }, + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + m := new(dns.Msg) + m.SetQuestion("db.service.consul.", dns.TypeSRV) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + wantAdditional := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "bar.node.dc1.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, + A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 + }, + &dns.TXT{ + Hdr: dns.RR_Header{Name: "bar.node.dc1.consul.", Rrtype: dns.TypeTXT, Class: dns.ClassINET, Rdlength: 0xa}, + Txt: []string{"key=value"}, + }, + } + require.Equal(t, wantAdditional, in.Extra) } func TestDNS_ServiceLookup_SuppressTXT(t *testing.T) { @@ -3846,48 +3809,44 @@ func TestDNS_ServiceLookup_SuppressTXT(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false } `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, `dns_config = { enable_additional_node_meta_txt = false } `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "127.0.0.1", - NodeMeta: map[string]string{ - "key": "value", - }, - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - m := new(dns.Msg) - m.SetQuestion("db.service.consul.", dns.TypeSRV) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - wantAdditional := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "bar.node.dc1.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, - A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 - }, - } - require.Equal(t, wantAdditional, in.Extra) - }) + // Register a node with a service. + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "127.0.0.1", + NodeMeta: map[string]string{ + "key": "value", + }, + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + m := new(dns.Msg) + m.SetQuestion("db.service.consul.", dns.TypeSRV) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + wantAdditional := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "bar.node.dc1.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, + A: []byte{0x7f, 0x0, 0x0, 0x1}, // 127.0.0.1 + }, + } + require.Equal(t, wantAdditional, in.Extra) } diff --git a/agent/dns_test.go b/agent/dns_test.go index 0922939ccc..c5a8c1db2c 100644 --- a/agent/dns_test.go +++ b/agent/dns_test.go @@ -16,7 +16,6 @@ import ( "context" "errors" "fmt" - "github.com/hashicorp/consul/agent/discovery" "math" "math/rand" "net" @@ -34,9 +33,8 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/config" "github.com/hashicorp/consul/agent/consul" - dnsConsul "github.com/hashicorp/consul/agent/dns" "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/sdk/testutil/retry" "github.com/hashicorp/consul/testrpc" ) @@ -119,17 +117,6 @@ func dnsTXT(src string, txt []string) *dns.TXT { } } -func getVersionHCL(enableV2 bool) map[string]string { - versions := map[string]string{ - "DNS: v1 / Catalog: v1": "experiments=[\"v1dns\"]", - } - - if enableV2 { - versions["DNS: v2 / Catalog: v1"] = "" - } - return versions -} - // Copied to agent/dns/recursor_test.go func TestDNS_RecursorAddr(t *testing.T) { addr, err := recursorAddr("8.8.8.8") @@ -190,38 +177,34 @@ func TestDNS_Over_TCP(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "Foo", - Address: "127.0.0.1", - } + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "Foo", + Address: "127.0.0.1", + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - m := new(dns.Msg) - m.SetQuestion("foo.node.dc1.consul.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("foo.node.dc1.consul.", dns.TypeANY) - c := new(dns.Client) - c.Net = "tcp" - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + c.Net = "tcp" + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("empty lookup: %#v", in) - } - }) + if len(in.Answer) != 1 { + t.Fatalf("empty lookup: %#v", in) } } @@ -230,21 +213,17 @@ func TestDNS_EmptyAltDomain(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - m := new(dns.Msg) - m.SetQuestion("consul.service.", dns.TypeA) + m := new(dns.Msg) + m.SetQuestion("consul.service.", dns.TypeA) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Empty(t, in.Answer) - }) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Empty(t, in.Answer) } func TestDNS_CycleRecursorCheck(t *testing.T) { @@ -265,29 +244,25 @@ func TestDNS_CycleRecursorCheck(t *testing.T) { }, }) defer server2.Shutdown() - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - // Mock the agent startup with the necessary configs - agent := NewTestAgent(t, - `recursors = ["`+server1.Addr+`", "`+server2.Addr+`"] - `+experimentsHCL) - defer agent.Shutdown() - // DNS Message init - m := new(dns.Msg) - m.SetQuestion("google.com.", dns.TypeA) - // Agent request - client := new(dns.Client) - in, _, _ := client.Exchange(m, agent.DNSAddr()) - wantAnswer := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "www.google.com.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, - A: []byte{0xAC, 0x15, 0x2D, 0x43}, // 172 , 21, 45, 67 - }, - } - require.NotNil(t, in) - require.Equal(t, wantAnswer, in.Answer) - }) + // Mock the agent startup with the necessary configs + agent := NewTestAgent(t, + `recursors = ["`+server1.Addr+`", "`+server2.Addr+`"] + `) + defer agent.Shutdown() + // DNS Message init + m := new(dns.Msg) + m.SetQuestion("google.com.", dns.TypeA) + // Agent request + client := new(dns.Client) + in, _, _ := client.Exchange(m, agent.DNSAddr()) + wantAnswer := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "www.google.com.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4}, + A: []byte{0xAC, 0x15, 0x2D, 0x43}, // 172 , 21, 45, 67 + }, } + require.NotNil(t, in) + require.Equal(t, wantAnswer, in.Answer) } func TestDNS_CycleRecursorCheckAllFail(t *testing.T) { if testing.Short() { @@ -307,25 +282,21 @@ func TestDNS_CycleRecursorCheckAllFail(t *testing.T) { MsgHdr: dns.MsgHdr{Rcode: dns.RcodeRefused}, }) defer server3.Shutdown() - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - // Mock the agent startup with the necessary configs - agent := NewTestAgent(t, - `recursors = ["`+server1.Addr+`", "`+server2.Addr+`","`+server3.Addr+`"] - `+experimentsHCL) - defer agent.Shutdown() - // DNS dummy message initialization - m := new(dns.Msg) - m.SetQuestion("google.com.", dns.TypeA) - // Agent request - client := new(dns.Client) - in, _, err := client.Exchange(m, agent.DNSAddr()) - require.NoError(t, err) - // Verify if we hit SERVFAIL from Consul - require.NotNil(t, in) - require.Equal(t, dns.RcodeServerFailure, in.Rcode) - }) - } + // Mock the agent startup with the necessary configs + agent := NewTestAgent(t, + `recursors = ["`+server1.Addr+`", "`+server2.Addr+`","`+server3.Addr+`"] + `) + defer agent.Shutdown() + // DNS dummy message initialization + m := new(dns.Msg) + m.SetQuestion("google.com.", dns.TypeA) + // Agent request + client := new(dns.Client) + in, _, err := client.Exchange(m, agent.DNSAddr()) + require.NoError(t, err) + // Verify if we hit SERVFAIL from Consul + require.NotNil(t, in) + require.Equal(t, dns.RcodeServerFailure, in.Rcode) } func TestDNS_EDNS0(t *testing.T) { @@ -333,45 +304,41 @@ func TestDNS_EDNS0(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register node - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.2", - } + // Register node + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.2", + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } - m := new(dns.Msg) - m.SetEdns0(12345, true) - m.SetQuestion("foo.node.dc1.consul.", dns.TypeANY) + m := new(dns.Msg) + m.SetEdns0(12345, true) + m.SetQuestion("foo.node.dc1.consul.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("empty lookup: %#v", in) - } - edns := in.IsEdns0() - if edns == nil { - t.Fatalf("empty edns: %#v", in) - } - if edns.UDPSize() != 12345 { - t.Fatalf("bad edns size: %d", edns.UDPSize()) - } - }) + if len(in.Answer) != 1 { + t.Fatalf("empty lookup: %#v", in) + } + edns := in.IsEdns0() + if edns == nil { + t.Fatalf("empty edns: %#v", in) + } + if edns.UDPSize() != 12345 { + t.Fatalf("bad edns size: %d", edns.UDPSize()) } } @@ -380,95 +347,91 @@ func TestDNS_EDNS0_ECS(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + } - var out struct{} - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - } + var out struct{} + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, - }, - } - require.NoError(t, a.RPC(context.Background(), "PreparedQuery.Apply", args, &id)) - } + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + require.NoError(t, a.RPC(context.Background(), "PreparedQuery.Apply", args, &id)) + } - cases := []struct { - Name string - Question string - SubnetAddr string - SourceNetmask uint8 - ExpectedScope uint8 - }{ - {"global", "db.service.consul.", "198.18.0.1", 32, 0}, - {"query", "test.query.consul.", "198.18.0.1", 32, 32}, - {"query-subnet", "test.query.consul.", "198.18.0.0", 21, 21}, - } + cases := []struct { + Name string + Question string + SubnetAddr string + SourceNetmask uint8 + ExpectedScope uint8 + }{ + {"global", "db.service.consul.", "198.18.0.1", 32, 0}, + {"query", "test.query.consul.", "198.18.0.1", 32, 32}, + {"query-subnet", "test.query.consul.", "198.18.0.0", 21, 21}, + } - for _, tc := range cases { - t.Run(tc.Name, func(t *testing.T) { - c := new(dns.Client) - // Query the service directly - should have a globally valid scope (0) - m := new(dns.Msg) - edns := new(dns.OPT) - edns.Hdr.Name = "." - edns.Hdr.Rrtype = dns.TypeOPT - edns.SetUDPSize(12345) - edns.SetDo(true) - subnetOp := new(dns.EDNS0_SUBNET) - subnetOp.Code = dns.EDNS0SUBNET - subnetOp.Family = 1 - subnetOp.SourceNetmask = tc.SourceNetmask - subnetOp.Address = net.ParseIP(tc.SubnetAddr) - edns.Option = append(edns.Option, subnetOp) - m.Extra = append(m.Extra, edns) - m.SetQuestion(tc.Question, dns.TypeA) + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + c := new(dns.Client) + // Query the service directly - should have a globally valid scope (0) + m := new(dns.Msg) + edns := new(dns.OPT) + edns.Hdr.Name = "." + edns.Hdr.Rrtype = dns.TypeOPT + edns.SetUDPSize(12345) + edns.SetDo(true) + subnetOp := new(dns.EDNS0_SUBNET) + subnetOp.Code = dns.EDNS0SUBNET + subnetOp.Family = 1 + subnetOp.SourceNetmask = tc.SourceNetmask + subnetOp.Address = net.ParseIP(tc.SubnetAddr) + edns.Option = append(edns.Option, subnetOp) + m.Extra = append(m.Extra, edns) + m.SetQuestion(tc.Question, dns.TypeA) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, in.Answer, 1) - aRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok) - require.Equal(t, "127.0.0.1", aRec.A.String()) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok) + require.Equal(t, "127.0.0.1", aRec.A.String()) - optRR := in.IsEdns0() - require.NotNil(t, optRR) - require.Len(t, optRR.Option, 1) + optRR := in.IsEdns0() + require.NotNil(t, optRR) + require.Len(t, optRR.Option, 1) - subnet, ok := optRR.Option[0].(*dns.EDNS0_SUBNET) - require.True(t, ok) - require.Equal(t, uint16(1), subnet.Family) - require.Equal(t, tc.SourceNetmask, subnet.SourceNetmask) - require.Equal(t, tc.ExpectedScope, subnet.SourceScope) - require.Equal(t, net.ParseIP(tc.SubnetAddr), subnet.Address) - }) - } + subnet, ok := optRR.Option[0].(*dns.EDNS0_SUBNET) + require.True(t, ok) + require.Equal(t, uint16(1), subnet.Family) + require.Equal(t, tc.SourceNetmask, subnet.SourceNetmask) + require.Equal(t, tc.ExpectedScope, subnet.SourceScope) + require.Equal(t, net.ParseIP(tc.SubnetAddr), subnet.Address) }) } } @@ -499,19 +462,15 @@ func TestDNS_SOA_Settings(t *testing.T) { require.Equal(t, uint32(retry), soaRec.Retry) require.Equal(t, uint32(ttl), soaRec.Hdr.Ttl) } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - // Default configuration - testSoaWithConfig(experimentsHCL, 0, 86400, 3600, 600) - // Override all settings - testSoaWithConfig("dns_config={soa={min_ttl=60,expire=43200,refresh=1800,retry=300}} "+experimentsHCL, 60, 43200, 1800, 300) - // Override partial settings - testSoaWithConfig("dns_config={soa={min_ttl=60,expire=43200}} "+experimentsHCL, 60, 43200, 3600, 600) - // Override partial settings, part II - testSoaWithConfig("dns_config={soa={refresh=1800,retry=300}} "+experimentsHCL, 0, 86400, 1800, 300) - }) - } + // Default configuration + testSoaWithConfig("", 0, 86400, 3600, 600) + // Override all settings + testSoaWithConfig("dns_config={soa={min_ttl=60,expire=43200,refresh=1800,retry=300}} ", 60, 43200, 1800, 300) + // Override partial settings + testSoaWithConfig("dns_config={soa={min_ttl=60,expire=43200}} ", 60, 43200, 3600, 600) + // Override partial settings, part II + testSoaWithConfig("dns_config={soa={refresh=1800,retry=300}} ", 0, 86400, 1800, 300) } func TestDNS_VirtualIPLookup(t *testing.T) { @@ -519,93 +478,89 @@ func TestDNS_VirtualIPLookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := StartTestAgent(t, TestAgent{HCL: experimentsHCL, Overrides: `peering = { test_allow_peer_registrations = true } log_level = "debug"`}) - defer a.Shutdown() + a := StartTestAgent(t, TestAgent{HCL: "", Overrides: `peering = { test_allow_peer_registrations = true } log_level = "debug"`}) + defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + testrpc.WaitForLeader(t, a.RPC, "dc1") - server, ok := a.delegate.(*consul.Server) - require.True(t, ok) + server, ok := a.delegate.(*consul.Server) + require.True(t, ok) - // The proxy service will not receive a virtual IP if the server is not assigning virtual IPs yet. - retry.Run(t, func(r *retry.R) { - _, entry, err := server.FSM().State().SystemMetadataGet(nil, structs.SystemMetadataVirtualIPsEnabled) - require.NoError(r, err) - require.NotNil(r, entry) - }) + // The proxy service will not receive a virtual IP if the server is not assigning virtual IPs yet. + retry.Run(t, func(r *retry.R) { + _, entry, err := server.FSM().State().SystemMetadataGet(nil, structs.SystemMetadataVirtualIPsEnabled) + require.NoError(r, err) + require.NotNil(r, entry) + }) - type testCase struct { - name string - reg *structs.RegisterRequest - question string - expect string - } + type testCase struct { + name string + reg *structs.RegisterRequest + question string + expect string + } - run := func(t *testing.T, tc testCase) { - var out struct{} - require.Nil(t, a.RPC(context.Background(), "Catalog.Register", tc.reg, &out)) + run := func(t *testing.T, tc testCase) { + var out struct{} + require.Nil(t, a.RPC(context.Background(), "Catalog.Register", tc.reg, &out)) - m := new(dns.Msg) - m.SetQuestion(tc.question, dns.TypeA) + m := new(dns.Msg) + m.SetQuestion(tc.question, dns.TypeA) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.Nil(t, err) - require.Len(t, in.Answer, 1) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.Nil(t, err) + require.Len(t, in.Answer, 1) - aRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok) - require.Equal(t, tc.expect, aRec.A.String()) - } + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok) + require.Equal(t, tc.expect, aRec.A.String()) + } - tt := []testCase{ - { - name: "local query", - reg: &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.55", - Service: &structs.NodeService{ - Kind: structs.ServiceKindConnectProxy, - Service: "web-proxy", - Port: 12345, - Proxy: structs.ConnectProxyConfig{ - DestinationServiceName: "db", - }, - }, + tt := []testCase{ + { + name: "local query", + reg: &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.55", + Service: &structs.NodeService{ + Kind: structs.ServiceKindConnectProxy, + Service: "web-proxy", + Port: 12345, + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "db", }, - question: "db.virtual.consul.", - expect: "240.0.0.1", }, - { - name: "query for imported service", - reg: &structs.RegisterRequest{ - PeerName: "frontend", - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.55", - Service: &structs.NodeService{ - PeerName: "frontend", - Kind: structs.ServiceKindConnectProxy, - Service: "web-proxy", - Port: 12345, - Proxy: structs.ConnectProxyConfig{ - DestinationServiceName: "db", - }, - }, + }, + question: "db.virtual.consul.", + expect: "240.0.0.1", + }, + { + name: "query for imported service", + reg: &structs.RegisterRequest{ + PeerName: "frontend", + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.55", + Service: &structs.NodeService{ + PeerName: "frontend", + Kind: structs.ServiceKindConnectProxy, + Service: "web-proxy", + Port: 12345, + Proxy: structs.ConnectProxyConfig{ + DestinationServiceName: "db", }, - question: "db.virtual.frontend.consul.", - expect: "240.0.0.2", }, - } + }, + question: "db.virtual.frontend.consul.", + expect: "240.0.0.2", + }, + } - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + run(t, tc) }) } } @@ -616,59 +571,55 @@ func TestDNS_InifiniteRecursion(t *testing.T) { } // This test should not create an infinite recursion - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` domain = "CONSUL." node_name = "test node" - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register the initial node with a service - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "web", - Address: "web.service.consul.", - Service: &structs.NodeService{ - Service: "web", - Port: 12345, - Address: "web.service.consul.", - }, - } + // Register the initial node with a service + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "web", + Address: "web.service.consul.", + Service: &structs.NodeService{ + Service: "web", + Port: 12345, + Address: "web.service.consul.", + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - // Look up the service directly - questions := []string{ - "web.service.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeA) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + // Look up the service directly + questions := []string{ + "web.service.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) < 1 { - t.Fatalf("Bad: %#v", in) - } - aRec, ok := in.Answer[0].(*dns.CNAME) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aRec.Target != "web.service.consul." { - t.Fatalf("Bad: %#v, target:=%s", aRec, aRec.Target) - } - } - }) + if len(in.Answer) < 1 { + t.Fatalf("Bad: %#v", in) + } + aRec, ok := in.Answer[0].(*dns.CNAME) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aRec.Target != "web.service.consul." { + t.Fatalf("Bad: %#v, target:=%s", aRec, aRec.Target) + } } } @@ -677,41 +628,37 @@ func TestDNS_NSRecords(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` domain = "CONSUL." node_name = "server1" - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForTestAgent(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") - m := new(dns.Msg) - m.SetQuestion("something.node.consul.", dns.TypeNS) + m := new(dns.Msg) + m.SetQuestion("something.node.consul.", dns.TypeNS) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - wantAnswer := []dns.RR{ - &dns.NS{ - Hdr: dns.RR_Header{Name: "consul.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x13}, - Ns: "server1.node.dc1.consul.", - }, - } - require.Equal(t, wantAnswer, in.Answer, "answer") - wantExtra := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: "server1.node.dc1.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4, Ttl: 0}, - A: net.ParseIP("127.0.0.1").To4(), - }, - } - - require.Equal(t, wantExtra, in.Extra, "extra") - }) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) } + + wantAnswer := []dns.RR{ + &dns.NS{ + Hdr: dns.RR_Header{Name: "consul.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x13}, + Ns: "server1.node.dc1.consul.", + }, + } + require.Equal(t, wantAnswer, in.Answer, "answer") + wantExtra := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: "server1.node.dc1.consul.", Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4, Ttl: 0}, + A: net.ParseIP("127.0.0.1").To4(), + }, + } + + require.Equal(t, wantExtra, in.Extra, "extra") } func TestDNS_AltDomain_NSRecords(t *testing.T) { @@ -719,53 +666,48 @@ func TestDNS_AltDomain_NSRecords(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` domain = "CONSUL." node_name = "server1" alt_domain = "test-domain." - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForTestAgent(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") - questions := []struct { - ask string - domain string - wantDomain string - }{ - {"something.node.consul.", "consul.", "server1.node.dc1.consul."}, - {"something.node.test-domain.", "test-domain.", "server1.node.dc1.test-domain."}, - } + questions := []struct { + ask string + domain string + wantDomain string + }{ + {"something.node.consul.", "consul.", "server1.node.dc1.consul."}, + {"something.node.test-domain.", "test-domain.", "server1.node.dc1.test-domain."}, + } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question.ask, dns.TypeNS) + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question.ask, dns.TypeNS) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - wantAnswer := []dns.RR{ - &dns.NS{ - Hdr: dns.RR_Header{Name: question.domain, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x13}, - Ns: question.wantDomain, - }, - } - require.Equal(t, wantAnswer, in.Answer, "answer") - wantExtra := []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{Name: question.wantDomain, Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4, Ttl: 0}, - A: net.ParseIP("127.0.0.1").To4(), - }, - } + wantAnswer := []dns.RR{ + &dns.NS{ + Hdr: dns.RR_Header{Name: question.domain, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x13}, + Ns: question.wantDomain, + }, + } + require.Equal(t, wantAnswer, in.Answer, "answer") + wantExtra := []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{Name: question.wantDomain, Rrtype: dns.TypeA, Class: dns.ClassINET, Rdlength: 0x4, Ttl: 0}, + A: net.ParseIP("127.0.0.1").To4(), + }, + } - require.Equal(t, wantExtra, in.Extra, "extra") - } - }) + require.Equal(t, wantExtra, in.Extra, "extra") } } @@ -774,42 +716,38 @@ func TestDNS_NSRecords_IPV6(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` domain = "CONSUL." node_name = "server1" advertise_addr = "::1" - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForTestAgent(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") - m := new(dns.Msg) - m.SetQuestion("server1.node.dc1.consul.", dns.TypeNS) + m := new(dns.Msg) + m.SetQuestion("server1.node.dc1.consul.", dns.TypeNS) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - wantAnswer := []dns.RR{ - &dns.NS{ - Hdr: dns.RR_Header{Name: "consul.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x2}, - Ns: "server1.node.dc1.consul.", - }, - } - require.Equal(t, wantAnswer, in.Answer, "answer") - wantExtra := []dns.RR{ - &dns.AAAA{ - Hdr: dns.RR_Header{Name: "server1.node.dc1.consul.", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Rdlength: 0x10, Ttl: 0}, - AAAA: net.ParseIP("::1"), - }, - } - - require.Equal(t, wantExtra, in.Extra, "extra") - }) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) } + + wantAnswer := []dns.RR{ + &dns.NS{ + Hdr: dns.RR_Header{Name: "consul.", Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x2}, + Ns: "server1.node.dc1.consul.", + }, + } + require.Equal(t, wantAnswer, in.Answer, "answer") + wantExtra := []dns.RR{ + &dns.AAAA{ + Hdr: dns.RR_Header{Name: "server1.node.dc1.consul.", Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Rdlength: 0x10, Ttl: 0}, + AAAA: net.ParseIP("::1"), + }, + } + + require.Equal(t, wantExtra, in.Extra, "extra") } func TestDNS_AltDomain_NSRecords_IPV6(t *testing.T) { @@ -817,53 +755,49 @@ func TestDNS_AltDomain_NSRecords_IPV6(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` domain = "CONSUL." node_name = "server1" advertise_addr = "::1" alt_domain = "test-domain." - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForTestAgent(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") - questions := []struct { - ask string - domain string - wantDomain string - }{ - {"server1.node.dc1.consul.", "consul.", "server1.node.dc1.consul."}, - {"server1.node.dc1.test-domain.", "test-domain.", "server1.node.dc1.test-domain."}, - } + questions := []struct { + ask string + domain string + wantDomain string + }{ + {"server1.node.dc1.consul.", "consul.", "server1.node.dc1.consul."}, + {"server1.node.dc1.test-domain.", "test-domain.", "server1.node.dc1.test-domain."}, + } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question.ask, dns.TypeNS) + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question.ask, dns.TypeNS) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - wantAnswer := []dns.RR{ - &dns.NS{ - Hdr: dns.RR_Header{Name: question.domain, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x2}, - Ns: question.wantDomain, - }, - } - require.Equal(t, wantAnswer, in.Answer, "answer") - wantExtra := []dns.RR{ - &dns.AAAA{ - Hdr: dns.RR_Header{Name: question.wantDomain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Rdlength: 0x10, Ttl: 0}, - AAAA: net.ParseIP("::1"), - }, - } + wantAnswer := []dns.RR{ + &dns.NS{ + Hdr: dns.RR_Header{Name: question.domain, Rrtype: dns.TypeNS, Class: dns.ClassINET, Ttl: 0, Rdlength: 0x2}, + Ns: question.wantDomain, + }, + } + require.Equal(t, wantAnswer, in.Answer, "answer") + wantExtra := []dns.RR{ + &dns.AAAA{ + Hdr: dns.RR_Header{Name: question.wantDomain, Rrtype: dns.TypeAAAA, Class: dns.ClassINET, Rdlength: 0x10, Ttl: 0}, + AAAA: net.ParseIP("::1"), + }, + } - require.Equal(t, wantExtra, in.Extra, "extra") - } - }) + require.Equal(t, wantExtra, in.Extra, "extra") } } @@ -872,193 +806,189 @@ func TestDNS_Lookup_TaggedIPAddresses(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + require.NoError(t, a.RPC(context.Background(), "PreparedQuery.Apply", args, &id)) + } + + type testCase struct { + nodeAddress string + nodeTaggedAddresses map[string]string + serviceAddress string + serviceTaggedAddresses map[string]structs.ServiceAddress + + expectedServiceIPv4Address string + expectedServiceIPv6Address string + expectedNodeIPv4Address string + expectedNodeIPv6Address string + } + + cases := map[string]testCase{ + "simple-ipv4": { + serviceAddress: "127.0.0.2", + nodeAddress: "127.0.0.1", + + expectedServiceIPv4Address: "127.0.0.2", + expectedServiceIPv6Address: "", + expectedNodeIPv4Address: "127.0.0.1", + expectedNodeIPv6Address: "", + }, + "simple-ipv6": { + serviceAddress: "::2", + nodeAddress: "::1", + + expectedServiceIPv6Address: "::2", + expectedServiceIPv4Address: "", + expectedNodeIPv6Address: "::1", + expectedNodeIPv4Address: "", + }, + "ipv4-with-tagged-ipv6": { + serviceAddress: "127.0.0.2", + nodeAddress: "127.0.0.1", + + serviceTaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressLANIPv6: {Address: "::2"}, + }, + nodeTaggedAddresses: map[string]string{ + structs.TaggedAddressLANIPv6: "::1", + }, + + expectedServiceIPv4Address: "127.0.0.2", + expectedServiceIPv6Address: "::2", + expectedNodeIPv4Address: "127.0.0.1", + expectedNodeIPv6Address: "::1", + }, + "ipv6-with-tagged-ipv4": { + serviceAddress: "::2", + nodeAddress: "::1", + + serviceTaggedAddresses: map[string]structs.ServiceAddress{ + structs.TaggedAddressLANIPv4: {Address: "127.0.0.2"}, + }, + nodeTaggedAddresses: map[string]string{ + structs.TaggedAddressLANIPv4: "127.0.0.1", + }, + + expectedServiceIPv4Address: "127.0.0.2", + expectedServiceIPv6Address: "::2", + expectedNodeIPv4Address: "127.0.0.1", + expectedNodeIPv6Address: "::1", + }, + } + + for name, tc := range cases { + name := name + tc := tc t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: tc.nodeAddress, + TaggedAddresses: tc.nodeTaggedAddresses, + Service: &structs.NodeService{ + Service: "db", + Address: tc.serviceAddress, + Port: 8080, + TaggedAddresses: tc.serviceTaggedAddresses, + }, + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, - }, + var out struct{} + require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) + + // Look up the SRV record via service and prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) + + c := new(dns.Client) + addr := a.config.DNSAddrs[0].String() + in, _, err := c.Exchange(m, addr) + require.NoError(t, err) + + if tc.expectedServiceIPv4Address != "" { + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok, "Bad: %#v", in.Answer[0]) + require.Equal(t, question, aRec.Hdr.Name) + require.Equal(t, tc.expectedServiceIPv4Address, aRec.A.String()) + } else { + require.Len(t, in.Answer, 0) + } + + m = new(dns.Msg) + m.SetQuestion(question, dns.TypeAAAA) + + c = new(dns.Client) + addr = a.config.DNSAddrs[0].String() + in, _, err = c.Exchange(m, addr) + require.NoError(t, err) + + if tc.expectedServiceIPv6Address != "" { + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.AAAA) + require.True(t, ok, "Bad: %#v", in.Answer[0]) + require.Equal(t, question, aRec.Hdr.Name) + require.Equal(t, tc.expectedServiceIPv6Address, aRec.AAAA.String()) + } else { + require.Len(t, in.Answer, 0) } - require.NoError(t, a.RPC(context.Background(), "PreparedQuery.Apply", args, &id)) } - type testCase struct { - nodeAddress string - nodeTaggedAddresses map[string]string - serviceAddress string - serviceTaggedAddresses map[string]structs.ServiceAddress + // Look up node + m := new(dns.Msg) + m.SetQuestion("foo.node.consul.", dns.TypeA) - expectedServiceIPv4Address string - expectedServiceIPv6Address string - expectedNodeIPv4Address string - expectedNodeIPv6Address string + c := new(dns.Client) + addr := a.config.DNSAddrs[0].String() + in, _, err := c.Exchange(m, addr) + require.NoError(t, err) + + if tc.expectedNodeIPv4Address != "" { + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok, "Bad: %#v", in.Answer[0]) + require.Equal(t, "foo.node.consul.", aRec.Hdr.Name) + require.Equal(t, tc.expectedNodeIPv4Address, aRec.A.String()) + } else { + require.Len(t, in.Answer, 0) } - cases := map[string]testCase{ - "simple-ipv4": { - serviceAddress: "127.0.0.2", - nodeAddress: "127.0.0.1", + m = new(dns.Msg) + m.SetQuestion("foo.node.consul.", dns.TypeAAAA) - expectedServiceIPv4Address: "127.0.0.2", - expectedServiceIPv6Address: "", - expectedNodeIPv4Address: "127.0.0.1", - expectedNodeIPv6Address: "", - }, - "simple-ipv6": { - serviceAddress: "::2", - nodeAddress: "::1", + c = new(dns.Client) + addr = a.config.DNSAddrs[0].String() + in, _, err = c.Exchange(m, addr) + require.NoError(t, err) - expectedServiceIPv6Address: "::2", - expectedServiceIPv4Address: "", - expectedNodeIPv6Address: "::1", - expectedNodeIPv4Address: "", - }, - "ipv4-with-tagged-ipv6": { - serviceAddress: "127.0.0.2", - nodeAddress: "127.0.0.1", - - serviceTaggedAddresses: map[string]structs.ServiceAddress{ - structs.TaggedAddressLANIPv6: {Address: "::2"}, - }, - nodeTaggedAddresses: map[string]string{ - structs.TaggedAddressLANIPv6: "::1", - }, - - expectedServiceIPv4Address: "127.0.0.2", - expectedServiceIPv6Address: "::2", - expectedNodeIPv4Address: "127.0.0.1", - expectedNodeIPv6Address: "::1", - }, - "ipv6-with-tagged-ipv4": { - serviceAddress: "::2", - nodeAddress: "::1", - - serviceTaggedAddresses: map[string]structs.ServiceAddress{ - structs.TaggedAddressLANIPv4: {Address: "127.0.0.2"}, - }, - nodeTaggedAddresses: map[string]string{ - structs.TaggedAddressLANIPv4: "127.0.0.1", - }, - - expectedServiceIPv4Address: "127.0.0.2", - expectedServiceIPv6Address: "::2", - expectedNodeIPv4Address: "127.0.0.1", - expectedNodeIPv6Address: "::1", - }, - } - - for name, tc := range cases { - name := name - tc := tc - t.Run(name, func(t *testing.T) { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: tc.nodeAddress, - TaggedAddresses: tc.nodeTaggedAddresses, - Service: &structs.NodeService{ - Service: "db", - Address: tc.serviceAddress, - Port: 8080, - TaggedAddresses: tc.serviceTaggedAddresses, - }, - } - - var out struct{} - require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) - - // Look up the SRV record via service and prepared query. - questions := []string{ - "db.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeA) - - c := new(dns.Client) - addr := a.config.DNSAddrs[0].String() - in, _, err := c.Exchange(m, addr) - require.NoError(t, err) - - if tc.expectedServiceIPv4Address != "" { - require.Len(t, in.Answer, 1) - aRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok, "Bad: %#v", in.Answer[0]) - require.Equal(t, question, aRec.Hdr.Name) - require.Equal(t, tc.expectedServiceIPv4Address, aRec.A.String()) - } else { - require.Len(t, in.Answer, 0) - } - - m = new(dns.Msg) - m.SetQuestion(question, dns.TypeAAAA) - - c = new(dns.Client) - addr = a.config.DNSAddrs[0].String() - in, _, err = c.Exchange(m, addr) - require.NoError(t, err) - - if tc.expectedServiceIPv6Address != "" { - require.Len(t, in.Answer, 1) - aRec, ok := in.Answer[0].(*dns.AAAA) - require.True(t, ok, "Bad: %#v", in.Answer[0]) - require.Equal(t, question, aRec.Hdr.Name) - require.Equal(t, tc.expectedServiceIPv6Address, aRec.AAAA.String()) - } else { - require.Len(t, in.Answer, 0) - } - } - - // Look up node - m := new(dns.Msg) - m.SetQuestion("foo.node.consul.", dns.TypeA) - - c := new(dns.Client) - addr := a.config.DNSAddrs[0].String() - in, _, err := c.Exchange(m, addr) - require.NoError(t, err) - - if tc.expectedNodeIPv4Address != "" { - require.Len(t, in.Answer, 1) - aRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok, "Bad: %#v", in.Answer[0]) - require.Equal(t, "foo.node.consul.", aRec.Hdr.Name) - require.Equal(t, tc.expectedNodeIPv4Address, aRec.A.String()) - } else { - require.Len(t, in.Answer, 0) - } - - m = new(dns.Msg) - m.SetQuestion("foo.node.consul.", dns.TypeAAAA) - - c = new(dns.Client) - addr = a.config.DNSAddrs[0].String() - in, _, err = c.Exchange(m, addr) - require.NoError(t, err) - - if tc.expectedNodeIPv6Address != "" { - require.Len(t, in.Answer, 1) - aRec, ok := in.Answer[0].(*dns.AAAA) - require.True(t, ok, "Bad: %#v", in.Answer[0]) - require.Equal(t, "foo.node.consul.", aRec.Hdr.Name) - require.Equal(t, tc.expectedNodeIPv6Address, aRec.AAAA.String()) - } else { - require.Len(t, in.Answer, 0) - } - }) + if tc.expectedNodeIPv6Address != "" { + require.Len(t, in.Answer, 1) + aRec, ok := in.Answer[0].(*dns.AAAA) + require.True(t, ok, "Bad: %#v", in.Answer[0]) + require.Equal(t, "foo.node.consul.", aRec.Hdr.Name) + require.Equal(t, tc.expectedNodeIPv6Address, aRec.AAAA.String()) + } else { + require.Len(t, in.Answer, 0) } }) } @@ -1069,133 +999,129 @@ func TestDNS_PreparedQueryNearIPEDNS(t *testing.T) { t.Skip("too slow for testing.Short") } - ipCoord := lib.GenerateCoordinate(1 * time.Millisecond) + ipCoord := librtt.GenerateCoordinate(1 * time.Millisecond) serviceNodes := []struct { name string address string coord *coordinate.Coordinate }{ - {"foo1", "198.18.0.1", lib.GenerateCoordinate(1 * time.Millisecond)}, - {"foo2", "198.18.0.2", lib.GenerateCoordinate(10 * time.Millisecond)}, - {"foo3", "198.18.0.3", lib.GenerateCoordinate(30 * time.Millisecond)}, + {"foo1", "198.18.0.1", librtt.GenerateCoordinate(1 * time.Millisecond)}, + {"foo2", "198.18.0.2", librtt.GenerateCoordinate(10 * time.Millisecond)}, + {"foo3", "198.18.0.3", librtt.GenerateCoordinate(30 * time.Millisecond)}, } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - added := 0 + added := 0 - // Register nodes with a service - for _, cfg := range serviceNodes { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: cfg.name, - Address: cfg.address, - Service: &structs.NodeService{ - Service: "db", - Port: 12345, - }, - } + // Register nodes with a service + for _, cfg := range serviceNodes { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: cfg.name, + Address: cfg.address, + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + }, + } - var out struct{} - err := a.RPC(context.Background(), "Catalog.Register", args, &out) - require.NoError(t, err) + var out struct{} + err := a.RPC(context.Background(), "Catalog.Register", args, &out) + require.NoError(t, err) - // Send coordinate updates - coordArgs := structs.CoordinateUpdateRequest{ - Datacenter: "dc1", - Node: cfg.name, - Coord: cfg.coord, - } - err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) - require.NoError(t, err) + // Send coordinate updates + coordArgs := structs.CoordinateUpdateRequest{ + Datacenter: "dc1", + Node: cfg.name, + Coord: cfg.coord, + } + err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) + require.NoError(t, err) - added += 1 - } - - fmt.Printf("Added %d service nodes\n", added) - - // Register a node without a service - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "198.18.0.9", - } - - var out struct{} - err := a.RPC(context.Background(), "Catalog.Register", args, &out) - require.NoError(t, err) - - // Send coordinate updates for a few nodes. - coordArgs := structs.CoordinateUpdateRequest{ - Datacenter: "dc1", - Node: "bar", - Coord: ipCoord, - } - err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) - require.NoError(t, err) - } - - // Register a prepared query Near = _ip - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "some.query.we.like", - Service: structs.ServiceQuery{ - Service: "db", - Near: "_ip", - }, - }, - } - - var id string - err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id) - require.NoError(t, err) - } - retry.Run(t, func(r *retry.R) { - m := new(dns.Msg) - m.SetQuestion("some.query.we.like.query.consul.", dns.TypeA) - m.SetEdns0(4096, false) - o := new(dns.OPT) - o.Hdr.Name = "." - o.Hdr.Rrtype = dns.TypeOPT - e := new(dns.EDNS0_SUBNET) - e.Code = dns.EDNS0SUBNET - e.Family = 1 - e.SourceNetmask = 32 - e.SourceScope = 0 - e.Address = net.ParseIP("198.18.0.9").To4() - o.Option = append(o.Option, e) - m.Extra = append(m.Extra, o) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - r.Fatalf("Error with call to dns.Client.Exchange: %s", err) - } - - if len(serviceNodes) != len(in.Answer) { - r.Fatalf("Expecting %d A RRs in response, Actual found was %d", len(serviceNodes), len(in.Answer)) - } - - for i, rr := range in.Answer { - if aRec, ok := rr.(*dns.A); ok { - if actual := aRec.A.String(); serviceNodes[i].address != actual { - r.Fatalf("Expecting A RR #%d = %s, Actual RR was %s", i, serviceNodes[i].address, actual) - } - } else { - r.Fatalf("DNS Answer contained a non-A RR") - } - } - }) - }) + added += 1 } + + fmt.Printf("Added %d service nodes\n", added) + + // Register a node without a service + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "198.18.0.9", + } + + var out struct{} + err := a.RPC(context.Background(), "Catalog.Register", args, &out) + require.NoError(t, err) + + // Send coordinate updates for a few nodes. + coordArgs := structs.CoordinateUpdateRequest{ + Datacenter: "dc1", + Node: "bar", + Coord: ipCoord, + } + err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) + require.NoError(t, err) + } + + // Register a prepared query Near = _ip + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "some.query.we.like", + Service: structs.ServiceQuery{ + Service: "db", + Near: "_ip", + }, + }, + } + + var id string + err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id) + require.NoError(t, err) + } + retry.Run(t, func(r *retry.R) { + m := new(dns.Msg) + m.SetQuestion("some.query.we.like.query.consul.", dns.TypeA) + m.SetEdns0(4096, false) + o := new(dns.OPT) + o.Hdr.Name = "." + o.Hdr.Rrtype = dns.TypeOPT + e := new(dns.EDNS0_SUBNET) + e.Code = dns.EDNS0SUBNET + e.Family = 1 + e.SourceNetmask = 32 + e.SourceScope = 0 + e.Address = net.ParseIP("198.18.0.9").To4() + o.Option = append(o.Option, e) + m.Extra = append(m.Extra, o) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + r.Fatalf("Error with call to dns.Client.Exchange: %s", err) + } + + if len(serviceNodes) != len(in.Answer) { + r.Fatalf("Expecting %d A RRs in response, Actual found was %d", len(serviceNodes), len(in.Answer)) + } + + for i, rr := range in.Answer { + if aRec, ok := rr.(*dns.A); ok { + if actual := aRec.A.String(); serviceNodes[i].address != actual { + r.Fatalf("Expecting A RR #%d = %s, Actual RR was %s", i, serviceNodes[i].address, actual) + } + } else { + r.Fatalf("DNS Answer contained a non-A RR") + } + } + }) } func TestDNS_PreparedQueryNearIP(t *testing.T) { @@ -1203,122 +1129,118 @@ func TestDNS_PreparedQueryNearIP(t *testing.T) { t.Skip("too slow for testing.Short") } - ipCoord := lib.GenerateCoordinate(1 * time.Millisecond) + ipCoord := librtt.GenerateCoordinate(1 * time.Millisecond) serviceNodes := []struct { name string address string coord *coordinate.Coordinate }{ - {"foo1", "198.18.0.1", lib.GenerateCoordinate(1 * time.Millisecond)}, - {"foo2", "198.18.0.2", lib.GenerateCoordinate(10 * time.Millisecond)}, - {"foo3", "198.18.0.3", lib.GenerateCoordinate(30 * time.Millisecond)}, + {"foo1", "198.18.0.1", librtt.GenerateCoordinate(1 * time.Millisecond)}, + {"foo2", "198.18.0.2", librtt.GenerateCoordinate(10 * time.Millisecond)}, + {"foo3", "198.18.0.3", librtt.GenerateCoordinate(30 * time.Millisecond)}, } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - added := 0 + added := 0 - // Register nodes with a service - for _, cfg := range serviceNodes { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: cfg.name, - Address: cfg.address, - Service: &structs.NodeService{ - Service: "db", - Port: 12345, - }, - } + // Register nodes with a service + for _, cfg := range serviceNodes { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: cfg.name, + Address: cfg.address, + Service: &structs.NodeService{ + Service: "db", + Port: 12345, + }, + } - var out struct{} - err := a.RPC(context.Background(), "Catalog.Register", args, &out) - require.NoError(t, err) + var out struct{} + err := a.RPC(context.Background(), "Catalog.Register", args, &out) + require.NoError(t, err) - // Send coordinate updates - coordArgs := structs.CoordinateUpdateRequest{ - Datacenter: "dc1", - Node: cfg.name, - Coord: cfg.coord, - } - err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) - require.NoError(t, err) + // Send coordinate updates + coordArgs := structs.CoordinateUpdateRequest{ + Datacenter: "dc1", + Node: cfg.name, + Coord: cfg.coord, + } + err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) + require.NoError(t, err) - added += 1 - } - - fmt.Printf("Added %d service nodes\n", added) - - // Register a node without a service - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "bar", - Address: "198.18.0.9", - } - - var out struct{} - err := a.RPC(context.Background(), "Catalog.Register", args, &out) - require.NoError(t, err) - - // Send coordinate updates for a few nodes. - coordArgs := structs.CoordinateUpdateRequest{ - Datacenter: "dc1", - Node: "bar", - Coord: ipCoord, - } - err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) - require.NoError(t, err) - } - - // Register a prepared query Near = _ip - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "some.query.we.like", - Service: structs.ServiceQuery{ - Service: "db", - Near: "_ip", - }, - }, - } - - var id string - err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id) - require.NoError(t, err) - } - - retry.Run(t, func(r *retry.R) { - m := new(dns.Msg) - m.SetQuestion("some.query.we.like.query.consul.", dns.TypeA) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - r.Fatalf("Error with call to dns.Client.Exchange: %s", err) - } - - if len(serviceNodes) != len(in.Answer) { - r.Fatalf("Expecting %d A RRs in response, Actual found was %d", len(serviceNodes), len(in.Answer)) - } - - for i, rr := range in.Answer { - if aRec, ok := rr.(*dns.A); ok { - if actual := aRec.A.String(); serviceNodes[i].address != actual { - r.Fatalf("Expecting A RR #%d = %s, Actual RR was %s", i, serviceNodes[i].address, actual) - } - } else { - r.Fatalf("DNS Answer contained a non-A RR") - } - } - }) - }) + added += 1 } + + fmt.Printf("Added %d service nodes\n", added) + + // Register a node without a service + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "bar", + Address: "198.18.0.9", + } + + var out struct{} + err := a.RPC(context.Background(), "Catalog.Register", args, &out) + require.NoError(t, err) + + // Send coordinate updates for a few nodes. + coordArgs := structs.CoordinateUpdateRequest{ + Datacenter: "dc1", + Node: "bar", + Coord: ipCoord, + } + err = a.RPC(context.Background(), "Coordinate.Update", &coordArgs, &out) + require.NoError(t, err) + } + + // Register a prepared query Near = _ip + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "some.query.we.like", + Service: structs.ServiceQuery{ + Service: "db", + Near: "_ip", + }, + }, + } + + var id string + err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id) + require.NoError(t, err) + } + + retry.Run(t, func(r *retry.R) { + m := new(dns.Msg) + m.SetQuestion("some.query.we.like.query.consul.", dns.TypeA) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + r.Fatalf("Error with call to dns.Client.Exchange: %s", err) + } + + if len(serviceNodes) != len(in.Answer) { + r.Fatalf("Expecting %d A RRs in response, Actual found was %d", len(serviceNodes), len(in.Answer)) + } + + for i, rr := range in.Answer { + if aRec, ok := rr.(*dns.A); ok { + if actual := aRec.A.String(); serviceNodes[i].address != actual { + r.Fatalf("Expecting A RR #%d = %s, Actual RR was %s", i, serviceNodes[i].address, actual) + } + } else { + r.Fatalf("DNS Answer contained a non-A RR") + } + } + }) } func TestDNS_Recurse(t *testing.T) { @@ -1331,31 +1253,28 @@ func TestDNS_Recurse(t *testing.T) { }) defer recursor.Shutdown() - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` recursors = ["`+recursor.Addr+`"] - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - m := new(dns.Msg) - m.SetQuestion("apple.com.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("apple.com.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(in.Answer) == 0 { - t.Fatalf("Bad: %#v", in) - } - if in.Rcode != dns.RcodeSuccess { - t.Fatalf("Bad: %#v", in) - } - }) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) } + + if len(in.Answer) == 0 { + t.Fatalf("Bad: %#v", in) + } + if in.Rcode != dns.RcodeSuccess { + t.Fatalf("Bad: %#v", in) + } + } func TestDNS_Recurse_Truncation(t *testing.T) { @@ -1369,32 +1288,28 @@ func TestDNS_Recurse_Truncation(t *testing.T) { }) defer recursor.Shutdown() - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` recursors = ["`+recursor.Addr+`"] - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - m := new(dns.Msg) - m.SetQuestion("apple.com.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("apple.com.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - if in.Truncated != true { - t.Fatalf("err: message should have been truncated %v", in) - } - if len(in.Answer) == 0 { - t.Fatalf("Bad: Truncated message ignored, expected some reply %#v", in) - } - if in.Rcode != dns.RcodeSuccess { - t.Fatalf("Bad: %#v", in) - } - }) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + if in.Truncated != true { + t.Fatalf("err: message should have been truncated %v", in) + } + if len(in.Answer) == 0 { + t.Fatalf("Bad: Truncated message ignored, expected some reply %#v", in) + } + if in.Rcode != dns.RcodeSuccess { + t.Fatalf("Bad: %#v", in) } } @@ -1417,43 +1332,39 @@ func TestDNS_RecursorTimeout(t *testing.T) { } defer resolver.Close() - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` recursors = ["`+resolver.LocalAddr().String()+`"] // host must cause a connection|read|write timeout dns_config { recursor_timeout = "`+serverClientTimeout.String()+`" } - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - m := new(dns.Msg) - m.SetQuestion("apple.com.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("apple.com.", dns.TypeANY) - // This client calling the server under test must have a longer timeout than the one we set internally - c := &dns.Client{Timeout: testClientTimeout} + // This client calling the server under test must have a longer timeout than the one we set internally + c := &dns.Client{Timeout: testClientTimeout} - start := time.Now() - in, _, err := c.Exchange(m, a.DNSAddr()) + start := time.Now() + in, _, err := c.Exchange(m, a.DNSAddr()) - duration := time.Since(start) + duration := time.Since(start) - if err != nil { - t.Fatalf("err: %v", err) - } + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 0 { - t.Fatalf("Bad: %#v", in) - } - if in.Rcode != dns.RcodeServerFailure { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 0 { + t.Fatalf("Bad: %#v", in) + } + if in.Rcode != dns.RcodeServerFailure { + t.Fatalf("Bad: %#v", in) + } - if duration < serverClientTimeout { - t.Fatalf("Expected the call to return after at least %f seconds but lasted only %f", serverClientTimeout.Seconds(), duration.Seconds()) - } - }) + if duration < serverClientTimeout { + t.Fatalf("Expected the call to return after at least %f seconds but lasted only %f", serverClientTimeout.Seconds(), duration.Seconds()) } } @@ -1504,115 +1415,111 @@ func TestDNS_TCP_and_UDP_Truncate(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` dns_config { enable_truncate = true } - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - services := []string{"normal", "truncated"} - for index, service := range services { - numServices := (index * 5000) + 2 - var eg errgroup.Group - for i := 1; i < numServices; i++ { - j := i - eg.Go(func() error { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: fmt.Sprintf("%s-%d.acme.com", service, j), - Address: fmt.Sprintf("127.%d.%d.%d", 0, (j / 255), j%255), - Service: &structs.NodeService{ - Service: service, - Port: 8000, - }, - } - - var out struct{} - return a.RPC(context.Background(), "Catalog.Register", args, &out) - }) - } - if err := eg.Wait(); err != nil { - t.Fatalf("error registering: %v", err) + services := []string{"normal", "truncated"} + for index, service := range services { + numServices := (index * 5000) + 2 + var eg errgroup.Group + for i := 1; i < numServices; i++ { + j := i + eg.Go(func() error { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: fmt.Sprintf("%s-%d.acme.com", service, j), + Address: fmt.Sprintf("127.%d.%d.%d", 0, (j / 255), j%255), + Service: &structs.NodeService{ + Service: service, + Port: 8000, + }, } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: service, - Service: structs.ServiceQuery{ - Service: service, - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + return a.RPC(context.Background(), "Catalog.Register", args, &out) + }) + } + if err := eg.Wait(); err != nil { + t.Fatalf("error registering: %v", err) + } - // Look up the service directly and via prepared query. Ensure the - // response is truncated each time. - questions := []string{ - fmt.Sprintf("%s.service.consul.", service), - id + ".query.consul.", - } - protocols := []string{ - "tcp", - "udp", - } - for _, maxSize := range []uint16{8192, 65535} { - for _, qType := range []uint16{dns.TypeANY, dns.TypeA, dns.TypeSRV} { - for _, question := range questions { - for _, protocol := range protocols { - for _, compress := range []bool{true, false} { - t.Run(fmt.Sprintf("lookup %s %s (qType:=%d) compressed=%v", question, protocol, qType, compress), func(t *testing.T) { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeANY) - maxSz := maxSize - if protocol == "udp" { - maxSz = 8192 - } - m.SetEdns0(maxSz, true) - c := new(dns.Client) - c.Net = protocol - m.Compress = compress - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - // actually check if we need to have the truncate bit - resbuf, err := in.Pack() - if err != nil { - t.Fatalf("Error while packing answer: %s", err) - } - if !in.Truncated && len(resbuf) > int(maxSz) { - t.Fatalf("should have truncate bit %#v %#v", in, len(in.Answer)) - } - // Check for the truncate bit - buf, err := m.Pack() - info := fmt.Sprintf("service %s question:=%s (%s) (%d total records) sz:= %d in %v", - service, question, protocol, numServices, len(in.Answer), in) - if err != nil { - t.Fatalf("Error while packing: %v ; info:=%s", err, info) - } - if len(buf) > int(maxSz) { - t.Fatalf("len(buf) := %d > maxSz=%d for %v", len(buf), maxSz, info) - } - }) + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: service, + Service: structs.ServiceQuery{ + Service: service, + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Look up the service directly and via prepared query. Ensure the + // response is truncated each time. + questions := []string{ + fmt.Sprintf("%s.service.consul.", service), + id + ".query.consul.", + } + protocols := []string{ + "tcp", + "udp", + } + for _, maxSize := range []uint16{8192, 65535} { + for _, qType := range []uint16{dns.TypeANY, dns.TypeA, dns.TypeSRV} { + for _, question := range questions { + for _, protocol := range protocols { + for _, compress := range []bool{true, false} { + t.Run(fmt.Sprintf("lookup %s %s (qType:=%d) compressed=%v", question, protocol, qType, compress), func(t *testing.T) { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) + maxSz := maxSize + if protocol == "udp" { + maxSz = 8192 } - } + m.SetEdns0(maxSz, true) + c := new(dns.Client) + c.Net = protocol + m.Compress = compress + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + // actually check if we need to have the truncate bit + resbuf, err := in.Pack() + if err != nil { + t.Fatalf("Error while packing answer: %s", err) + } + if !in.Truncated && len(resbuf) > int(maxSz) { + t.Fatalf("should have truncate bit %#v %#v", in, len(in.Answer)) + } + // Check for the truncate bit + buf, err := m.Pack() + info := fmt.Sprintf("service %s question:=%s (%s) (%d total records) sz:= %d in %v", + service, question, protocol, numServices, len(in.Answer), in) + if err != nil { + t.Fatalf("Error while packing: %v ; info:=%s", err, info) + } + if len(buf) > int(maxSz) { + t.Fatalf("len(buf) := %d > maxSz=%d for %v", len(buf), maxSz, info) + } + }) } } } } - }) + } } } @@ -1621,37 +1528,33 @@ func TestDNS_AddressLookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Look up the addresses - cases := map[string]string{ - "7f000001.addr.dc1.consul.": "127.0.0.1", - } - for question, answer := range cases { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeA) + // Look up the addresses + cases := map[string]string{ + "7f000001.addr.dc1.consul.": "127.0.0.1", + } + for question, answer := range cases { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - require.Len(t, in.Answer, 1) + require.Len(t, in.Answer, 1) - require.Equal(t, dns.TypeA, in.Answer[0].Header().Rrtype) - aRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok) - require.Equal(t, aRec.A.To4().String(), answer) - require.Zero(t, aRec.Hdr.Ttl) - require.Nil(t, in.Ns) - require.Nil(t, in.Extra) - } - }) + require.Equal(t, dns.TypeA, in.Answer[0].Header().Rrtype) + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok) + require.Equal(t, aRec.A.To4().String(), answer) + require.Zero(t, aRec.Hdr.Ttl) + require.Nil(t, in.Ns) + require.Nil(t, in.Extra) } } @@ -1660,33 +1563,29 @@ func TestDNS_AddressLookupANY(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Look up the addresses - cases := map[string]string{ - "7f000001.addr.dc1.consul.": "127.0.0.1", - } - for question, answer := range cases { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeANY) + // Look up the addresses + cases := map[string]string{ + "7f000001.addr.dc1.consul.": "127.0.0.1", + } + for question, answer := range cases { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Len(t, in.Answer, 1) - require.Equal(t, in.Answer[0].Header().Rrtype, dns.TypeA) - aRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok) - require.Equal(t, aRec.A.To4().String(), answer) - require.Zero(t, aRec.Hdr.Ttl) + require.NoError(t, err) + require.Len(t, in.Answer, 1) + require.Equal(t, in.Answer[0].Header().Rrtype, dns.TypeA) + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok) + require.Equal(t, aRec.A.To4().String(), answer) + require.Zero(t, aRec.Hdr.Ttl) - } - }) } } @@ -1695,34 +1594,30 @@ func TestDNS_AddressLookupInvalidType(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Look up the addresses - cases := map[string]string{ - "7f000001.addr.dc1.consul.": "", - } - for question := range cases { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + // Look up the addresses + cases := map[string]string{ + "7f000001.addr.dc1.consul.": "", + } + for question := range cases { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - require.NoError(t, err) - require.Zero(t, in.Rcode) - require.Nil(t, in.Answer) - require.NotNil(t, in.Extra) - require.Len(t, in.Extra, 1) - aRecord := in.Extra[0].(*dns.A) - require.Equal(t, "7f000001.addr.dc1.consul.", aRecord.Hdr.Name) - require.Equal(t, dns.TypeA, aRecord.Hdr.Rrtype) - require.Zero(t, aRecord.Hdr.Ttl) - require.Equal(t, "127.0.0.1", aRecord.A.String()) - } - }) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + require.NoError(t, err) + require.Zero(t, in.Rcode) + require.Nil(t, in.Answer) + require.NotNil(t, in.Extra) + require.Len(t, in.Extra, 1) + aRecord := in.Extra[0].(*dns.A) + require.Equal(t, "7f000001.addr.dc1.consul.", aRecord.Hdr.Name) + require.Equal(t, dns.TypeA, aRecord.Hdr.Rrtype) + require.Zero(t, aRecord.Hdr.Ttl) + require.Equal(t, "127.0.0.1", aRecord.A.String()) } } @@ -1731,46 +1626,42 @@ func TestDNS_AddressLookupIPV6(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Look up the addresses - cases := map[string]string{ - "2607002040050808000000000000200e.addr.consul.": "2607:20:4005:808::200e", - "2607112040051808ffffffffffff200e.addr.consul.": "2607:1120:4005:1808:ffff:ffff:ffff:200e", - } - for question, answer := range cases { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeAAAA) + // Look up the addresses + cases := map[string]string{ + "2607002040050808000000000000200e.addr.consul.": "2607:20:4005:808::200e", + "2607112040051808ffffffffffff200e.addr.consul.": "2607:1120:4005:1808:ffff:ffff:ffff:200e", + } + for question, answer := range cases { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeAAAA) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - if in.Answer[0].Header().Rrtype != dns.TypeAAAA { - t.Fatalf("Invalid type: %#v", in.Answer[0]) - } - aaaaRec, ok := in.Answer[0].(*dns.AAAA) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if aaaaRec.AAAA.To16().String() != answer { - t.Fatalf("Bad: %#v", aaaaRec) - } - if aaaaRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - } - }) + if in.Answer[0].Header().Rrtype != dns.TypeAAAA { + t.Fatalf("Invalid type: %#v", in.Answer[0]) + } + aaaaRec, ok := in.Answer[0].(*dns.AAAA) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if aaaaRec.AAAA.To16().String() != answer { + t.Fatalf("Bad: %#v", aaaaRec) + } + if aaaaRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Answer[0]) + } } } @@ -1779,32 +1670,28 @@ func TestDNS_AddressLookupIPV6InvalidType(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Look up the addresses - cases := map[string]string{ - "2607002040050808000000000000200e.addr.consul.": "2607:20:4005:808::200e", - "2607112040051808ffffffffffff200e.addr.consul.": "2607:1120:4005:1808:ffff:ffff:ffff:200e", - } - for question := range cases { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + // Look up the addresses + cases := map[string]string{ + "2607002040050808000000000000200e.addr.consul.": "2607:20:4005:808::200e", + "2607112040051808ffffffffffff200e.addr.consul.": "2607:1120:4005:1808:ffff:ffff:ffff:200e", + } + for question := range cases { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if in.Answer != nil { - t.Fatalf("Bad: %#v", in) - } - } - }) + if in.Answer != nil { + t.Fatalf("Bad: %#v", in) + } } } @@ -1816,31 +1703,27 @@ func TestDNS_NonExistentDC_Server(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - m := new(dns.Msg) - m.SetQuestion("consul.service.dc2.consul.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("consul.service.dc2.consul.", dns.TypeANY) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - require.Equal(t, dns.RcodeNameError, in.Rcode) - require.Equal(t, 0, len(in.Answer)) - require.Equal(t, 0, len(in.Extra)) - require.Equal(t, 1, len(in.Ns)) - soa := in.Ns[0].(*dns.SOA) - require.Equal(t, "consul.", soa.Hdr.Name) - require.Equal(t, "ns.consul.", soa.Ns) - require.Equal(t, "hostmaster.consul.", soa.Mbox) - }) + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) } + + require.Equal(t, dns.RcodeNameError, in.Rcode) + require.Equal(t, 0, len(in.Answer)) + require.Equal(t, 0, len(in.Extra)) + require.Equal(t, 1, len(in.Ns)) + soa := in.Ns[0].(*dns.SOA) + require.Equal(t, "consul.", soa.Hdr.Name) + require.Equal(t, "ns.consul.", soa.Ns) + require.Equal(t, "hostmaster.consul.", soa.Mbox) } // TestDNS_NonExistentDC_RPC verifies NXDOMAIN is returned when @@ -1851,39 +1734,35 @@ func TestDNS_NonExistentDC_RPC(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - s := NewTestAgent(t, ` + s := NewTestAgent(t, ` node_name = "test-server" - `+experimentsHCL) + `) - defer s.Shutdown() - c := NewTestAgent(t, ` + defer s.Shutdown() + c := NewTestAgent(t, ` node_name = "test-client" bootstrap = false server = false - `+experimentsHCL) - defer c.Shutdown() + `) + defer c.Shutdown() - // Join LAN cluster - addr := fmt.Sprintf("127.0.0.1:%d", s.Config.SerfPortLAN) - _, err := c.JoinLAN([]string{addr}, nil) - require.NoError(t, err) - testrpc.WaitForTestAgent(t, c.RPC, "dc1") + // Join LAN cluster + addr := fmt.Sprintf("127.0.0.1:%d", s.Config.SerfPortLAN) + _, err := c.JoinLAN([]string{addr}, nil) + require.NoError(t, err) + testrpc.WaitForTestAgent(t, c.RPC, "dc1") - m := new(dns.Msg) - m.SetQuestion("consul.service.dc2.consul.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("consul.service.dc2.consul.", dns.TypeANY) - d := new(dns.Client) - in, _, err := d.Exchange(m, c.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + d := new(dns.Client) + in, _, err := d.Exchange(m, c.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if in.Rcode != dns.RcodeNameError { - t.Fatalf("Expected RCode: %#v, had: %#v", dns.RcodeNameError, in.Rcode) - } - }) + if in.Rcode != dns.RcodeNameError { + t.Fatalf("Expected RCode: %#v, had: %#v", dns.RcodeNameError, in.Rcode) } } @@ -1892,15 +1771,148 @@ func TestDNS_NonExistentLookup(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // lookup a non-existing node, we should receive a SOA + // lookup a non-existing node, we should receive a SOA + m := new(dns.Msg) + m.SetQuestion("nonexisting.consul.", dns.TypeANY) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + if len(in.Ns) != 1 { + t.Fatalf("Bad: %#v %#v", in, len(in.Answer)) + } + + soaRec, ok := in.Ns[0].(*dns.SOA) + if !ok { + t.Fatalf("Bad: %#v", in.Ns[0]) + } + if soaRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Ns[0]) + } +} + +func TestDNS_NonExistentLookupEmptyAorAAAA(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") + + // Register a v6-only service and a v4-only service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foov6", + Address: "fe80::1", + Service: &structs.NodeService{ + Service: "webv6", + Port: 8000, + }, + } + + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + + args = &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foov4", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "webv4", + Port: 8000, + }, + } + + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Register equivalent prepared queries. + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "webv4", + Service: structs.ServiceQuery{ + Service: "webv4", + }, + }, + } + + var id string + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + + args = &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "webv6", + Service: structs.ServiceQuery{ + Service: "webv6", + }, + }, + } + + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } + + // Check for ipv6 records on ipv4-only service directly and via the + // prepared query. + questions := []string{ + "webv4.service.consul.", + "webv4.query.consul.", + } + for _, question := range questions { + t.Run(question, func(t *testing.T) { m := new(dns.Msg) - m.SetQuestion("nonexisting.consul.", dns.TypeANY) + m.SetQuestion(question, dns.TypeAAAA) + + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } + + require.Len(t, in.Ns, 1) + soaRec, ok := in.Ns[0].(*dns.SOA) + if !ok { + t.Fatalf("Bad: %#v", in.Ns[0]) + } + if soaRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Ns[0]) + } + + require.Equal(t, dns.RcodeSuccess, in.Rcode) + }) + } + + // Check for ipv4 records on ipv6-only service directly and via the + // prepared query. + questions = []string{ + "webv6.service.consul.", + "webv6.query.consul.", + } + for _, question := range questions { + t.Run(question, func(t *testing.T) { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) c := new(dns.Client) in, _, err := c.Exchange(m, a.DNSAddr()) @@ -1909,7 +1921,7 @@ func TestDNS_NonExistentLookup(t *testing.T) { } if len(in.Ns) != 1 { - t.Fatalf("Bad: %#v %#v", in, len(in.Answer)) + t.Fatalf("Bad: %#v", in) } soaRec, ok := in.Ns[0].(*dns.SOA) @@ -1919,150 +1931,9 @@ func TestDNS_NonExistentLookup(t *testing.T) { if soaRec.Hdr.Ttl != 0 { t.Fatalf("Bad: %#v", in.Ns[0]) } - }) - } -} -func TestDNS_NonExistentLookupEmptyAorAAAA(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - // Register a v6-only service and a v4-only service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foov6", - Address: "fe80::1", - Service: &structs.NodeService{ - Service: "webv6", - Port: 8000, - }, - } - - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - - args = &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foov4", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "webv4", - Port: 8000, - }, - } - - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Register equivalent prepared queries. - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "webv4", - Service: structs.ServiceQuery{ - Service: "webv4", - }, - }, - } - - var id string - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - - args = &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "webv6", - Service: structs.ServiceQuery{ - Service: "webv6", - }, - }, - } - - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } - - // Check for ipv6 records on ipv4-only service directly and via the - // prepared query. - questions := []string{ - "webv4.service.consul.", - "webv4.query.consul.", - } - for _, question := range questions { - t.Run(question, func(t *testing.T) { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeAAAA) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - require.Len(t, in.Ns, 1) - soaRec, ok := in.Ns[0].(*dns.SOA) - if !ok { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - if soaRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - - require.Equal(t, dns.RcodeSuccess, in.Rcode) - }) - } - - // Check for ipv4 records on ipv6-only service directly and via the - // prepared query. - questions = []string{ - "webv6.service.consul.", - "webv6.query.consul.", - } - for _, question := range questions { - t.Run(question, func(t *testing.T) { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeA) - - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } - - if len(in.Ns) != 1 { - t.Fatalf("Bad: %#v", in) - } - - soaRec, ok := in.Ns[0].(*dns.SOA) - if !ok { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - if soaRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - - if in.Rcode != dns.RcodeSuccess { - t.Fatalf("Bad: %#v", in) - } - }) + if in.Rcode != dns.RcodeSuccess { + t.Fatalf("Bad: %#v", in) } }) } @@ -2073,96 +1944,92 @@ func TestDNS_AltDomains_Service(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` alt_domain = "test-domain." - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "test-node", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - NodeMeta: map[string]string{ - "key": "value", - }, - } + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "test-node", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + NodeMeta: map[string]string{ + "key": "value", + }, + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - questions := []struct { - ask string - wantDomain string - }{ - {"db.service.consul.", "test-node.node.dc1.consul."}, - {"db.service.test-domain.", "test-node.node.dc1.test-domain."}, - {"db.service.dc1.consul.", "test-node.node.dc1.consul."}, - {"db.service.dc1.test-domain.", "test-node.node.dc1.test-domain."}, - } + questions := []struct { + ask string + wantDomain string + }{ + {"db.service.consul.", "test-node.node.dc1.consul."}, + {"db.service.test-domain.", "test-node.node.dc1.test-domain."}, + {"db.service.dc1.consul.", "test-node.node.dc1.consul."}, + {"db.service.dc1.test-domain.", "test-node.node.dc1.test-domain."}, + } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question.ask, dns.TypeSRV) + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question.ask, dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - srvRec, ok := in.Answer[0].(*dns.SRV) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } - if srvRec.Port != 12345 { - t.Fatalf("Bad: %#v", srvRec) - } - if got, want := srvRec.Target, question.wantDomain; got != want { - t.Fatalf("SRV target invalid, got %v want %v", got, want) - } + srvRec, ok := in.Answer[0].(*dns.SRV) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } + if srvRec.Port != 12345 { + t.Fatalf("Bad: %#v", srvRec) + } + if got, want := srvRec.Target, question.wantDomain; got != want { + t.Fatalf("SRV target invalid, got %v want %v", got, want) + } - aRec, ok := in.Extra[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[0]) - } + aRec, ok := in.Extra[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[0]) + } - if got, want := aRec.Hdr.Name, question.wantDomain; got != want { - t.Fatalf("A record header invalid, got %v want %v", got, want) - } + if got, want := aRec.Hdr.Name, question.wantDomain; got != want { + t.Fatalf("A record header invalid, got %v want %v", got, want) + } - if aRec.A.String() != "127.0.0.1" { - t.Fatalf("Bad: %#v", in.Extra[0]) - } + if aRec.A.String() != "127.0.0.1" { + t.Fatalf("Bad: %#v", in.Extra[0]) + } - txtRec, ok := in.Extra[1].(*dns.TXT) - if !ok { - t.Fatalf("Bad: %#v", in.Extra[1]) - } - if got, want := txtRec.Hdr.Name, question.wantDomain; got != want { - t.Fatalf("TXT record header invalid, got %v want %v", got, want) - } - if txtRec.Txt[0] != "key=value" { - t.Fatalf("Bad: %#v", in.Extra[1]) - } - } - }) + txtRec, ok := in.Extra[1].(*dns.TXT) + if !ok { + t.Fatalf("Bad: %#v", in.Extra[1]) + } + if got, want := txtRec.Hdr.Name, question.wantDomain; got != want { + t.Fatalf("TXT record header invalid, got %v want %v", got, want) + } + if txtRec.Txt[0] != "key=value" { + t.Fatalf("Bad: %#v", in.Extra[1]) + } } } @@ -2171,50 +2038,46 @@ func TestDNS_AltDomains_SOA(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` node_name = "test-node" alt_domain = "test-domain." - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - questions := []struct { - ask string - want_domain string - }{ - {"test-node.node.consul.", "consul."}, - {"test-node.node.test-domain.", "test-domain."}, - } + questions := []struct { + ask string + want_domain string + }{ + {"test-node.node.consul.", "consul."}, + {"test-node.node.test-domain.", "test-domain."}, + } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question.ask, dns.TypeSOA) + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question.ask, dns.TypeSOA) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Answer) != 1 { + t.Fatalf("Bad: %#v", in) + } - soaRec, ok := in.Answer[0].(*dns.SOA) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + soaRec, ok := in.Answer[0].(*dns.SOA) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - if got, want := soaRec.Hdr.Name, question.want_domain; got != want { - t.Fatalf("SOA name invalid, got %q want %q", got, want) - } - if got, want := soaRec.Ns, ("ns." + question.want_domain); got != want { - t.Fatalf("SOA ns invalid, got %q want %q", got, want) - } - } - }) + if got, want := soaRec.Hdr.Name, question.want_domain; got != want { + t.Fatalf("SOA name invalid, got %q want %q", got, want) + } + if got, want := soaRec.Ns, ("ns." + question.want_domain); got != want { + t.Fatalf("SOA ns invalid, got %q want %q", got, want) + } } } @@ -2226,46 +2089,42 @@ func TestDNS_AltDomains_Overlap(t *testing.T) { // this tests the domain matching logic in DNSServer when encountering more // than one potential match (i.e. ambiguous match) // it should select the longer matching domain when dispatching - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` node_name = "test-node" alt_domain = "test.consul." - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - questions := []string{ - "test-node.node.consul.", - "test-node.node.test.consul.", - "test-node.node.dc1.consul.", - "test-node.node.dc1.test.consul.", - } + questions := []string{ + "test-node.node.consul.", + "test-node.node.test.consul.", + "test-node.node.dc1.consul.", + "test-node.node.dc1.test.consul.", + } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeA) + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Answer) != 1 { - t.Fatalf("failed to resolve ambiguous alt domain %q: %#v", question, in) - } + if len(in.Answer) != 1 { + t.Fatalf("failed to resolve ambiguous alt domain %q: %#v", question, in) + } - aRec, ok := in.Answer[0].(*dns.A) - if !ok { - t.Fatalf("Bad: %#v", in.Answer[0]) - } + aRec, ok := in.Answer[0].(*dns.A) + if !ok { + t.Fatalf("Bad: %#v", in.Answer[0]) + } - if got, want := aRec.A.To4().String(), "127.0.0.1"; got != want { - t.Fatalf("A ip invalid, got %v want %v", got, want) - } - } - }) + if got, want := aRec.A.To4().String(), "127.0.0.1"; got != want { + t.Fatalf("A ip invalid, got %v want %v", got, want) + } } } @@ -2276,38 +2135,34 @@ func TestDNS_AltDomain_DCName_Overlap(t *testing.T) { // this tests the DC name overlap with the consul domain/alt-domain // we should get response when DC suffix is a prefix of consul alt-domain - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` datacenter = "dc-test" node_name = "test-node" alt_domain = "test.consul." - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc-test") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc-test") - questions := []string{ - "test-node.node.dc-test.consul.", - "test-node.node.dc-test.test.consul.", - } + questions := []string{ + "test-node.node.dc-test.consul.", + "test-node.node.dc-test.test.consul.", + } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeA) + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeA) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - require.Len(t, in.Answer, 1) + require.Len(t, in.Answer, 1) - aRec, ok := in.Answer[0].(*dns.A) - require.True(t, ok) - require.Equal(t, aRec.A.To4().String(), "127.0.0.1") - } - }) + aRec, ok := in.Answer[0].(*dns.A) + require.True(t, ok) + require.Equal(t, aRec.A.To4().String(), "127.0.0.1") } } @@ -2316,54 +2171,50 @@ func TestDNS_PreparedQuery_AllowStale(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` dns_config { allow_stale = true max_stale = "1s" } - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - m := MockPreparedQuery{ - executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { - // Return a response that's perpetually too stale. - reply.LastContact = 2 * time.Second - return nil - }, - } + m := MockPreparedQuery{ + executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { + // Return a response that's perpetually too stale. + reply.LastContact = 2 * time.Second + return nil + }, + } - if err := a.registerEndpoint("PreparedQuery", &m); err != nil { - t.Fatalf("err: %v", err) - } + if err := a.registerEndpoint("PreparedQuery", &m); err != nil { + t.Fatalf("err: %v", err) + } - // Make sure that the lookup terminates and results in an SOA since - // the query doesn't exist. - { - m := new(dns.Msg) - m.SetQuestion("nope.query.consul.", dns.TypeSRV) + // Make sure that the lookup terminates and results in an SOA since + // the query doesn't exist. + { + m := new(dns.Msg) + m.SetQuestion("nope.query.consul.", dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Ns) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Ns) != 1 { + t.Fatalf("Bad: %#v", in) + } - soaRec, ok := in.Ns[0].(*dns.SOA) - if !ok { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - if soaRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - } - }) + soaRec, ok := in.Ns[0].(*dns.SOA) + if !ok { + t.Fatalf("Bad: %#v", in.Ns[0]) + } + if soaRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Ns[0]) + } } } @@ -2372,46 +2223,42 @@ func TestDNS_InvalidQueries(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - // Try invalid forms of queries that should hit the special invalid case - // of our query parser. - questions := []string{ - "consul.", - "node.consul.", - "service.consul.", - "query.consul.", - "foo.node.dc1.extra.more.consul.", - "foo.service.dc1.extra.more.consul.", - "foo.query.dc1.extra.more.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + // Try invalid forms of queries that should hit the special invalid case + // of our query parser. + questions := []string{ + "consul.", + "node.consul.", + "service.consul.", + "query.consul.", + "foo.node.dc1.extra.more.consul.", + "foo.service.dc1.extra.more.consul.", + "foo.query.dc1.extra.more.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - if len(in.Ns) != 1 { - t.Fatalf("Bad: %#v", in) - } + if len(in.Ns) != 1 { + t.Fatalf("Bad: %#v", in) + } - soaRec, ok := in.Ns[0].(*dns.SOA) - if !ok { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - if soaRec.Hdr.Ttl != 0 { - t.Fatalf("Bad: %#v", in.Ns[0]) - } - } - }) + soaRec, ok := in.Ns[0].(*dns.SOA) + if !ok { + t.Fatalf("Bad: %#v", in.Ns[0]) + } + if soaRec.Hdr.Ttl != 0 { + t.Fatalf("Bad: %#v", in.Ns[0]) + } } } @@ -2420,38 +2267,34 @@ func TestDNS_PreparedQuery_AgentSource(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - m := MockPreparedQuery{ - executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { - // Check that the agent inserted its self-name and datacenter to - // the RPC request body. - if args.Agent.Datacenter != a.Config.Datacenter || - args.Agent.Node != a.Config.NodeName { - t.Fatalf("bad: %#v", args.Agent) - } - return nil - }, + m := MockPreparedQuery{ + executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { + // Check that the agent inserted its self-name and datacenter to + // the RPC request body. + if args.Agent.Datacenter != a.Config.Datacenter || + args.Agent.Node != a.Config.NodeName { + t.Fatalf("bad: %#v", args.Agent) } + return nil + }, + } - if err := a.registerEndpoint("PreparedQuery", &m); err != nil { - t.Fatalf("err: %v", err) - } + if err := a.registerEndpoint("PreparedQuery", &m); err != nil { + t.Fatalf("err: %v", err) + } - { - m := new(dns.Msg) - m.SetQuestion("foo.query.consul.", dns.TypeSRV) + { + m := new(dns.Msg) + m.SetQuestion("foo.query.consul.", dns.TypeSRV) - c := new(dns.Client) - if _, _, err := c.Exchange(m, a.DNSAddr()); err != nil { - t.Fatalf("err: %v", err) - } - } - }) + c := new(dns.Client) + if _, _, err := c.Exchange(m, a.DNSAddr()); err != nil { + t.Fatalf("err: %v", err) + } } } @@ -2460,255 +2303,234 @@ func TestDNS_EDNS_Truncate_AgentSource(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` dns_config { enable_truncate = true } - `+experimentsHCL) - defer a.Shutdown() - a.DNSDisableCompression(true) - testrpc.WaitForLeader(t, a.RPC, "dc1") + `) + defer a.Shutdown() + a.DNSDisableCompression(true) + testrpc.WaitForLeader(t, a.RPC, "dc1") - m := MockPreparedQuery{ - executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { - // Check that the agent inserted its self-name and datacenter to - // the RPC request body. - if args.Agent.Datacenter != a.Config.Datacenter || - args.Agent.Node != a.Config.NodeName { - t.Fatalf("bad: %#v", args.Agent) - } - for i := 0; i < 100; i++ { - reply.Nodes = append(reply.Nodes, structs.CheckServiceNode{Node: &structs.Node{Node: "apple", Address: fmt.Sprintf("node.address:%d", i)}, Service: &structs.NodeService{Service: "appleService", Address: fmt.Sprintf("service.address:%d", i)}}) - } - return nil - }, + m := MockPreparedQuery{ + executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { + // Check that the agent inserted its self-name and datacenter to + // the RPC request body. + if args.Agent.Datacenter != a.Config.Datacenter || + args.Agent.Node != a.Config.NodeName { + t.Fatalf("bad: %#v", args.Agent) } - - if err := a.registerEndpoint("PreparedQuery", &m); err != nil { - t.Fatalf("err: %v", err) + for i := 0; i < 100; i++ { + reply.Nodes = append(reply.Nodes, structs.CheckServiceNode{Node: &structs.Node{Node: "apple", Address: fmt.Sprintf("node.address:%d", i)}, Service: &structs.NodeService{Service: "appleService", Address: fmt.Sprintf("service.address:%d", i)}}) } - - req := new(dns.Msg) - req.SetQuestion("foo.query.consul.", dns.TypeSRV) - req.SetEdns0(2048, true) - req.Compress = false - - c := new(dns.Client) - resp, _, err := c.Exchange(req, a.DNSAddr()) - require.NoError(t, err) - require.True(t, resp.Len() < 2048) - }) + return nil + }, } + + if err := a.registerEndpoint("PreparedQuery", &m); err != nil { + t.Fatalf("err: %v", err) + } + + req := new(dns.Msg) + req.SetQuestion("foo.query.consul.", dns.TypeSRV) + req.SetEdns0(2048, true) + req.Compress = false + + c := new(dns.Client) + resp, _, err := c.Exchange(req, a.DNSAddr()) + require.NoError(t, err) + require.True(t, resp.Len() < 2048) } func TestDNS_trimUDPResponse_NoTrim(t *testing.T) { - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { + req := &dns.Msg{} + resp := &dns.Msg{ + Answer: []dns.RR{ + &dns.SRV{ + Hdr: dns.RR_Header{ + Name: "redis-cache-redis.service.consul.", + Rrtype: dns.TypeSRV, + Class: dns.ClassINET, + }, + Target: "ip-10-0-1-185.node.dc1.consul.", + }, + }, + Extra: []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{ + Name: "ip-10-0-1-185.node.dc1.consul.", + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: net.ParseIP("10.0.1.185"), + }, + }, + } - req := &dns.Msg{} - resp := &dns.Msg{ - Answer: []dns.RR{ - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Target: "ip-10-0-1-185.node.dc1.consul.", - }, - }, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "ip-10-0-1-185.node.dc1.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("10.0.1.185"), - }, - }, - } + cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `) + if trimmed := trimUDPResponse(req, resp, cfg.DNSUDPAnswerLimit); trimmed { + t.Fatalf("Bad %#v", *resp) + } - cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `+experimentsHCL) - if trimmed := trimUDPResponse(req, resp, cfg.DNSUDPAnswerLimit); trimmed { - t.Fatalf("Bad %#v", *resp) - } - - expected := &dns.Msg{ - Answer: []dns.RR{ - &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Target: "ip-10-0-1-185.node.dc1.consul.", - }, + expected := &dns.Msg{ + Answer: []dns.RR{ + &dns.SRV{ + Hdr: dns.RR_Header{ + Name: "redis-cache-redis.service.consul.", + Rrtype: dns.TypeSRV, + Class: dns.ClassINET, }, - Extra: []dns.RR{ - &dns.A{ - Hdr: dns.RR_Header{ - Name: "ip-10-0-1-185.node.dc1.consul.", - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP("10.0.1.185"), - }, + Target: "ip-10-0-1-185.node.dc1.consul.", + }, + }, + Extra: []dns.RR{ + &dns.A{ + Hdr: dns.RR_Header{ + Name: "ip-10-0-1-185.node.dc1.consul.", + Rrtype: dns.TypeA, + Class: dns.ClassINET, }, - } - if !reflect.DeepEqual(resp, expected) { - t.Fatalf("Bad %#v vs. %#v", *resp, *expected) - } - }) + A: net.ParseIP("10.0.1.185"), + }, + }, + } + if !reflect.DeepEqual(resp, expected) { + t.Fatalf("Bad %#v vs. %#v", *resp, *expected) } } func TestDNS_trimUDPResponse_TrimLimit(t *testing.T) { - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `+experimentsHCL) + cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `) - req, resp, expected := &dns.Msg{}, &dns.Msg{}, &dns.Msg{} - for i := 0; i < cfg.DNSUDPAnswerLimit+1; i++ { - target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 185+i) - srv := &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Target: target, - } - a := &dns.A{ - Hdr: dns.RR_Header{ - Name: target, - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 185+i)), - } + req, resp, expected := &dns.Msg{}, &dns.Msg{}, &dns.Msg{} + for i := 0; i < cfg.DNSUDPAnswerLimit+1; i++ { + target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 185+i) + srv := &dns.SRV{ + Hdr: dns.RR_Header{ + Name: "redis-cache-redis.service.consul.", + Rrtype: dns.TypeSRV, + Class: dns.ClassINET, + }, + Target: target, + } + a := &dns.A{ + Hdr: dns.RR_Header{ + Name: target, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 185+i)), + } - resp.Answer = append(resp.Answer, srv) - resp.Extra = append(resp.Extra, a) - if i < cfg.DNSUDPAnswerLimit { - expected.Answer = append(expected.Answer, srv) - expected.Extra = append(expected.Extra, a) - } - } + resp.Answer = append(resp.Answer, srv) + resp.Extra = append(resp.Extra, a) + if i < cfg.DNSUDPAnswerLimit { + expected.Answer = append(expected.Answer, srv) + expected.Extra = append(expected.Extra, a) + } + } - if trimmed := trimUDPResponse(req, resp, cfg.DNSUDPAnswerLimit); !trimmed { - t.Fatalf("Bad %#v", *resp) - } - if !reflect.DeepEqual(resp, expected) { - t.Fatalf("Bad %#v vs. %#v", *resp, *expected) - } - }) + if trimmed := trimUDPResponse(req, resp, cfg.DNSUDPAnswerLimit); !trimmed { + t.Fatalf("Bad %#v", *resp) + } + if !reflect.DeepEqual(resp, expected) { + t.Fatalf("Bad %#v vs. %#v", *resp, *expected) } } func TestDNS_trimUDPResponse_TrimLimitWithNS(t *testing.T) { - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `+experimentsHCL) + cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `) - req, resp, expected := &dns.Msg{}, &dns.Msg{}, &dns.Msg{} - for i := 0; i < cfg.DNSUDPAnswerLimit+1; i++ { - target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 185+i) - srv := &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Target: target, - } - a := &dns.A{ - Hdr: dns.RR_Header{ - Name: target, - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 185+i)), - } - ns := &dns.SOA{ - Hdr: dns.RR_Header{ - Name: target, - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - }, - Ns: fmt.Sprintf("soa-%d", i), - } + req, resp, expected := &dns.Msg{}, &dns.Msg{}, &dns.Msg{} + for i := 0; i < cfg.DNSUDPAnswerLimit+1; i++ { + target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 185+i) + srv := &dns.SRV{ + Hdr: dns.RR_Header{ + Name: "redis-cache-redis.service.consul.", + Rrtype: dns.TypeSRV, + Class: dns.ClassINET, + }, + Target: target, + } + a := &dns.A{ + Hdr: dns.RR_Header{ + Name: target, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 185+i)), + } + ns := &dns.SOA{ + Hdr: dns.RR_Header{ + Name: target, + Rrtype: dns.TypeSOA, + Class: dns.ClassINET, + }, + Ns: fmt.Sprintf("soa-%d", i), + } - resp.Answer = append(resp.Answer, srv) - resp.Extra = append(resp.Extra, a) - resp.Ns = append(resp.Ns, ns) - if i < cfg.DNSUDPAnswerLimit { - expected.Answer = append(expected.Answer, srv) - expected.Extra = append(expected.Extra, a) - } - } - - if trimmed := trimUDPResponse(req, resp, cfg.DNSUDPAnswerLimit); !trimmed { - t.Fatalf("Bad %#v", *resp) - } - require.LessOrEqual(t, resp.Len(), defaultMaxUDPSize) - require.Len(t, resp.Ns, 0) - }) + resp.Answer = append(resp.Answer, srv) + resp.Extra = append(resp.Extra, a) + resp.Ns = append(resp.Ns, ns) + if i < cfg.DNSUDPAnswerLimit { + expected.Answer = append(expected.Answer, srv) + expected.Extra = append(expected.Extra, a) + } } + + if trimmed := trimUDPResponse(req, resp, cfg.DNSUDPAnswerLimit); !trimmed { + t.Fatalf("Bad %#v", *resp) + } + require.LessOrEqual(t, resp.Len(), defaultMaxUDPSize) + require.Len(t, resp.Ns, 0) } func TestDNS_trimTCPResponse_TrimLimitWithNS(t *testing.T) { - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `+experimentsHCL) + cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `) - req, resp, expected := &dns.Msg{}, &dns.Msg{}, &dns.Msg{} - for i := 0; i < 5000; i++ { - target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 185+i) - srv := &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Target: target, - } - a := &dns.A{ - Hdr: dns.RR_Header{ - Name: target, - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 185+i)), - } - ns := &dns.SOA{ - Hdr: dns.RR_Header{ - Name: target, - Rrtype: dns.TypeSOA, - Class: dns.ClassINET, - }, - Ns: fmt.Sprintf("soa-%d", i), - } + req, resp, expected := &dns.Msg{}, &dns.Msg{}, &dns.Msg{} + for i := 0; i < 5000; i++ { + target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 185+i) + srv := &dns.SRV{ + Hdr: dns.RR_Header{ + Name: "redis-cache-redis.service.consul.", + Rrtype: dns.TypeSRV, + Class: dns.ClassINET, + }, + Target: target, + } + a := &dns.A{ + Hdr: dns.RR_Header{ + Name: target, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 185+i)), + } + ns := &dns.SOA{ + Hdr: dns.RR_Header{ + Name: target, + Rrtype: dns.TypeSOA, + Class: dns.ClassINET, + }, + Ns: fmt.Sprintf("soa-%d", i), + } - resp.Answer = append(resp.Answer, srv) - resp.Extra = append(resp.Extra, a) - resp.Ns = append(resp.Ns, ns) - if i < cfg.DNSUDPAnswerLimit { - expected.Answer = append(expected.Answer, srv) - expected.Extra = append(expected.Extra, a) - } - } - req.Question = append(req.Question, dns.Question{Qtype: dns.TypeSRV}) - - if trimmed := trimTCPResponse(req, resp); !trimmed { - t.Fatalf("Bad %#v", *resp) - } - require.LessOrEqual(t, resp.Len(), 65523) - require.Len(t, resp.Ns, 0) - }) + resp.Answer = append(resp.Answer, srv) + resp.Extra = append(resp.Extra, a) + resp.Ns = append(resp.Ns, ns) + if i < cfg.DNSUDPAnswerLimit { + expected.Answer = append(expected.Answer, srv) + expected.Extra = append(expected.Extra, a) + } } + req.Question = append(req.Question, dns.Question{Qtype: dns.TypeSRV}) + + if trimmed := trimTCPResponse(req, resp); !trimmed { + t.Fatalf("Bad %#v", *resp) + } + require.LessOrEqual(t, resp.Len(), 65523) + require.Len(t, resp.Ns, 0) } func loadRuntimeConfig(t *testing.T, hcl string) *config.RuntimeConfig { @@ -2720,193 +2542,179 @@ func loadRuntimeConfig(t *testing.T, hcl string) *config.RuntimeConfig { } func TestDNS_trimUDPResponse_TrimSize(t *testing.T) { - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `+experimentsHCL) + cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `) - req, resp := &dns.Msg{}, &dns.Msg{} - for i := 0; i < 100; i++ { - target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 185+i) - srv := &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Target: target, - } - a := &dns.A{ - Hdr: dns.RR_Header{ - Name: target, - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 185+i)), - } + req, resp := &dns.Msg{}, &dns.Msg{} + for i := 0; i < 100; i++ { + target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 185+i) + srv := &dns.SRV{ + Hdr: dns.RR_Header{ + Name: "redis-cache-redis.service.consul.", + Rrtype: dns.TypeSRV, + Class: dns.ClassINET, + }, + Target: target, + } + a := &dns.A{ + Hdr: dns.RR_Header{ + Name: target, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 185+i)), + } - resp.Answer = append(resp.Answer, srv) - resp.Extra = append(resp.Extra, a) - } + resp.Answer = append(resp.Answer, srv) + resp.Extra = append(resp.Extra, a) + } - // We don't know the exact trim, but we know the resulting answer - // data should match its extra data. - if trimmed := trimUDPResponse(req, resp, cfg.DNSUDPAnswerLimit); !trimmed { - t.Fatalf("Bad %#v", *resp) - } - if len(resp.Answer) == 0 || len(resp.Answer) != len(resp.Extra) { - t.Fatalf("Bad %#v", *resp) - } - for i := range resp.Answer { - srv, ok := resp.Answer[i].(*dns.SRV) - if !ok { - t.Fatalf("should be SRV") - } + // We don't know the exact trim, but we know the resulting answer + // data should match its extra data. + if trimmed := trimUDPResponse(req, resp, cfg.DNSUDPAnswerLimit); !trimmed { + t.Fatalf("Bad %#v", *resp) + } + if len(resp.Answer) == 0 || len(resp.Answer) != len(resp.Extra) { + t.Fatalf("Bad %#v", *resp) + } + for i := range resp.Answer { + srv, ok := resp.Answer[i].(*dns.SRV) + if !ok { + t.Fatalf("should be SRV") + } - a, ok := resp.Extra[i].(*dns.A) - if !ok { - t.Fatalf("should be A") - } + a, ok := resp.Extra[i].(*dns.A) + if !ok { + t.Fatalf("should be A") + } - if srv.Target != a.Header().Name { - t.Fatalf("Bad %#v vs. %#v", *srv, *a) - } - } - }) + if srv.Target != a.Header().Name { + t.Fatalf("Bad %#v vs. %#v", *srv, *a) + } } } func TestDNS_trimUDPResponse_TrimSizeEDNS(t *testing.T) { - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { + cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `) - cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `+experimentsHCL) + req, resp := &dns.Msg{}, &dns.Msg{} - req, resp := &dns.Msg{}, &dns.Msg{} + for i := 0; i < 100; i++ { + target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 150+i) + srv := &dns.SRV{ + Hdr: dns.RR_Header{ + Name: "redis-cache-redis.service.consul.", + Rrtype: dns.TypeSRV, + Class: dns.ClassINET, + }, + Target: target, + } + a := &dns.A{ + Hdr: dns.RR_Header{ + Name: target, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 150+i)), + } - for i := 0; i < 100; i++ { - target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 150+i) - srv := &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Target: target, - } - a := &dns.A{ - Hdr: dns.RR_Header{ - Name: target, - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 150+i)), - } + resp.Answer = append(resp.Answer, srv) + resp.Extra = append(resp.Extra, a) + } - resp.Answer = append(resp.Answer, srv) - resp.Extra = append(resp.Extra, a) - } + // Copy over to a new slice since we are trimming both. + reqEDNS, respEDNS := &dns.Msg{}, &dns.Msg{} + reqEDNS.SetEdns0(2048, true) + respEDNS.Answer = append(respEDNS.Answer, resp.Answer...) + respEDNS.Extra = append(respEDNS.Extra, resp.Extra...) - // Copy over to a new slice since we are trimming both. - reqEDNS, respEDNS := &dns.Msg{}, &dns.Msg{} - reqEDNS.SetEdns0(2048, true) - respEDNS.Answer = append(respEDNS.Answer, resp.Answer...) - respEDNS.Extra = append(respEDNS.Extra, resp.Extra...) + // Trim each response + if trimmed := trimUDPResponse(req, resp, cfg.DNSUDPAnswerLimit); !trimmed { + t.Errorf("expected response to be trimmed: %#v", resp) + } + if trimmed := trimUDPResponse(reqEDNS, respEDNS, cfg.DNSUDPAnswerLimit); !trimmed { + t.Errorf("expected edns to be trimmed: %#v", resp) + } - // Trim each response - if trimmed := trimUDPResponse(req, resp, cfg.DNSUDPAnswerLimit); !trimmed { - t.Errorf("expected response to be trimmed: %#v", resp) - } - if trimmed := trimUDPResponse(reqEDNS, respEDNS, cfg.DNSUDPAnswerLimit); !trimmed { - t.Errorf("expected edns to be trimmed: %#v", resp) - } + // Check answer lengths + if len(resp.Answer) == 0 || len(resp.Answer) != len(resp.Extra) { + t.Errorf("bad response answer length: %#v", resp) + } + if len(respEDNS.Answer) == 0 || len(respEDNS.Answer) != len(respEDNS.Extra) { + t.Errorf("bad edns answer length: %#v", resp) + } - // Check answer lengths - if len(resp.Answer) == 0 || len(resp.Answer) != len(resp.Extra) { - t.Errorf("bad response answer length: %#v", resp) - } - if len(respEDNS.Answer) == 0 || len(respEDNS.Answer) != len(respEDNS.Extra) { - t.Errorf("bad edns answer length: %#v", resp) - } + // Due to the compression, we can't check exact equality of sizes, but we can + // make two requests and ensure that the edns one returns a larger payload + // than the non-edns0 one. + if len(resp.Answer) >= len(respEDNS.Answer) { + t.Errorf("expected edns have larger answer: %#v\n%#v", resp, respEDNS) + } + if len(resp.Extra) >= len(respEDNS.Extra) { + t.Errorf("expected edns have larger extra: %#v\n%#v", resp, respEDNS) + } - // Due to the compression, we can't check exact equality of sizes, but we can - // make two requests and ensure that the edns one returns a larger payload - // than the non-edns0 one. - if len(resp.Answer) >= len(respEDNS.Answer) { - t.Errorf("expected edns have larger answer: %#v\n%#v", resp, respEDNS) - } - if len(resp.Extra) >= len(respEDNS.Extra) { - t.Errorf("expected edns have larger extra: %#v\n%#v", resp, respEDNS) - } + // Verify that the things point where they should + for i := range resp.Answer { + srv, ok := resp.Answer[i].(*dns.SRV) + if !ok { + t.Errorf("%d should be an SRV", i) + } - // Verify that the things point where they should - for i := range resp.Answer { - srv, ok := resp.Answer[i].(*dns.SRV) - if !ok { - t.Errorf("%d should be an SRV", i) - } + a, ok := resp.Extra[i].(*dns.A) + if !ok { + t.Errorf("%d should be an A", i) + } - a, ok := resp.Extra[i].(*dns.A) - if !ok { - t.Errorf("%d should be an A", i) - } - - if srv.Target != a.Header().Name { - t.Errorf("%d: bad %#v vs. %#v", i, srv, a) - } - } - }) + if srv.Target != a.Header().Name { + t.Errorf("%d: bad %#v vs. %#v", i, srv, a) + } } } func TestDNS_trimUDPResponse_TrimSizeMaxSize(t *testing.T) { - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { + cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `) - cfg := loadRuntimeConfig(t, `node_name = "test" data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `+experimentsHCL) + resp := &dns.Msg{} - resp := &dns.Msg{} + for i := 0; i < 600; i++ { + target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 150+i) + srv := &dns.SRV{ + Hdr: dns.RR_Header{ + Name: "redis-cache-redis.service.consul.", + Rrtype: dns.TypeSRV, + Class: dns.ClassINET, + }, + Target: target, + } + a := &dns.A{ + Hdr: dns.RR_Header{ + Name: target, + Rrtype: dns.TypeA, + Class: dns.ClassINET, + }, + A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 150+i)), + } - for i := 0; i < 600; i++ { - target := fmt.Sprintf("ip-10-0-1-%d.node.dc1.consul.", 150+i) - srv := &dns.SRV{ - Hdr: dns.RR_Header{ - Name: "redis-cache-redis.service.consul.", - Rrtype: dns.TypeSRV, - Class: dns.ClassINET, - }, - Target: target, - } - a := &dns.A{ - Hdr: dns.RR_Header{ - Name: target, - Rrtype: dns.TypeA, - Class: dns.ClassINET, - }, - A: net.ParseIP(fmt.Sprintf("10.0.1.%d", 150+i)), - } + resp.Answer = append(resp.Answer, srv) + resp.Extra = append(resp.Extra, a) + } - resp.Answer = append(resp.Answer, srv) - resp.Extra = append(resp.Extra, a) - } + reqEDNS, respEDNS := &dns.Msg{}, &dns.Msg{} + reqEDNS.SetEdns0(math.MaxUint16, true) + respEDNS.Answer = append(respEDNS.Answer, resp.Answer...) + respEDNS.Extra = append(respEDNS.Extra, resp.Extra...) + require.Greater(t, respEDNS.Len(), math.MaxUint16) + t.Logf("length is: %v", respEDNS.Len()) - reqEDNS, respEDNS := &dns.Msg{}, &dns.Msg{} - reqEDNS.SetEdns0(math.MaxUint16, true) - respEDNS.Answer = append(respEDNS.Answer, resp.Answer...) - respEDNS.Extra = append(respEDNS.Extra, resp.Extra...) - require.Greater(t, respEDNS.Len(), math.MaxUint16) - t.Logf("length is: %v", respEDNS.Len()) + if trimmed := trimUDPResponse(reqEDNS, respEDNS, cfg.DNSUDPAnswerLimit); !trimmed { + t.Errorf("expected edns to be trimmed: %#v", resp) + } + require.Greater(t, math.MaxUint16, respEDNS.Len()) - if trimmed := trimUDPResponse(reqEDNS, respEDNS, cfg.DNSUDPAnswerLimit); !trimmed { - t.Errorf("expected edns to be trimmed: %#v", resp) - } - require.Greater(t, math.MaxUint16, respEDNS.Len()) + t.Logf("length is: %v", respEDNS.Len()) - t.Logf("length is: %v", respEDNS.Len()) - - if len(respEDNS.Answer) == 0 || len(respEDNS.Answer) != len(respEDNS.Extra) { - t.Errorf("bad edns answer length: %#v", resp) - } - }) + if len(respEDNS.Answer) == 0 || len(respEDNS.Answer) != len(respEDNS.Extra) { + t.Errorf("bad edns answer length: %#v", resp) } } @@ -3134,25 +2942,20 @@ func TestDNS_syncExtra(t *testing.T) { } func TestDNS_Compression_trimUDPResponse(t *testing.T) { - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { + cfg := loadRuntimeConfig(t, `data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `) - cfg := loadRuntimeConfig(t, `data_dir = "a" bind_addr = "127.0.0.1" node_name = "dummy" `+experimentsHCL) + req, m := dns.Msg{}, dns.Msg{} + trimUDPResponse(&req, &m, cfg.DNSUDPAnswerLimit) + if m.Compress { + t.Fatalf("compression should be off") + } - req, m := dns.Msg{}, dns.Msg{} - trimUDPResponse(&req, &m, cfg.DNSUDPAnswerLimit) - if m.Compress { - t.Fatalf("compression should be off") - } - - // The trim function temporarily turns off compression, so we need to - // make sure the setting gets restored properly. - m.Compress = true - trimUDPResponse(&req, &m, cfg.DNSUDPAnswerLimit) - if !m.Compress { - t.Fatalf("compression should be on") - } - }) + // The trim function temporarily turns off compression, so we need to + // make sure the setting gets restored properly. + m.Compress = true + trimUDPResponse(&req, &m, cfg.DNSUDPAnswerLimit) + if !m.Compress { + t.Fatalf("compression should be on") } } @@ -3161,93 +2964,88 @@ func TestDNS_Compression_Query(t *testing.T) { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + // Register a node with a service. + { + args := &structs.RegisterRequest{ + Datacenter: "dc1", + Node: "foo", + Address: "127.0.0.1", + Service: &structs.NodeService{ + Service: "db", + Tags: []string{"primary"}, + Port: 12345, + }, + } - // Register a node with a service. - { - args := &structs.RegisterRequest{ - Datacenter: "dc1", - Node: "foo", - Address: "127.0.0.1", - Service: &structs.NodeService{ - Service: "db", - Tags: []string{"primary"}, - Port: 12345, - }, - } + var out struct{} + if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { + t.Fatalf("err: %v", err) + } + } - var out struct{} - if err := a.RPC(context.Background(), "Catalog.Register", args, &out); err != nil { - t.Fatalf("err: %v", err) - } - } + // Register an equivalent prepared query. + var id string + { + args := &structs.PreparedQueryRequest{ + Datacenter: "dc1", + Op: structs.PreparedQueryCreate, + Query: &structs.PreparedQuery{ + Name: "test", + Service: structs.ServiceQuery{ + Service: "db", + }, + }, + } + if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { + t.Fatalf("err: %v", err) + } + } - // Register an equivalent prepared query. - var id string - { - args := &structs.PreparedQueryRequest{ - Datacenter: "dc1", - Op: structs.PreparedQueryCreate, - Query: &structs.PreparedQuery{ - Name: "test", - Service: structs.ServiceQuery{ - Service: "db", - }, - }, - } - if err := a.RPC(context.Background(), "PreparedQuery.Apply", args, &id); err != nil { - t.Fatalf("err: %v", err) - } - } + // Look up the service directly and via prepared query. + questions := []string{ + "db.service.consul.", + id + ".query.consul.", + } + for _, question := range questions { + m := new(dns.Msg) + m.SetQuestion(question, dns.TypeSRV) - // Look up the service directly and via prepared query. - questions := []string{ - "db.service.consul.", - id + ".query.consul.", - } - for _, question := range questions { - m := new(dns.Msg) - m.SetQuestion(question, dns.TypeSRV) + conn, err := dns.Dial("udp", a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - conn, err := dns.Dial("udp", a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + // Do a manual exchange with compression on (the default). + a.DNSDisableCompression(false) + if err := conn.WriteMsg(m); err != nil { + t.Fatalf("err: %v", err) + } + p := make([]byte, dns.MaxMsgSize) + compressed, err := conn.Read(p) + if err != nil { + t.Fatalf("err: %v", err) + } - // Do a manual exchange with compression on (the default). - a.DNSDisableCompression(false) - if err := conn.WriteMsg(m); err != nil { - t.Fatalf("err: %v", err) - } - p := make([]byte, dns.MaxMsgSize) - compressed, err := conn.Read(p) - if err != nil { - t.Fatalf("err: %v", err) - } + // Disable compression and try again. + a.DNSDisableCompression(true) + if err := conn.WriteMsg(m); err != nil { + t.Fatalf("err: %v", err) + } + unc, err := conn.Read(p) + if err != nil { + t.Fatalf("err: %v", err) + } - // Disable compression and try again. - a.DNSDisableCompression(true) - if err := conn.WriteMsg(m); err != nil { - t.Fatalf("err: %v", err) - } - unc, err := conn.Read(p) - if err != nil { - t.Fatalf("err: %v", err) - } - - // We can't see the compressed status given the DNS API, so we - // just make sure the message is smaller to see if it's - // respecting the flag. - if compressed == 0 || unc == 0 || compressed >= unc { - t.Fatalf("'%s' doesn't look compressed: %d vs. %d", question, compressed, unc) - } - } - }) + // We can't see the compressed status given the DNS API, so we + // just make sure the message is smaller to see if it's + // respecting the flag. + if compressed == 0 || unc == 0 || compressed >= unc { + t.Fatalf("'%s' doesn't look compressed: %d vs. %d", question, compressed, unc) + } } } @@ -3261,49 +3059,44 @@ func TestDNS_Compression_Recurse(t *testing.T) { }) defer recursor.Shutdown() - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - - a := NewTestAgent(t, ` + a := NewTestAgent(t, ` recursors = ["`+recursor.Addr+`"] - `+experimentsHCL) - defer a.Shutdown() - testrpc.WaitForTestAgent(t, a.RPC, "dc1") + `) + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") - m := new(dns.Msg) - m.SetQuestion("apple.com.", dns.TypeANY) + m := new(dns.Msg) + m.SetQuestion("apple.com.", dns.TypeANY) - conn, err := dns.Dial("udp", a.DNSAddr()) - if err != nil { - t.Fatalf("err: %v", err) - } + conn, err := dns.Dial("udp", a.DNSAddr()) + if err != nil { + t.Fatalf("err: %v", err) + } - // Do a manual exchange with compression on (the default). - if err := conn.WriteMsg(m); err != nil { - t.Fatalf("err: %v", err) - } - p := make([]byte, dns.MaxMsgSize) - compressed, err := conn.Read(p) - if err != nil { - t.Fatalf("err: %v", err) - } + // Do a manual exchange with compression on (the default). + if err := conn.WriteMsg(m); err != nil { + t.Fatalf("err: %v", err) + } + p := make([]byte, dns.MaxMsgSize) + compressed, err := conn.Read(p) + if err != nil { + t.Fatalf("err: %v", err) + } - // Disable compression and try again. - a.DNSDisableCompression(true) - if err := conn.WriteMsg(m); err != nil { - t.Fatalf("err: %v", err) - } - unc, err := conn.Read(p) - if err != nil { - t.Fatalf("err: %v", err) - } + // Disable compression and try again. + a.DNSDisableCompression(true) + if err := conn.WriteMsg(m); err != nil { + t.Fatalf("err: %v", err) + } + unc, err := conn.Read(p) + if err != nil { + t.Fatalf("err: %v", err) + } - // We can't see the compressed status given the DNS API, so we just make - // sure the message is smaller to see if it's respecting the flag. - if compressed == 0 || unc == 0 || compressed >= unc { - t.Fatalf("doesn't look compressed: %d vs. %d", compressed, unc) - } - }) + // We can't see the compressed status given the DNS API, so we just make + // sure the message is smaller to see if it's respecting the flag. + if compressed == 0 || unc == 0 || compressed >= unc { + t.Fatalf("doesn't look compressed: %d vs. %d", compressed, unc) } } @@ -3338,7 +3131,6 @@ func TestDNS_V1ConfigReload(t *testing.T) { min_ttl = 4 } } - experiments = ["v1dns"] `) defer a.Shutdown() testrpc.WaitForLeader(t, a.RPC, "dc1") @@ -3347,7 +3139,7 @@ func TestDNS_V1ConfigReload(t *testing.T) { server, ok := s.(*DNSServer) require.True(t, ok) - cfg := server.config.Load().(*dnsConfig) + cfg := server.config.Load().(*dnsServerConfig) require.Equal(t, []string{"8.8.8.8:53"}, cfg.Recursors) require.Equal(t, structs.RecursorStrategy("sequential"), cfg.RecursorStrategy) require.False(t, cfg.AllowStale) @@ -3397,7 +3189,7 @@ func TestDNS_V1ConfigReload(t *testing.T) { server, ok := s.(*DNSServer) require.True(t, ok) - cfg := server.config.Load().(*dnsConfig) + cfg := server.config.Load().(*dnsServerConfig) require.Equal(t, []string{"1.1.1.1:53"}, cfg.Recursors) require.Equal(t, structs.RecursorStrategy("random"), cfg.RecursorStrategy) require.True(t, cfg.AllowStale) @@ -3424,319 +3216,70 @@ func TestDNS_V1ConfigReload(t *testing.T) { } } -// TestDNS_V2ConfigReload_WithV1DataFetcher validates that the dns configuration is saved to the -// DNS server when v2 DNS is configured with V1 catalog and reload config internal is called. -func TestDNS_V2ConfigReload_WithV1DataFetcher(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - a := NewTestAgent(t, ` - experiments=["v2dns"] - recursors = ["8.8.8.8:53"] - dns_config = { - allow_stale = false - max_stale = "20s" - node_ttl = "10s" - service_ttl = { - "my_services*" = "5s" - "my_specific_service" = "30s" - } - enable_truncate = false - only_passing = false - recursor_strategy = "sequential" - recursor_timeout = "15s" - disable_compression = false - a_record_limit = 1 - enable_additional_node_meta_txt = false - soa = { - refresh = 1 - retry = 2 - expire = 3 - min_ttl = 4 - } - } - `) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") - - for _, s := range a.dnsServers { - server, ok := s.(*dnsConsul.Server) - require.True(t, ok) - - cfg := server.Router.GetConfig() - require.Equal(t, []string{"8.8.8.8:53"}, cfg.Recursors) - require.Equal(t, structs.RecursorStrategy("sequential"), cfg.RecursorStrategy) - df := a.catalogDataFetcher.(*discovery.V1DataFetcher) - dfCfg := df.GetConfig() - - require.False(t, dfCfg.AllowStale) - require.Equal(t, 20*time.Second, dfCfg.MaxStale) - require.Equal(t, 10*time.Second, cfg.NodeTTL) - ttl, _ := cfg.GetTTLForService("my_services_1") - require.Equal(t, 5*time.Second, ttl) - ttl, _ = cfg.GetTTLForService("my_specific_service") - require.Equal(t, 30*time.Second, ttl) - require.False(t, cfg.EnableTruncate) - require.False(t, dfCfg.OnlyPassing) - require.Equal(t, 15*time.Second, cfg.RecursorTimeout) - require.False(t, cfg.DisableCompression) - require.Equal(t, 1, cfg.ARecordLimit) - require.False(t, cfg.NodeMetaTXT) - require.Equal(t, uint32(1), cfg.SOAConfig.Refresh) - require.Equal(t, uint32(2), cfg.SOAConfig.Retry) - require.Equal(t, uint32(3), cfg.SOAConfig.Expire) - require.Equal(t, uint32(4), cfg.SOAConfig.Minttl) - } - - newCfg := *a.Config - newCfg.DNSRecursors = []string{"1.1.1.1:53"} - newCfg.DNSAllowStale = true - newCfg.DNSMaxStale = 21 * time.Second - newCfg.DNSNodeTTL = 11 * time.Second - newCfg.DNSServiceTTL = map[string]time.Duration{ - "2_my_services*": 6 * time.Second, - "2_my_specific_service": 31 * time.Second, - } - newCfg.DNSEnableTruncate = true - newCfg.DNSOnlyPassing = true - newCfg.DNSRecursorStrategy = "random" - newCfg.DNSRecursorTimeout = 16 * time.Second - newCfg.DNSDisableCompression = true - newCfg.DNSARecordLimit = 2 - newCfg.DNSNodeMetaTXT = true - newCfg.DNSSOA.Refresh = 10 - newCfg.DNSSOA.Retry = 20 - newCfg.DNSSOA.Expire = 30 - newCfg.DNSSOA.Minttl = 40 - - err := a.reloadConfigInternal(&newCfg) - require.NoError(t, err) - - for _, s := range a.dnsServers { - server, ok := s.(*dnsConsul.Server) - require.True(t, ok) - - cfg := server.Router.GetConfig() - require.Equal(t, []string{"1.1.1.1:53"}, cfg.Recursors) - require.Equal(t, structs.RecursorStrategy("random"), cfg.RecursorStrategy) - df := a.catalogDataFetcher.(*discovery.V1DataFetcher) - dfCfg := df.GetConfig() - require.True(t, dfCfg.AllowStale) - require.Equal(t, 21*time.Second, dfCfg.MaxStale) - require.Equal(t, 11*time.Second, cfg.NodeTTL) - ttl, _ := cfg.GetTTLForService("my_services_1") - require.Equal(t, time.Duration(0), ttl) - ttl, _ = cfg.GetTTLForService("2_my_services_1") - require.Equal(t, 6*time.Second, ttl) - ttl, _ = cfg.GetTTLForService("my_specific_service") - require.Equal(t, time.Duration(0), ttl) - ttl, _ = cfg.GetTTLForService("2_my_specific_service") - require.Equal(t, 31*time.Second, ttl) - require.True(t, cfg.EnableTruncate) - require.True(t, dfCfg.OnlyPassing) - require.Equal(t, 16*time.Second, cfg.RecursorTimeout) - require.True(t, cfg.DisableCompression) - require.Equal(t, 2, cfg.ARecordLimit) - require.True(t, cfg.NodeMetaTXT) - require.Equal(t, uint32(10), cfg.SOAConfig.Refresh) - require.Equal(t, uint32(20), cfg.SOAConfig.Retry) - require.Equal(t, uint32(30), cfg.SOAConfig.Expire) - require.Equal(t, uint32(40), cfg.SOAConfig.Minttl) - } -} - -// TestDNS_V2ConfigReload_WithV2DataFetcher validates that the dns configuration is saved to the -// DNS server when v2 DNS is configured with V1 catalog and reload config internal is called. -func TestDNS_V2ConfigReload_WithV2DataFetcher(t *testing.T) { - if testing.Short() { - t.Skip("too slow for testing.Short") - } - - a := NewTestAgent(t, ` - experiments=["v2dns", "resource-apis"] - recursors = ["8.8.8.8:53"] - dns_config = { - allow_stale = false - max_stale = "20s" - node_ttl = "10s" - service_ttl = { - "my_services*" = "5s" - "my_specific_service" = "30s" - } - enable_truncate = false - only_passing = false - recursor_strategy = "sequential" - recursor_timeout = "15s" - disable_compression = false - a_record_limit = 1 - enable_additional_node_meta_txt = false - soa = { - refresh = 1 - retry = 2 - expire = 3 - min_ttl = 4 - } - } - `) - defer a.Shutdown() - // use WaitForRaftLeader with v2 resource apis - testrpc.WaitForRaftLeader(t, a.RPC, "dc1") - - for _, s := range a.dnsServers { - server, ok := s.(*dnsConsul.Server) - require.True(t, ok) - - cfg := server.Router.GetConfig() - require.Equal(t, []string{"8.8.8.8:53"}, cfg.Recursors) - require.Equal(t, structs.RecursorStrategy("sequential"), cfg.RecursorStrategy) - df := a.catalogDataFetcher.(*discovery.V2DataFetcher) - dfCfg := df.GetConfig() - - //require.False(t, dfCfg.AllowStale) - //require.Equal(t, 20*time.Second, dfCfg.MaxStale) - require.Equal(t, 10*time.Second, cfg.NodeTTL) - ttl, _ := cfg.GetTTLForService("my_services_1") - require.Equal(t, 5*time.Second, ttl) - ttl, _ = cfg.GetTTLForService("my_specific_service") - require.Equal(t, 30*time.Second, ttl) - require.False(t, cfg.EnableTruncate) - require.False(t, dfCfg.OnlyPassing) - require.Equal(t, 15*time.Second, cfg.RecursorTimeout) - require.False(t, cfg.DisableCompression) - require.Equal(t, 1, cfg.ARecordLimit) - require.False(t, cfg.NodeMetaTXT) - require.Equal(t, uint32(1), cfg.SOAConfig.Refresh) - require.Equal(t, uint32(2), cfg.SOAConfig.Retry) - require.Equal(t, uint32(3), cfg.SOAConfig.Expire) - require.Equal(t, uint32(4), cfg.SOAConfig.Minttl) - } - - newCfg := *a.Config - newCfg.DNSRecursors = []string{"1.1.1.1:53"} - newCfg.DNSAllowStale = true - newCfg.DNSMaxStale = 21 * time.Second - newCfg.DNSNodeTTL = 11 * time.Second - newCfg.DNSServiceTTL = map[string]time.Duration{ - "2_my_services*": 6 * time.Second, - "2_my_specific_service": 31 * time.Second, - } - newCfg.DNSEnableTruncate = true - newCfg.DNSOnlyPassing = true - newCfg.DNSRecursorStrategy = "random" - newCfg.DNSRecursorTimeout = 16 * time.Second - newCfg.DNSDisableCompression = true - newCfg.DNSARecordLimit = 2 - newCfg.DNSNodeMetaTXT = true - newCfg.DNSSOA.Refresh = 10 - newCfg.DNSSOA.Retry = 20 - newCfg.DNSSOA.Expire = 30 - newCfg.DNSSOA.Minttl = 40 - - err := a.reloadConfigInternal(&newCfg) - require.NoError(t, err) - - for _, s := range a.dnsServers { - server, ok := s.(*dnsConsul.Server) - require.True(t, ok) - - cfg := server.Router.GetConfig() - require.Equal(t, []string{"1.1.1.1:53"}, cfg.Recursors) - require.Equal(t, structs.RecursorStrategy("random"), cfg.RecursorStrategy) - df := a.catalogDataFetcher.(*discovery.V2DataFetcher) - dfCfg := df.GetConfig() - //require.True(t, dfCfg.AllowStale) - //require.Equal(t, 21*time.Second, dfCfg.MaxStale) - require.Equal(t, 11*time.Second, cfg.NodeTTL) - ttl, _ := cfg.GetTTLForService("my_services_1") - require.Equal(t, time.Duration(0), ttl) - ttl, _ = cfg.GetTTLForService("2_my_services_1") - require.Equal(t, 6*time.Second, ttl) - ttl, _ = cfg.GetTTLForService("my_specific_service") - require.Equal(t, time.Duration(0), ttl) - ttl, _ = cfg.GetTTLForService("2_my_specific_service") - require.Equal(t, 31*time.Second, ttl) - require.True(t, cfg.EnableTruncate) - require.True(t, dfCfg.OnlyPassing) - require.Equal(t, 16*time.Second, cfg.RecursorTimeout) - require.True(t, cfg.DisableCompression) - require.Equal(t, 2, cfg.ARecordLimit) - require.True(t, cfg.NodeMetaTXT) - require.Equal(t, uint32(10), cfg.SOAConfig.Refresh) - require.Equal(t, uint32(20), cfg.SOAConfig.Retry) - require.Equal(t, uint32(30), cfg.SOAConfig.Expire) - require.Equal(t, uint32(40), cfg.SOAConfig.Minttl) - } -} - func TestDNS_ReloadConfig_DuringQuery(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") } - for name, experimentsHCL := range getVersionHCL(true) { - t.Run(name, func(t *testing.T) { - a := NewTestAgent(t, experimentsHCL) - defer a.Shutdown() - testrpc.WaitForLeader(t, a.RPC, "dc1") + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForLeader(t, a.RPC, "dc1") - m := MockPreparedQuery{ - executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { - time.Sleep(100 * time.Millisecond) - reply.Nodes = structs.CheckServiceNodes{ - { - Node: &structs.Node{ - ID: "my_node", - Address: "127.0.0.1", - }, - Service: &structs.NodeService{ - Address: "127.0.0.1", - Port: 8080, - }, - }, - } - return nil + m := MockPreparedQuery{ + executeFn: func(args *structs.PreparedQueryExecuteRequest, reply *structs.PreparedQueryExecuteResponse) error { + time.Sleep(100 * time.Millisecond) + reply.Nodes = structs.CheckServiceNodes{ + { + Node: &structs.Node{ + ID: "my_node", + Address: "127.0.0.1", + }, + Service: &structs.NodeService{ + Address: "127.0.0.1", + Port: 8080, + }, }, } + return nil + }, + } - err := a.registerEndpoint("PreparedQuery", &m) - require.NoError(t, err) + err := a.registerEndpoint("PreparedQuery", &m) + require.NoError(t, err) - { - m := new(dns.Msg) - m.SetQuestion("nope.query.consul.", dns.TypeA) + { + m := new(dns.Msg) + m.SetQuestion("nope.query.consul.", dns.TypeA) - timeout := time.NewTimer(time.Second) - res := make(chan *dns.Msg) - errs := make(chan error) + timeout := time.NewTimer(time.Second) + res := make(chan *dns.Msg) + errs := make(chan error) - go func() { - c := new(dns.Client) - in, _, err := c.Exchange(m, a.DNSAddr()) - if err != nil { - errs <- err - return - } - res <- in - }() - - time.Sleep(50 * time.Millisecond) - - // reload the config halfway through, that should not affect the ongoing query - newCfg := *a.Config - newCfg.DNSAllowStale = true - a.reloadConfigInternal(&newCfg) - - select { - case in := <-res: - require.Equal(t, "127.0.0.1", in.Answer[0].(*dns.A).A.String()) - case err := <-errs: - require.NoError(t, err) - case <-timeout.C: - require.FailNow(t, "timeout") - } + go func() { + c := new(dns.Client) + in, _, err := c.Exchange(m, a.DNSAddr()) + if err != nil { + errs <- err + return } - }) + res <- in + }() + + time.Sleep(50 * time.Millisecond) + + // reload the config halfway through, that should not affect the ongoing query + newCfg := *a.Config + newCfg.DNSAllowStale = true + a.reloadConfigInternal(&newCfg) + + select { + case in := <-res: + require.Equal(t, "127.0.0.1", in.Answer[0].(*dns.A).A.String()) + case err := <-errs: + require.NoError(t, err) + case <-timeout.C: + require.FailNow(t, "timeout") + } } } @@ -3841,9 +3384,13 @@ func TestDNS_ParseLocality(t *testing.T) { d := &DNSServer{ defaultEnterpriseMeta: tc.defaultEntMeta, } - actualResult, actualOK := d.parseLocality(tc.labels, &dnsConfig{ - enterpriseDNSConfig: tc.enterpriseDNSConfig, - }) + actualResult, actualOK := d.parseLocality(tc.labels, + &dnsRequestConfig{ + dnsServerConfig: &dnsServerConfig{ + enterpriseDNSConfig: tc.enterpriseDNSConfig, + }, + defaultEnterpriseMeta: tc.defaultEntMeta, + }) require.Equal(t, tc.expectedOK, actualOK) require.Equal(t, tc.expectedResult, actualResult) diff --git a/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go index ea4852efab..bbc2390a77 100644 --- a/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go +++ b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params.go @@ -8,16 +8,12 @@ import ( "errors" "strings" - "github.com/hashicorp/go-hclog" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/encoding/protojson" "google.golang.org/protobuf/types/known/structpb" - "github.com/hashicorp/consul/internal/resource" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/configentry" @@ -50,72 +46,6 @@ func (s *Server) GetEnvoyBootstrapParams(ctx context.Context, req *pbdataplane.G return nil, status.Error(codes.Unauthenticated, err.Error()) } - if s.EnableV2 { - // Get the workload. - workloadId := &pbresource.ID{ - Name: proxyID, - Tenancy: &pbresource.Tenancy{ - Namespace: req.Namespace, - Partition: req.Partition, - }, - Type: pbcatalog.WorkloadType, - } - workloadRsp, err := s.ResourceAPIClient.Read(ctx, &pbresource.ReadRequest{ - Id: workloadId, - }) - if err != nil { - // This error should already include the gRPC status code and so we don't need to wrap it - // in status.Error. - logger.Error("Error looking up workload", "error", err) - return nil, err - } - var workload pbcatalog.Workload - err = workloadRsp.Resource.Data.UnmarshalTo(&workload) - if err != nil { - return nil, status.Error(codes.Internal, "failed to parse workload data") - } - - // Only workloads that have an associated identity can ask for proxy bootstrap parameters. - if workload.Identity == "" { - return nil, status.Errorf(codes.InvalidArgument, "workload %q doesn't have identity associated with it", req.ProxyId) - } - - // verify identity:write is allowed. if not, give permission denied error. - if err := authz.ToAllowAuthorizer().IdentityWriteAllowed(workload.Identity, &authzContext); err != nil { - return nil, err - } - - computedProxyConfig, err := resource.GetDecodedResource[*pbmesh.ComputedProxyConfiguration]( - ctx, - s.ResourceAPIClient, - resource.ReplaceType(pbmesh.ComputedProxyConfigurationType, workloadId)) - - if err != nil { - logger.Error("Error looking up ComputedProxyConfiguration for this workload", "error", err) - return nil, err - } - - rsp := &pbdataplane.GetEnvoyBootstrapParamsResponse{ - Identity: workload.Identity, - Partition: workloadRsp.Resource.Id.Tenancy.Partition, - Namespace: workloadRsp.Resource.Id.Tenancy.Namespace, - Datacenter: s.Datacenter, - NodeName: workload.NodeName, - } - - if computedProxyConfig != nil { - if computedProxyConfig.GetData().GetDynamicConfig() != nil { - rsp.AccessLogs = makeAccessLogs(computedProxyConfig.GetData().GetDynamicConfig().GetAccessLogs(), logger) - } - - rsp.BootstrapConfig = computedProxyConfig.GetData().GetBootstrapConfig() - } - - return rsp, nil - } - - // The remainder of this file focuses on v1 implementation of this endpoint. - store := s.GetStore() _, svc, err := store.ServiceNode(req.GetNodeId(), req.GetNodeName(), proxyID, &entMeta, structs.DefaultPeerKeyword) @@ -181,9 +111,9 @@ func (s *Server) GetEnvoyBootstrapParams(ctx context.Context, req *pbdataplane.G }, nil } -func makeAccessLogs(logs structs.AccessLogs, logger hclog.Logger) []string { +func makeAccessLogs(logs *structs.AccessLogsConfig, logger hclog.Logger) []string { var accessLogs []string - if logs.GetEnabled() { + if logs.Enabled { envoyLoggers, err := accesslogs.MakeAccessLogs(logs, false) if err != nil { logger.Warn("Error creating the envoy access log config", "error", err) diff --git a/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params_test.go b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params_test.go index 2a50094029..bcff21cce5 100644 --- a/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params_test.go +++ b/agent/grpc-external/services/dataplane/get_envoy_bootstrap_params_test.go @@ -18,18 +18,9 @@ import ( "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/acl/resolver" external "github.com/hashicorp/consul/agent/grpc-external" - svctest "github.com/hashicorp/consul/agent/grpc-external/services/resource/testing" "github.com/hashicorp/consul/agent/grpc-external/testutils" "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/internal/catalog" - "github.com/hashicorp/consul/internal/mesh" - "github.com/hashicorp/consul/internal/resource" - "github.com/hashicorp/consul/internal/resource/resourcetest" - pbcatalog "github.com/hashicorp/consul/proto-public/pbcatalog/v2beta1" "github.com/hashicorp/consul/proto-public/pbdataplane" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" - "github.com/hashicorp/consul/proto-public/pbresource" - "github.com/hashicorp/consul/proto/private/prototest" ) const ( @@ -252,156 +243,6 @@ func TestGetEnvoyBootstrapParams_Success(t *testing.T) { } } -func TestGetEnvoyBootstrapParams_Success_EnableV2(t *testing.T) { - type testCase struct { - name string - workloadData *pbcatalog.Workload - proxyCfg *pbmesh.ComputedProxyConfiguration - expBootstrapCfg *pbmesh.BootstrapConfig - expAccessLogs string - } - - run := func(t *testing.T, tc testCase) { - resourceClient := svctest.NewResourceServiceBuilder(). - WithRegisterFns(catalog.RegisterTypes, mesh.RegisterTypes). - Run(t) - - options := structs.QueryOptions{Token: testToken} - ctx, err := external.ContextWithQueryOptions(context.Background(), options) - require.NoError(t, err) - - aclResolver := &MockACLResolver{} - - server := NewServer(Config{ - Logger: hclog.NewNullLogger(), - ACLResolver: aclResolver, - Datacenter: serverDC, - EnableV2: true, - ResourceAPIClient: resourceClient, - }) - client := testClient(t, server) - - // Add required fields to workload data. - tc.workloadData.Addresses = []*pbcatalog.WorkloadAddress{ - { - Host: "127.0.0.1", - }, - } - tc.workloadData.Ports = map[string]*pbcatalog.WorkloadPort{ - "tcp": {Port: 8080, Protocol: pbcatalog.Protocol_PROTOCOL_TCP}, - } - workloadResource := resourcetest.Resource(pbcatalog.WorkloadType, "test-workload"). - WithData(t, tc.workloadData). - WithTenancy(resource.DefaultNamespacedTenancy()). - Write(t, resourceClient) - - // Create computed proxy cfg resource. - resourcetest.Resource(pbmesh.ComputedProxyConfigurationType, workloadResource.Id.Name). - WithData(t, tc.proxyCfg). - WithTenancy(resource.DefaultNamespacedTenancy()). - Write(t, resourceClient) - - req := &pbdataplane.GetEnvoyBootstrapParamsRequest{ - ProxyId: workloadResource.Id.Name, - Namespace: workloadResource.Id.Tenancy.Namespace, - Partition: workloadResource.Id.Tenancy.Partition, - } - - aclResolver.On("ResolveTokenAndDefaultMeta", testToken, mock.Anything, mock.Anything). - Return(testutils.ACLUseProvidedPolicy(t, - &acl.Policy{ - PolicyRules: acl.PolicyRules{ - Services: []*acl.ServiceRule{ - { - Name: workloadResource.Id.Name, - Policy: acl.PolicyRead, - }, - }, - Identities: []*acl.IdentityRule{ - { - Name: testIdentity, - Policy: acl.PolicyWrite, - }, - }, - }, - }), nil) - - resp, err := client.GetEnvoyBootstrapParams(ctx, req) - require.NoError(t, err) - - require.Equal(t, tc.workloadData.Identity, resp.Identity) - require.Equal(t, serverDC, resp.Datacenter) - require.Equal(t, workloadResource.Id.Tenancy.Partition, resp.Partition) - require.Equal(t, workloadResource.Id.Tenancy.Namespace, resp.Namespace) - require.Equal(t, resp.NodeName, tc.workloadData.NodeName) - prototest.AssertDeepEqual(t, tc.expBootstrapCfg, resp.BootstrapConfig) - if tc.expAccessLogs != "" { - require.JSONEq(t, tc.expAccessLogs, resp.AccessLogs[0]) - } - } - - testCases := []testCase{ - { - name: "workload without node", - workloadData: &pbcatalog.Workload{ - Identity: testIdentity, - }, - expBootstrapCfg: nil, - }, - { - name: "workload with node", - workloadData: &pbcatalog.Workload{ - Identity: testIdentity, - NodeName: "test-node", - }, - expBootstrapCfg: nil, - }, - { - name: "single proxy configuration", - workloadData: &pbcatalog.Workload{ - Identity: testIdentity, - }, - proxyCfg: &pbmesh.ComputedProxyConfiguration{ - BootstrapConfig: &pbmesh.BootstrapConfig{ - DogstatsdUrl: "dogstats-url", - }, - }, - expBootstrapCfg: &pbmesh.BootstrapConfig{ - DogstatsdUrl: "dogstats-url", - }, - }, - { - name: "multiple proxy configurations", - workloadData: &pbcatalog.Workload{ - Identity: testIdentity, - }, - proxyCfg: &pbmesh.ComputedProxyConfiguration{ - BootstrapConfig: &pbmesh.BootstrapConfig{ - DogstatsdUrl: "dogstats-url", - StatsdUrl: "stats-url", - }, - DynamicConfig: &pbmesh.DynamicConfig{ - AccessLogs: &pbmesh.AccessLogsConfig{ - Enabled: true, - JsonFormat: "{ \"custom_field\": \"%START_TIME%\" }", - }, - }, - }, - expBootstrapCfg: &pbmesh.BootstrapConfig{ - DogstatsdUrl: "dogstats-url", - StatsdUrl: "stats-url", - }, - expAccessLogs: testAccessLogs, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } -} - func TestGetEnvoyBootstrapParams_Error(t *testing.T) { type testCase struct { name string @@ -483,100 +324,6 @@ func TestGetEnvoyBootstrapParams_Error(t *testing.T) { } -func TestGetEnvoyBootstrapParams_Error_EnableV2(t *testing.T) { - type testCase struct { - name string - expectedErrCode codes.Code - expecteErrMsg string - workload *pbresource.Resource - } - - run := func(t *testing.T, tc testCase) { - resourceClient := svctest.NewResourceServiceBuilder(). - WithRegisterFns(catalog.RegisterTypes, mesh.RegisterTypes). - Run(t) - - options := structs.QueryOptions{Token: testToken} - ctx, err := external.ContextWithQueryOptions(context.Background(), options) - require.NoError(t, err) - - aclResolver := &MockACLResolver{} - aclResolver.On("ResolveTokenAndDefaultMeta", testToken, mock.Anything, mock.Anything). - Return(testutils.ACLServiceRead(t, "doesn't matter"), nil) - - server := NewServer(Config{ - Logger: hclog.NewNullLogger(), - ACLResolver: aclResolver, - Datacenter: serverDC, - EnableV2: true, - ResourceAPIClient: resourceClient, - }) - client := testClient(t, server) - - var req pbdataplane.GetEnvoyBootstrapParamsRequest - // Write the workload resource. - if tc.workload != nil { - _, err = resourceClient.Write(context.Background(), &pbresource.WriteRequest{ - Resource: tc.workload, - }) - require.NoError(t, err) - - req = pbdataplane.GetEnvoyBootstrapParamsRequest{ - ProxyId: tc.workload.Id.Name, - Namespace: tc.workload.Id.Tenancy.Namespace, - Partition: tc.workload.Id.Tenancy.Partition, - } - } else { - req = pbdataplane.GetEnvoyBootstrapParamsRequest{ - ProxyId: "not-found", - Namespace: "default", - Partition: "default", - } - } - - resp, err := client.GetEnvoyBootstrapParams(ctx, &req) - require.Nil(t, resp) - require.Error(t, err) - errStatus, ok := status.FromError(err) - require.True(t, ok) - require.Equal(t, tc.expectedErrCode.String(), errStatus.Code().String()) - require.Equal(t, tc.expecteErrMsg, errStatus.Message()) - } - - workload := resourcetest.Resource(pbcatalog.WorkloadType, "test-workload"). - WithData(t, &pbcatalog.Workload{ - Addresses: []*pbcatalog.WorkloadAddress{ - {Host: "127.0.0.1"}, - }, - Ports: map[string]*pbcatalog.WorkloadPort{ - "tcp": {Port: 8080}, - }, - }). - WithTenancy(resource.DefaultNamespacedTenancy()). - Build() - - testCases := []testCase{ - { - name: "workload doesn't exist", - expectedErrCode: codes.NotFound, - expecteErrMsg: "resource not found", - }, - { - name: "workload without identity", - expectedErrCode: codes.InvalidArgument, - expecteErrMsg: "workload \"test-workload\" doesn't have identity associated with it", - workload: workload, - }, - } - - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - run(t, tc) - }) - } - -} - func TestGetEnvoyBootstrapParams_Unauthenticated(t *testing.T) { // Mock the ACL resolver to return ErrNotFound. aclResolver := &MockACLResolver{} diff --git a/agent/grpc-external/services/dataplane/server.go b/agent/grpc-external/services/dataplane/server.go index 3a1809cc04..68972ce252 100644 --- a/agent/grpc-external/services/dataplane/server.go +++ b/agent/grpc-external/services/dataplane/server.go @@ -4,7 +4,6 @@ package dataplane import ( - "github.com/hashicorp/consul/proto-public/pbresource" "google.golang.org/grpc" "github.com/hashicorp/go-hclog" @@ -27,10 +26,6 @@ type Config struct { ACLResolver ACLResolver // Datacenter of the Consul server this gRPC server is hosted on Datacenter string - - // EnableV2 indicates whether a feature flag for v2 APIs is provided. - EnableV2 bool - ResourceAPIClient pbresource.ResourceServiceClient } type StateStore interface { diff --git a/agent/grpc-external/services/dns/server.go b/agent/grpc-external/services/dns/server.go index 3485bd2f13..120b1875ef 100644 --- a/agent/grpc-external/services/dns/server.go +++ b/agent/grpc-external/services/dns/server.go @@ -6,6 +6,7 @@ package dns import ( "context" "fmt" + agentdns "github.com/hashicorp/consul/agent/dns" "net" "github.com/hashicorp/go-hclog" @@ -41,61 +42,6 @@ func (s *Server) Register(registrar grpc.ServiceRegistrar) { pbdns.RegisterDNSServiceServer(registrar, s) } -// BufferResponseWriter writes a DNS response to a byte buffer. -type BufferResponseWriter struct { - responseBuffer []byte - LocalAddress net.Addr - RemoteAddress net.Addr - Logger hclog.Logger -} - -// LocalAddr returns the net.Addr of the server -func (b *BufferResponseWriter) LocalAddr() net.Addr { - return b.LocalAddress -} - -// RemoteAddr returns the net.Addr of the client that sent the current request. -func (b *BufferResponseWriter) RemoteAddr() net.Addr { - return b.RemoteAddress -} - -// WriteMsg writes a reply back to the client. -func (b *BufferResponseWriter) WriteMsg(m *dns.Msg) error { - // Pack message to bytes first. - msgBytes, err := m.Pack() - if err != nil { - b.Logger.Error("error packing message", "err", err) - return err - } - b.responseBuffer = msgBytes - return nil -} - -// Write writes a raw buffer back to the client. -func (b *BufferResponseWriter) Write(m []byte) (int, error) { - b.Logger.Debug("Write was called") - return copy(b.responseBuffer, m), nil -} - -// Close closes the connection. -func (b *BufferResponseWriter) Close() error { - // There's nothing for us to do here as we don't handle the connection. - return nil -} - -// TsigStatus returns the status of the Tsig. -func (b *BufferResponseWriter) TsigStatus() error { - // TSIG doesn't apply to this response writer. - return nil -} - -// TsigTimersOnly sets the tsig timers only boolean. -func (b *BufferResponseWriter) TsigTimersOnly(bool) {} - -// Hijack lets the caller take over the connection. -// After a call to Hijack(), the DNS package will not do anything with the connection. { -func (b *BufferResponseWriter) Hijack() {} - // Query is a gRPC endpoint that will serve dns requests. It will be consumed primarily by the // consul dataplane to proxy dns requests to consul. func (s *Server) Query(ctx context.Context, req *pbdns.QueryRequest) (*pbdns.QueryResponse, error) { @@ -121,21 +67,29 @@ func (s *Server) Query(ctx context.Context, req *pbdns.QueryRequest) (*pbdns.Que return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("error protocol type not set: %v", req.GetProtocol())) } - respWriter := &BufferResponseWriter{ - LocalAddress: local, - RemoteAddress: remote, - Logger: s.Logger, + reqCtx, err := agentdns.NewContextFromGRPCContext(ctx) + if err != nil { + s.Logger.Error("error parsing DNS context from grpc metadata", "err", err) + return nil, status.Error(codes.Internal, fmt.Sprintf("error parsing DNS context from grpc metadata: %s", err.Error())) + } + + respWriter := &agentdns.BufferResponseWriter{ + LocalAddress: local, + RemoteAddress: remote, + Logger: s.Logger, + RequestContext: reqCtx, } msg := &dns.Msg{} - err := msg.Unpack(req.Msg) + err = msg.Unpack(req.Msg) if err != nil { s.Logger.Error("error unpacking message", "err", err) return nil, status.Error(codes.Internal, fmt.Sprintf("failure decoding dns request: %s", err.Error())) } + s.DNSServeMux.ServeDNS(respWriter, msg) - queryResponse := &pbdns.QueryResponse{Msg: respWriter.responseBuffer} + queryResponse := &pbdns.QueryResponse{Msg: respWriter.ResponseBuffer()} return queryResponse, nil } diff --git a/agent/grpc-external/services/dns/server_v2.go b/agent/grpc-external/services/dns/server_v2.go deleted file mode 100644 index 848361535f..0000000000 --- a/agent/grpc-external/services/dns/server_v2.go +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "context" - "fmt" - "net" - - "github.com/miekg/dns" - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/peer" - "google.golang.org/grpc/status" - - "github.com/hashicorp/go-hclog" - - agentdns "github.com/hashicorp/consul/agent/dns" - "github.com/hashicorp/consul/proto-public/pbdns" -) - -type ConfigV2 struct { - DNSRouter agentdns.DNSRouter - Logger hclog.Logger - TokenFunc func() string -} - -var _ pbdns.DNSServiceServer = (*ServerV2)(nil) - -// ServerV2 is a gRPC server that implements pbdns.DNSServiceServer. -// It is compatible with the refactored V2 DNS server and suitable for -// passing additional metadata along the grpc connection to catalog queries. -type ServerV2 struct { - ConfigV2 -} - -func NewServerV2(cfg ConfigV2) *ServerV2 { - return &ServerV2{cfg} -} - -func (s *ServerV2) Register(registrar grpc.ServiceRegistrar) { - pbdns.RegisterDNSServiceServer(registrar, s) -} - -// Query is a gRPC endpoint that will serve dns requests. It will be consumed primarily by the -// consul dataplane to proxy dns requests to consul. -func (s *ServerV2) Query(ctx context.Context, req *pbdns.QueryRequest) (*pbdns.QueryResponse, error) { - pr, ok := peer.FromContext(ctx) - if !ok { - return nil, fmt.Errorf("error retrieving peer information from context") - } - - var remote net.Addr - // We do this so that we switch to udp/tcp when handling the request since it will be proxied - // through consul through gRPC, and we need to 'fake' the protocol so that the message is trimmed - // according to whether it is UDP or TCP. - switch req.GetProtocol() { - case pbdns.Protocol_PROTOCOL_TCP: - remote = pr.Addr - case pbdns.Protocol_PROTOCOL_UDP: - remoteAddr := pr.Addr.(*net.TCPAddr) - remote = &net.UDPAddr{IP: remoteAddr.IP, Port: remoteAddr.Port} - default: - return nil, status.Error(codes.InvalidArgument, fmt.Sprintf("error protocol type not set: %v", req.GetProtocol())) - } - - msg := &dns.Msg{} - err := msg.Unpack(req.Msg) - if err != nil { - s.Logger.Error("error unpacking message", "err", err) - return nil, status.Error(codes.Internal, fmt.Sprintf("failure decoding dns request: %s", err.Error())) - } - - reqCtx, err := agentdns.NewContextFromGRPCContext(ctx) - if err != nil { - s.Logger.Error("error parsing DNS context from grpc metadata", "err", err) - return nil, status.Error(codes.Internal, fmt.Sprintf("error parsing DNS context from grpc metadata: %s", err.Error())) - } - - resp := s.DNSRouter.HandleRequest(msg, reqCtx, remote) - data, err := resp.Pack() - if err != nil { - s.Logger.Error("error packing message", "err", err) - return nil, status.Error(codes.Internal, fmt.Sprintf("failure encoding dns request: %s", err.Error())) - } - - queryResponse := &pbdns.QueryResponse{Msg: data} - return queryResponse, nil -} diff --git a/agent/grpc-external/services/dns/server_v2_test.go b/agent/grpc-external/services/dns/server_v2_test.go deleted file mode 100644 index 06c7d4f96a..0000000000 --- a/agent/grpc-external/services/dns/server_v2_test.go +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -package dns - -import ( - "context" - "errors" - - "github.com/hashicorp/go-hclog" - "github.com/miekg/dns" - "github.com/stretchr/testify/mock" - "github.com/stretchr/testify/require" - "google.golang.org/grpc/metadata" - - agentdns "github.com/hashicorp/consul/agent/dns" - "github.com/hashicorp/consul/proto-public/pbdns" -) - -func basicResponse() *dns.Msg { - return &dns.Msg{ - MsgHdr: dns.MsgHdr{ - Opcode: dns.OpcodeQuery, - Response: true, - Authoritative: true, - }, - Compress: true, - Question: []dns.Question{ - { - Name: "abc.com.", - Qtype: dns.TypeANY, - Qclass: dns.ClassINET, - }, - }, - Extra: []dns.RR{ - &dns.TXT{ - Hdr: dns.RR_Header{ - Name: "abc.com.", - Rrtype: dns.TypeTXT, - Class: dns.ClassINET, - Ttl: 0, - }, - Txt: txtRR, - }, - }, - } -} - -func (s *DNSTestSuite) TestProxy_V2Success() { - - testCases := map[string]struct { - question string - configureRouter func(router *agentdns.MockDNSRouter) - clientQuery func(qR *pbdns.QueryRequest) - metadata map[string]string - expectedErr error - }{ - - "happy path udp": { - question: "abc.com.", - configureRouter: func(router *agentdns.MockDNSRouter) { - router.On("HandleRequest", mock.Anything, mock.Anything, mock.Anything). - Return(basicResponse(), nil) - }, - clientQuery: func(qR *pbdns.QueryRequest) { - qR.Protocol = pbdns.Protocol_PROTOCOL_UDP - }, - }, - "happy path tcp": { - question: "abc.com.", - configureRouter: func(router *agentdns.MockDNSRouter) { - router.On("HandleRequest", mock.Anything, mock.Anything, mock.Anything). - Return(basicResponse(), nil) - }, - clientQuery: func(qR *pbdns.QueryRequest) { - qR.Protocol = pbdns.Protocol_PROTOCOL_TCP - }, - }, - "happy path with context variables set": { - question: "abc.com.", - configureRouter: func(router *agentdns.MockDNSRouter) { - router.On("HandleRequest", mock.Anything, mock.Anything, mock.Anything). - Run(func(args mock.Arguments) { - ctx, ok := args.Get(1).(agentdns.Context) - require.True(s.T(), ok, "error casting to agentdns.Context") - require.Equal(s.T(), "test-token", ctx.Token, "token not set in context") - require.Equal(s.T(), "test-namespace", ctx.DefaultNamespace, "namespace not set in context") - require.Equal(s.T(), "test-partition", ctx.DefaultPartition, "partition not set in context") - }). - Return(basicResponse(), nil) - }, - clientQuery: func(qR *pbdns.QueryRequest) { - qR.Protocol = pbdns.Protocol_PROTOCOL_UDP - }, - metadata: map[string]string{ - "x-consul-token": "test-token", - "x-consul-namespace": "test-namespace", - "x-consul-partition": "test-partition", - }, - }, - "No protocol set": { - question: "abc.com.", - clientQuery: func(qR *pbdns.QueryRequest) {}, - expectedErr: errors.New("error protocol type not set: PROTOCOL_UNSET_UNSPECIFIED"), - }, - "Invalid question": { - question: "notvalid", - clientQuery: func(qR *pbdns.QueryRequest) { - qR.Protocol = pbdns.Protocol_PROTOCOL_UDP - }, - expectedErr: errors.New("failure decoding dns request"), - }, - } - - for name, tc := range testCases { - s.Run(name, func() { - router := agentdns.NewMockDNSRouter(s.T()) - - if tc.configureRouter != nil { - tc.configureRouter(router) - } - - server := NewServerV2(ConfigV2{ - Logger: hclog.Default(), - DNSRouter: router, - TokenFunc: func() string { return "" }, - }) - - client := testClient(s.T(), server) - - req := dns.Msg{} - req.SetQuestion(tc.question, dns.TypeA) - - bytes, _ := req.Pack() - - ctx := context.Background() - if len(tc.metadata) > 0 { - md := metadata.MD{} - for k, v := range tc.metadata { - md.Set(k, v) - } - ctx = metadata.NewOutgoingContext(ctx, md) - } - - clientReq := &pbdns.QueryRequest{Msg: bytes} - tc.clientQuery(clientReq) - clientResp, err := client.Query(ctx, clientReq) - if tc.expectedErr != nil { - s.Require().Error(err, "no errror calling gRPC endpoint") - s.Require().ErrorContains(err, tc.expectedErr.Error()) - } else { - s.Require().NoError(err, "error calling gRPC endpoint") - - resp := clientResp.GetMsg() - var dnsResp dns.Msg - - err = dnsResp.Unpack(resp) - s.Require().NoError(err, "error unpacking dns response") - rr := dnsResp.Extra[0].(*dns.TXT) - s.Require().EqualValues(rr.Txt, txtRR) - } - }) - } -} diff --git a/agent/grpc-external/services/resource/delete.go b/agent/grpc-external/services/resource/delete.go index dbfdf07edb..839bc7fa70 100644 --- a/agent/grpc-external/services/resource/delete.go +++ b/agent/grpc-external/services/resource/delete.go @@ -19,7 +19,6 @@ import ( "github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/storage" "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" ) // Delete deletes a resource. @@ -200,17 +199,10 @@ func (s *Server) ensureDeleteRequestValid(req *pbresource.DeleteRequest) (*resou return nil, err } - if err = checkV2Tenancy(s.UseV2Tenancy, req.Id.Type); err != nil { - return nil, err - } - if err := validateScopedTenancy(reg.Scope, reg.Type, req.Id.Tenancy, false); err != nil { return nil, err } - if err := blockBuiltinsDeletion(reg.Type, req.Id); err != nil { - return nil, err - } return reg, nil } @@ -220,12 +212,3 @@ func TombstoneNameFor(deleteId *pbresource.ID) string { // deleteId.Name is just included for easier identification return fmt.Sprintf("tombstone-%v-%v", deleteId.Name, strings.ToLower(deleteId.Uid)) } - -func blockDefaultNamespaceDeletion(rtype *pbresource.Type, id *pbresource.ID) error { - if id.Name == resource.DefaultNamespaceName && - id.Tenancy.Partition == resource.DefaultPartitionName && - resource.EqualType(rtype, pbtenancy.NamespaceType) { - return status.Errorf(codes.InvalidArgument, "cannot delete default namespace") - } - return nil -} diff --git a/agent/grpc-external/services/resource/delete_ce.go b/agent/grpc-external/services/resource/delete_ce.go deleted file mode 100644 index d2ff805a24..0000000000 --- a/agent/grpc-external/services/resource/delete_ce.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -//go:build !consulent - -package resource - -import "github.com/hashicorp/consul/proto-public/pbresource" - -func blockBuiltinsDeletion(rtype *pbresource.Type, id *pbresource.ID) error { - if err := blockDefaultNamespaceDeletion(rtype, id); err != nil { - return err - } - return nil -} diff --git a/agent/grpc-external/services/resource/delete_test.go b/agent/grpc-external/services/resource/delete_test.go index 76403bb4d6..25a8012051 100644 --- a/agent/grpc-external/services/resource/delete_test.go +++ b/agent/grpc-external/services/resource/delete_test.go @@ -5,7 +5,6 @@ package resource_test import ( "context" - "fmt" "strings" "testing" @@ -22,7 +21,6 @@ import ( "github.com/hashicorp/consul/internal/resource/demo" rtest "github.com/hashicorp/consul/internal/resource/resourcetest" "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" pbdemo "github.com/hashicorp/consul/proto/private/pbdemo/v1" ) @@ -137,37 +135,28 @@ func TestDelete_InputValidation(t *testing.T) { }, } - for _, useV2Tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", useV2Tenancy), func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(useV2Tenancy). - WithRegisterFns(demo.RegisterTypes). - Run(t) + client := svctest.NewResourceServiceBuilder(). + WithRegisterFns(demo.RegisterTypes). + Run(t) - for desc, tc := range testCases { - t.Run(desc, func(t *testing.T) { - run(t, client, tc) - }) - } + for desc, tc := range testCases { + t.Run(desc, func(t *testing.T) { + run(t, client, tc) }) } } func TestDelete_TypeNotRegistered(t *testing.T) { - for _, useV2Tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", useV2Tenancy), func(t *testing.T) { - client := svctest.NewResourceServiceBuilder().WithV2Tenancy(useV2Tenancy).Run(t) + client := svctest.NewResourceServiceBuilder().Run(t) - artist, err := demo.GenerateV2Artist() - require.NoError(t, err) + artist, err := demo.GenerateV2Artist() + require.NoError(t, err) - // delete artist with unregistered type - _, err = client.Delete(context.Background(), &pbresource.DeleteRequest{Id: artist.Id, Version: ""}) - require.Error(t, err) - require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.ErrorContains(t, err, "not registered") - }) - } + // delete artist with unregistered type + _, err = client.Delete(context.Background(), &pbresource.DeleteRequest{Id: artist.Id, Version: ""}) + require.Error(t, err) + require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) + require.ErrorContains(t, err, "not registered") } func TestDelete_ACLs(t *testing.T) { @@ -274,15 +263,10 @@ func TestDelete_Success(t *testing.T) { t.Run(desc, func(t *testing.T) { for tenancyDesc, modFn := range tenancyCases() { t.Run(tenancyDesc, func(t *testing.T) { - for _, useV2Tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", useV2Tenancy), func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(useV2Tenancy). - WithRegisterFns(demo.RegisterTypes). - Run(t) - run(t, client, tc, modFn) - }) - } + client := svctest.NewResourceServiceBuilder(). + WithRegisterFns(demo.RegisterTypes). + Run(t) + run(t, client, tc, modFn) }) } }) @@ -338,46 +322,41 @@ func TestDelete_NonCAS_Retry(t *testing.T) { func TestDelete_TombstoneDeletionDoesNotCreateNewTombstone(t *testing.T) { t.Parallel() - for _, useV2Tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", useV2Tenancy), func(t *testing.T) { - ctx := context.Background() - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(useV2Tenancy). - WithRegisterFns(demo.RegisterTypes). - Run(t) + ctx := context.Background() + client := svctest.NewResourceServiceBuilder(). + WithRegisterFns(demo.RegisterTypes). + Run(t) - artist, err := demo.GenerateV2Artist() - require.NoError(t, err) + artist, err := demo.GenerateV2Artist() + require.NoError(t, err) - rsp, err := client.Write(ctx, &pbresource.WriteRequest{Resource: artist}) - require.NoError(t, err) - artist = rsp.Resource + rsp, err := client.Write(ctx, &pbresource.WriteRequest{Resource: artist}) + require.NoError(t, err) + artist = rsp.Resource - // delete artist - _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: artist.Id, Version: ""}) - require.NoError(t, err) + // delete artist + _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: artist.Id, Version: ""}) + require.NoError(t, err) - // verify artist's tombstone created - rsp2, err := client.Read(ctx, &pbresource.ReadRequest{ - Id: &pbresource.ID{ - Name: svc.TombstoneNameFor(artist.Id), - Type: resource.TypeV1Tombstone, - Tenancy: artist.Id.Tenancy, - }, - }) - require.NoError(t, err) - tombstone := rsp2.Resource + // verify artist's tombstone created + rsp2, err := client.Read(ctx, &pbresource.ReadRequest{ + Id: &pbresource.ID{ + Name: svc.TombstoneNameFor(artist.Id), + Type: resource.TypeV1Tombstone, + Tenancy: artist.Id.Tenancy, + }, + }) + require.NoError(t, err) + tombstone := rsp2.Resource - // delete artist's tombstone - _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: tombstone.Id, Version: tombstone.Version}) - require.NoError(t, err) + // delete artist's tombstone + _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: tombstone.Id, Version: tombstone.Version}) + require.NoError(t, err) - // verify no new tombstones created and artist's existing tombstone deleted - rsp3, err := client.List(ctx, &pbresource.ListRequest{Type: resource.TypeV1Tombstone, Tenancy: artist.Id.Tenancy}) - require.NoError(t, err) - require.Empty(t, rsp3.Resources) - }) - } + // verify no new tombstones created and artist's existing tombstone deleted + rsp3, err := client.List(ctx, &pbresource.ListRequest{Type: resource.TypeV1Tombstone, Tenancy: artist.Id.Tenancy}) + require.NoError(t, err) + require.Empty(t, rsp3.Resources) } func TestDelete_NotFound(t *testing.T) { @@ -392,18 +371,13 @@ func TestDelete_NotFound(t *testing.T) { require.NoError(t, err) } - for _, useV2Tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", useV2Tenancy), func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(useV2Tenancy). - WithRegisterFns(demo.RegisterTypes). - Run(t) + client := svctest.NewResourceServiceBuilder(). + WithRegisterFns(demo.RegisterTypes). + Run(t) - for desc, tc := range deleteTestCases() { - t.Run(desc, func(t *testing.T) { - run(t, client, tc) - }) - } + for desc, tc := range deleteTestCases() { + t.Run(desc, func(t *testing.T) { + run(t, client, tc) }) } } @@ -411,115 +385,86 @@ func TestDelete_NotFound(t *testing.T) { func TestDelete_VersionMismatch(t *testing.T) { t.Parallel() - for _, useV2Tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", useV2Tenancy), func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(useV2Tenancy). - WithRegisterFns(demo.RegisterTypes). - Run(t) + client := svctest.NewResourceServiceBuilder(). + WithRegisterFns(demo.RegisterTypes). + Run(t) - artist, err := demo.GenerateV2Artist() - require.NoError(t, err) - rsp, err := client.Write(context.Background(), &pbresource.WriteRequest{Resource: artist}) - require.NoError(t, err) + artist, err := demo.GenerateV2Artist() + require.NoError(t, err) + rsp, err := client.Write(context.Background(), &pbresource.WriteRequest{Resource: artist}) + require.NoError(t, err) - // delete with a version that is different from the stored version - _, err = client.Delete(context.Background(), &pbresource.DeleteRequest{Id: rsp.Resource.Id, Version: "non-existent-version"}) - require.Error(t, err) - require.Equal(t, codes.Aborted.String(), status.Code(err).String()) - require.ErrorContains(t, err, "CAS operation failed") - }) - } + // delete with a version that is different from the stored version + _, err = client.Delete(context.Background(), &pbresource.DeleteRequest{Id: rsp.Resource.Id, Version: "non-existent-version"}) + require.Error(t, err) + require.Equal(t, codes.Aborted.String(), status.Code(err).String()) + require.ErrorContains(t, err, "CAS operation failed") } func TestDelete_MarkedForDeletionWhenFinalizersPresent(t *testing.T) { - for _, useV2Tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", useV2Tenancy), func(t *testing.T) { - ctx := context.Background() - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(useV2Tenancy). - WithRegisterFns(demo.RegisterTypes). - Run(t) + ctx := context.Background() + client := svctest.NewResourceServiceBuilder(). + WithRegisterFns(demo.RegisterTypes). + Run(t) - // Create a resource with a finalizer - res := rtest.Resource(demo.TypeV1Artist, "manwithnoname"). - WithTenancy(resource.DefaultClusteredTenancy()). - WithData(t, &pbdemo.Artist{Name: "Man With No Name"}). - WithMeta(resource.FinalizerKey, "finalizer1"). - Write(t, client) + // Create a resource with a finalizer + res := rtest.Resource(demo.TypeV1Artist, "manwithnoname"). + WithTenancy(resource.DefaultClusteredTenancy()). + WithData(t, &pbdemo.Artist{Name: "Man With No Name"}). + WithMeta(resource.FinalizerKey, "finalizer1"). + Write(t, client) - // Delete it - _, err := client.Delete(ctx, &pbresource.DeleteRequest{Id: res.Id}) - require.NoError(t, err) + // Delete it + _, err := client.Delete(ctx, &pbresource.DeleteRequest{Id: res.Id}) + require.NoError(t, err) - // Verify resource has been marked for deletion - rsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: res.Id}) - require.NoError(t, err) - require.True(t, resource.IsMarkedForDeletion(rsp.Resource)) + // Verify resource has been marked for deletion + rsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: res.Id}) + require.NoError(t, err) + require.True(t, resource.IsMarkedForDeletion(rsp.Resource)) - // Delete again - should be no-op - _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: res.Id}) - require.NoError(t, err) + // Delete again - should be no-op + _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: res.Id}) + require.NoError(t, err) - // Verify no-op by checking version still the same - rsp2, err := client.Read(ctx, &pbresource.ReadRequest{Id: res.Id}) - require.NoError(t, err) - rtest.RequireVersionUnchanged(t, rsp2.Resource, rsp.Resource.Version) - }) - } + // Verify no-op by checking version still the same + rsp2, err := client.Read(ctx, &pbresource.ReadRequest{Id: res.Id}) + require.NoError(t, err) + rtest.RequireVersionUnchanged(t, rsp2.Resource, rsp.Resource.Version) } func TestDelete_ImmediatelyDeletedAfterFinalizersRemoved(t *testing.T) { - for _, useV2Tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", useV2Tenancy), func(t *testing.T) { - ctx := context.Background() - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(useV2Tenancy). - WithRegisterFns(demo.RegisterTypes). - Run(t) + ctx := context.Background() + client := svctest.NewResourceServiceBuilder(). + WithRegisterFns(demo.RegisterTypes). + Run(t) - // Create a resource with a finalizer - res := rtest.Resource(demo.TypeV1Artist, "manwithnoname"). - WithTenancy(resource.DefaultClusteredTenancy()). - WithData(t, &pbdemo.Artist{Name: "Man With No Name"}). - WithMeta(resource.FinalizerKey, "finalizer1"). - Write(t, client) + // Create a resource with a finalizer + res := rtest.Resource(demo.TypeV1Artist, "manwithnoname"). + WithTenancy(resource.DefaultClusteredTenancy()). + WithData(t, &pbdemo.Artist{Name: "Man With No Name"}). + WithMeta(resource.FinalizerKey, "finalizer1"). + Write(t, client) - // Delete should mark it for deletion - _, err := client.Delete(ctx, &pbresource.DeleteRequest{Id: res.Id}) - require.NoError(t, err) + // Delete should mark it for deletion + _, err := client.Delete(ctx, &pbresource.DeleteRequest{Id: res.Id}) + require.NoError(t, err) - // Remove the finalizer - rsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: res.Id}) - require.NoError(t, err) - resource.RemoveFinalizer(rsp.Resource, "finalizer1") - _, err = client.Write(ctx, &pbresource.WriteRequest{Resource: rsp.Resource}) - require.NoError(t, err) + // Remove the finalizer + rsp, err := client.Read(ctx, &pbresource.ReadRequest{Id: res.Id}) + require.NoError(t, err) + resource.RemoveFinalizer(rsp.Resource, "finalizer1") + _, err = client.Write(ctx, &pbresource.WriteRequest{Resource: rsp.Resource}) + require.NoError(t, err) - // Delete should be immediate - _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: rsp.Resource.Id}) - require.NoError(t, err) + // Delete should be immediate + _, err = client.Delete(ctx, &pbresource.DeleteRequest{Id: rsp.Resource.Id}) + require.NoError(t, err) - // Verify deleted - _, err = client.Read(ctx, &pbresource.ReadRequest{Id: rsp.Resource.Id}) - require.Error(t, err) - require.Equal(t, codes.NotFound.String(), status.Code(err).String()) - }) - } -} - -func TestDelete_BlockDeleteDefaultNamespace(t *testing.T) { - client := svctest.NewResourceServiceBuilder().WithV2Tenancy(true).Run(t) - - id := &pbresource.ID{ - Name: resource.DefaultNamespaceName, - Type: pbtenancy.NamespaceType, - Tenancy: &pbresource.Tenancy{Partition: resource.DefaultPartitionName}, - } - _, err := client.Delete(context.Background(), &pbresource.DeleteRequest{Id: id}) + // Verify deleted + _, err = client.Read(ctx, &pbresource.ReadRequest{Id: rsp.Resource.Id}) require.Error(t, err) - require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.ErrorContains(t, err, "cannot delete default namespace") + require.Equal(t, codes.NotFound.String(), status.Code(err).String()) } type deleteTestCase struct { diff --git a/agent/grpc-external/services/resource/list.go b/agent/grpc-external/services/resource/list.go index 62ec2d7975..2e51c443d4 100644 --- a/agent/grpc-external/services/resource/list.go +++ b/agent/grpc-external/services/resource/list.go @@ -104,10 +104,6 @@ func (s *Server) ensureListRequestValid(req *pbresource.ListRequest) (*resource. // not enabled in the license. _ = s.FeatureCheck(reg) - if err = checkV2Tenancy(s.UseV2Tenancy, req.Type); err != nil { - return nil, err - } - if err := validateWildcardTenancy(req.Tenancy, req.NamePrefix); err != nil { return nil, err } diff --git a/agent/grpc-external/services/resource/list_by_owner.go b/agent/grpc-external/services/resource/list_by_owner.go index bb1868a620..29e14e4072 100644 --- a/agent/grpc-external/services/resource/list_by_owner.go +++ b/agent/grpc-external/services/resource/list_by_owner.go @@ -100,10 +100,6 @@ func (s *Server) ensureListByOwnerRequestValid(req *pbresource.ListByOwnerReques return nil, err } - if err = checkV2Tenancy(s.UseV2Tenancy, req.Owner.Type); err != nil { - return nil, err - } - if err = validateScopedTenancy(reg.Scope, reg.Type, req.Owner.Tenancy, true); err != nil { return nil, err } diff --git a/agent/grpc-external/services/resource/list_by_owner_test.go b/agent/grpc-external/services/resource/list_by_owner_test.go index 92167042ea..23c537dcd6 100644 --- a/agent/grpc-external/services/resource/list_by_owner_test.go +++ b/agent/grpc-external/services/resource/list_by_owner_test.go @@ -27,8 +27,6 @@ import ( "github.com/hashicorp/consul/proto/private/prototest" ) -// TODO: Update all tests to use true/false table test for v2tenancy - func TestListByOwner_InputValidation(t *testing.T) { client := svctest.NewResourceServiceBuilder(). WithRegisterFns(demo.RegisterTypes). diff --git a/agent/grpc-external/services/resource/list_test.go b/agent/grpc-external/services/resource/list_test.go index efcfa3cafd..43d5def0c3 100644 --- a/agent/grpc-external/services/resource/list_test.go +++ b/agent/grpc-external/services/resource/list_test.go @@ -27,8 +27,6 @@ import ( "github.com/hashicorp/consul/proto/private/prototest" ) -// TODO: Update all tests to use true/false table test for v2tenancy - func TestList_InputValidation(t *testing.T) { client := svctest.NewResourceServiceBuilder(). WithRegisterFns(demo.RegisterTypes). diff --git a/agent/grpc-external/services/resource/mutate_and_validate.go b/agent/grpc-external/services/resource/mutate_and_validate.go index 7aa3519f38..c58fd4a095 100644 --- a/agent/grpc-external/services/resource/mutate_and_validate.go +++ b/agent/grpc-external/services/resource/mutate_and_validate.go @@ -127,10 +127,6 @@ func (s *Server) ensureResourceValid(res *pbresource.Resource, enforceLicenseChe return nil, err } - if err = checkV2Tenancy(s.UseV2Tenancy, res.Id.Type); err != nil { - return nil, err - } - // Check scope if reg.Scope == resource.ScopePartition && res.Id.Tenancy.Namespace != "" { return nil, status.Errorf( diff --git a/agent/grpc-external/services/resource/mutate_and_validate_test.go b/agent/grpc-external/services/resource/mutate_and_validate_test.go index 8f163e778c..6644f108d4 100644 --- a/agent/grpc-external/services/resource/mutate_and_validate_test.go +++ b/agent/grpc-external/services/resource/mutate_and_validate_test.go @@ -4,7 +4,6 @@ package resource_test import ( - "fmt" "testing" "github.com/stretchr/testify/require" @@ -34,18 +33,13 @@ func TestMutateAndValidate_InputValidation(t *testing.T) { require.ErrorContains(t, err, tc.errContains) } - for _, v2tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", v2tenancy), func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithRegisterFns(demo.RegisterTypes). - WithV2Tenancy(v2tenancy). - Run(t) + client := svctest.NewResourceServiceBuilder(). + WithRegisterFns(demo.RegisterTypes). + Run(t) - for desc, tc := range resourceValidTestCases(t) { - t.Run(desc, func(t *testing.T) { - run(t, client, tc) - }) - } + for desc, tc := range resourceValidTestCases(t) { + t.Run(desc, func(t *testing.T) { + run(t, client, tc) }) } } @@ -66,39 +60,27 @@ func TestMutateAndValidate_OwnerValidation(t *testing.T) { require.ErrorContains(t, err, tc.errorContains) } - for _, v2tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", v2tenancy), func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithRegisterFns(demo.RegisterTypes). - WithV2Tenancy(v2tenancy). - Run(t) + client := svctest.NewResourceServiceBuilder(). + WithRegisterFns(demo.RegisterTypes). + Run(t) - for desc, tc := range ownerValidationTestCases(t) { - t.Run(desc, func(t *testing.T) { - run(t, client, tc) - }) - } + for desc, tc := range ownerValidationTestCases(t) { + t.Run(desc, func(t *testing.T) { + run(t, client, tc) }) } } func TestMutateAndValidate_TypeNotFound(t *testing.T) { - run := func(t *testing.T, client pbresource.ResourceServiceClient) { - res, err := demo.GenerateV2Artist() - require.NoError(t, err) + client := svctest.NewResourceServiceBuilder().Run(t) - _, err = client.MutateAndValidate(testContext(t), &pbresource.MutateAndValidateRequest{Resource: res}) - require.Error(t, err) - require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.Contains(t, err.Error(), "resource type demo.v2.Artist not registered") - } + res, err := demo.GenerateV2Artist() + require.NoError(t, err) - for _, v2tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", v2tenancy), func(t *testing.T) { - client := svctest.NewResourceServiceBuilder().WithV2Tenancy(v2tenancy).Run(t) - run(t, client) - }) - } + _, err = client.MutateAndValidate(testContext(t), &pbresource.MutateAndValidateRequest{Resource: res}) + require.Error(t, err) + require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) + require.Contains(t, err.Error(), "resource type demo.v2.Artist not registered") } func TestMutateAndValidate_Success(t *testing.T) { @@ -114,72 +96,40 @@ func TestMutateAndValidate_Success(t *testing.T) { prototest.AssertDeepEqual(t, tc.expectedTenancy, rsp.Resource.Id.Tenancy) } - for _, v2tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", v2tenancy), func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithRegisterFns(demo.RegisterTypes). - WithV2Tenancy(v2tenancy). - Run(t) + client := svctest.NewResourceServiceBuilder(). + WithRegisterFns(demo.RegisterTypes). + Run(t) - for desc, tc := range mavOrWriteSuccessTestCases(t) { - t.Run(desc, func(t *testing.T) { - run(t, client, tc) - }) - } + for desc, tc := range mavOrWriteSuccessTestCases(t) { + t.Run(desc, func(t *testing.T) { + run(t, client, tc) }) } } func TestMutateAndValidate_Mutate(t *testing.T) { - for _, v2tenancy := range []bool{false, true} { - t.Run(fmt.Sprintf("v2tenancy %v", v2tenancy), func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithRegisterFns(demo.RegisterTypes). - WithV2Tenancy(v2tenancy). - Run(t) + client := svctest.NewResourceServiceBuilder(). + WithRegisterFns(demo.RegisterTypes). + Run(t) - artist, err := demo.GenerateV2Artist() - require.NoError(t, err) + artist, err := demo.GenerateV2Artist() + require.NoError(t, err) - artistData := &pbdemov2.Artist{} - artist.Data.UnmarshalTo(artistData) - require.NoError(t, err) + artistData := &pbdemov2.Artist{} + artist.Data.UnmarshalTo(artistData) + require.NoError(t, err) - // mutate hook sets genre to disco when unspecified - artistData.Genre = pbdemov2.Genre_GENRE_UNSPECIFIED - artist.Data.MarshalFrom(artistData) - require.NoError(t, err) + // mutate hook sets genre to disco when unspecified + artistData.Genre = pbdemov2.Genre_GENRE_UNSPECIFIED + artist.Data.MarshalFrom(artistData) + require.NoError(t, err) - rsp, err := client.MutateAndValidate(testContext(t), &pbresource.MutateAndValidateRequest{Resource: artist}) - require.NoError(t, err) + rsp, err := client.MutateAndValidate(testContext(t), &pbresource.MutateAndValidateRequest{Resource: artist}) + require.NoError(t, err) - // verify mutate hook set genre to disco - require.NoError(t, rsp.Resource.Data.UnmarshalTo(artistData)) - require.Equal(t, pbdemov2.Genre_GENRE_DISCO, artistData.Genre) - }) - } -} - -func TestMutateAndValidate_Tenancy_NotFound(t *testing.T) { - for desc, tc := range mavOrWriteTenancyNotFoundTestCases(t) { - t.Run(desc, func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(true). - WithRegisterFns(demo.RegisterTypes). - Run(t) - - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") - require.NoError(t, err) - - artist, err := demo.GenerateV2Artist() - require.NoError(t, err) - - _, err = client.MutateAndValidate(testContext(t), &pbresource.MutateAndValidateRequest{Resource: tc.modFn(artist, recordLabel)}) - require.Error(t, err) - require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.Contains(t, err.Error(), tc.errContains) - }) - } + // verify mutate hook set genre to disco + require.NoError(t, rsp.Resource.Data.UnmarshalTo(artistData)) + require.Equal(t, pbdemov2.Genre_GENRE_DISCO, artistData.Genre) } func TestMutateAndValidate_TenancyMarkedForDeletion_Fails(t *testing.T) { diff --git a/agent/grpc-external/services/resource/read.go b/agent/grpc-external/services/resource/read.go index 48d07a3375..bf69e2549a 100644 --- a/agent/grpc-external/services/resource/read.go +++ b/agent/grpc-external/services/resource/read.go @@ -106,10 +106,6 @@ func (s *Server) ensureReadRequestValid(req *pbresource.ReadRequest) (*resource. // not enabled in the license. _ = s.FeatureCheck(reg) - if err = checkV2Tenancy(s.UseV2Tenancy, req.Id.Type); err != nil { - return nil, err - } - // Check scope if err = validateScopedTenancy(reg.Scope, req.Id.Type, req.Id.Tenancy, false); err != nil { return nil, err diff --git a/agent/grpc-external/services/resource/read_test.go b/agent/grpc-external/services/resource/read_test.go index b7367e6390..fbea0137af 100644 --- a/agent/grpc-external/services/resource/read_test.go +++ b/agent/grpc-external/services/resource/read_test.go @@ -30,8 +30,6 @@ import ( "github.com/hashicorp/consul/sdk/testutil" ) -// TODO: Update all tests to use true/false table test for v2tenancy - func TestRead_InputValidation(t *testing.T) { client := svctest.NewResourceServiceBuilder(). WithRegisterFns(demo.RegisterTypes). @@ -162,74 +160,6 @@ func TestRead_TypeNotFound(t *testing.T) { require.Contains(t, err.Error(), "resource type demo.v2.Artist not registered") } -func TestRead_ResourceNotFound(t *testing.T) { - for desc, tc := range readTestCases() { - t.Run(desc, func(t *testing.T) { - type tenancyCase struct { - modFn func(artistId, recordlabelId *pbresource.ID) *pbresource.ID - errContains string - } - tenancyCases := map[string]tenancyCase{ - "resource not found by name": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - artistId.Name = "bogusname" - return artistId - }, - errContains: "resource not found", - }, - "partition not found when namespace scoped": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - id := clone(artistId) - id.Tenancy.Partition = "boguspartition" - return id - }, - errContains: "partition not found", - }, - "namespace not found when namespace scoped": { - modFn: func(artistId, _ *pbresource.ID) *pbresource.ID { - id := clone(artistId) - id.Tenancy.Namespace = "bogusnamespace" - return id - }, - errContains: "namespace not found", - }, - "partition not found when partition scoped": { - modFn: func(_, recordLabelId *pbresource.ID) *pbresource.ID { - id := clone(recordLabelId) - id.Tenancy.Partition = "boguspartition" - return id - }, - errContains: "partition not found", - }, - } - for tenancyDesc, tenancyCase := range tenancyCases { - t.Run(tenancyDesc, func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(true). - WithRegisterFns(demo.RegisterTypes). - Run(t) - - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") - require.NoError(t, err) - _, err = client.Write(context.Background(), &pbresource.WriteRequest{Resource: recordLabel}) - require.NoError(t, err) - - artist, err := demo.GenerateV2Artist() - require.NoError(t, err) - _, err = client.Write(context.Background(), &pbresource.WriteRequest{Resource: artist}) - require.NoError(t, err) - - // Each tenancy test case picks which resource to use based on the resource type's scope. - _, err = client.Read(tc.ctx, &pbresource.ReadRequest{Id: tenancyCase.modFn(artist.Id, recordLabel.Id)}) - require.Error(t, err) - require.Equal(t, codes.NotFound.String(), status.Code(err).String()) - require.ErrorContains(t, err, tenancyCase.errContains) - }) - } - }) - } -} - func TestRead_GroupVersionMismatch(t *testing.T) { for desc, tc := range readTestCases() { t.Run(desc, func(t *testing.T) { diff --git a/agent/grpc-external/services/resource/server_ce.go b/agent/grpc-external/services/resource/server_ce.go index 6b2551b06b..88f6e60add 100644 --- a/agent/grpc-external/services/resource/server_ce.go +++ b/agent/grpc-external/services/resource/server_ce.go @@ -6,15 +6,11 @@ package resource import ( - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "github.com/hashicorp/go-hclog" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" ) func v2TenancyToV1EntMeta(tenancy *pbresource.Tenancy) *acl.EnterpriseMeta { @@ -31,15 +27,6 @@ func v1EntMetaToV2Tenancy(reg *resource.Registration, entMeta *acl.EnterpriseMet } } -// checkV2Tenancy returns FailedPrecondition error for namespace resource type -// when the "v2tenancy" feature flag is not enabled. -func checkV2Tenancy(useV2Tenancy bool, rtype *pbresource.Type) error { - if resource.EqualType(rtype, pbtenancy.NamespaceType) && !useV2Tenancy { - return status.Errorf(codes.FailedPrecondition, "use of the v2 namespace resource requires the \"v2tenancy\" feature flag") - } - return nil -} - type Config struct { Logger hclog.Logger Registry Registry @@ -50,11 +37,6 @@ type Config struct { // TenancyBridge temporarily allows us to use V1 implementations of // partitions and namespaces until V2 implementations are available. TenancyBridge TenancyBridge - - // UseV2Tenancy is true if the "v2tenancy" experiment is active, false otherwise. - // Attempts to create v2 tenancy resources (partition or namespace) will fail when the - // flag is false. - UseV2Tenancy bool } // FeatureCheck does not apply to the community edition. diff --git a/agent/grpc-external/services/resource/testing/builder.go b/agent/grpc-external/services/resource/testing/builder.go index ea61928ce3..8c42096746 100644 --- a/agent/grpc-external/services/resource/testing/builder.go +++ b/agent/grpc-external/services/resource/testing/builder.go @@ -15,7 +15,6 @@ import ( "github.com/hashicorp/consul/agent/grpc-external/testutils" "github.com/hashicorp/consul/internal/resource" "github.com/hashicorp/consul/internal/storage/inmem" - "github.com/hashicorp/consul/internal/tenancy" "github.com/hashicorp/consul/proto-public/pbresource" "github.com/hashicorp/consul/sdk/testutil" ) @@ -26,25 +25,14 @@ import ( // making requests. func NewResourceServiceBuilder() *Builder { b := &Builder{ - useV2Tenancy: false, - registry: resource.NewRegistry(), - // Regardless of whether using mock of v2tenancy, always make sure - // the builtin tenancy exists. + registry: resource.NewRegistry(), + // Always make sure the builtin tenancy exists. tenancies: []*pbresource.Tenancy{resource.DefaultNamespacedTenancy()}, cloning: true, } return b } -// WithV2Tenancy configures which tenancy bridge is used. -// -// true => real v2 default partition and namespace via v2 tenancy bridge -// false => mock default partition and namespace since v1 tenancy bridge can't be used (not spinning up an entire server here) -func (b *Builder) WithV2Tenancy(useV2Tenancy bool) *Builder { - b.useV2Tenancy = useV2Tenancy - return b -} - // Registry provides access to the constructed registry post-Run() when // needed by other test dependencies. func (b *Builder) Registry() resource.Registry { @@ -106,33 +94,22 @@ func (b *Builder) Run(t testutil.TestingTB) pbresource.ResourceServiceClient { t.Cleanup(cancel) go backend.Run(ctx) - // Automatically add tenancy types if v2 tenancy enabled - if b.useV2Tenancy { - b.registerFns = append(b.registerFns, tenancy.RegisterTypes) - } - for _, registerFn := range b.registerFns { registerFn(b.registry) } - var tenancyBridge resource.TenancyBridge - if !b.useV2Tenancy { - // use mock tenancy bridge. default/default has already been added out of the box - mockTenancyBridge := &svc.MockTenancyBridge{} + // use mock tenancy bridge. default/default has already been added out of the box + mockTenancyBridge := &svc.MockTenancyBridge{} - for _, tenancy := range b.tenancies { - mockTenancyBridge.On("PartitionExists", tenancy.Partition).Return(true, nil) - mockTenancyBridge.On("NamespaceExists", tenancy.Partition, tenancy.Namespace).Return(true, nil) - mockTenancyBridge.On("IsPartitionMarkedForDeletion", tenancy.Partition).Return(false, nil) - mockTenancyBridge.On("IsNamespaceMarkedForDeletion", tenancy.Partition, tenancy.Namespace).Return(false, nil) - } - - tenancyBridge = mockTenancyBridge - } else { - // use v2 tenancy bridge. population comes later after client injected. - tenancyBridge = tenancy.NewV2TenancyBridge() + for _, tenancy := range b.tenancies { + mockTenancyBridge.On("PartitionExists", tenancy.Partition).Return(true, nil) + mockTenancyBridge.On("NamespaceExists", tenancy.Partition, tenancy.Namespace).Return(true, nil) + mockTenancyBridge.On("IsPartitionMarkedForDeletion", tenancy.Partition).Return(false, nil) + mockTenancyBridge.On("IsNamespaceMarkedForDeletion", tenancy.Partition, tenancy.Namespace).Return(false, nil) } + tenancyBridge := mockTenancyBridge + if b.aclResolver == nil { // When not provided (regardless of V1 tenancy or V2 tenancy), configure an ACL resolver // that has ACLs disabled and fills in "default" for the partition and namespace when @@ -172,22 +149,5 @@ func (b *Builder) Run(t testutil.TestingTB) pbresource.ResourceServiceClient { client = pbresource.NewCloningResourceServiceClient(client) } - // HACK ALERT: The client needs to be injected into the V2TenancyBridge - // after it has been created due the circular dependency. This will - // go away when the tenancy bridge is removed and V1 is no more, however - // long that takes. - switch config.TenancyBridge.(type) { - case *tenancy.V2TenancyBridge: - config.TenancyBridge.(*tenancy.V2TenancyBridge).WithClient(client) - // Default partition and namespace can finally be created - require.NoError(t, initTenancy(ctx, backend)) - - for _, tenancy := range b.tenancies { - if tenancy.Partition == resource.DefaultPartitionName && tenancy.Namespace == resource.DefaultNamespaceName { - continue - } - t.Fatalf("TODO: implement creation of passed in v2 tenancy: %v", tenancy) - } - } return client } diff --git a/agent/grpc-external/services/resource/testing/builder_ce.go b/agent/grpc-external/services/resource/testing/builder_ce.go index d7f9a7c733..90954e4bfb 100644 --- a/agent/grpc-external/services/resource/testing/builder_ce.go +++ b/agent/grpc-external/services/resource/testing/builder_ce.go @@ -14,13 +14,12 @@ import ( ) type Builder struct { - registry resource.Registry - registerFns []func(resource.Registry) - useV2Tenancy bool - tenancies []*pbresource.Tenancy - aclResolver svc.ACLResolver - serviceImpl *svc.Server - cloning bool + registry resource.Registry + registerFns []func(resource.Registry) + tenancies []*pbresource.Tenancy + aclResolver svc.ACLResolver + serviceImpl *svc.Server + cloning bool } func (b *Builder) ensureLicenseManager() { @@ -33,6 +32,5 @@ func (b *Builder) newConfig(logger hclog.Logger, backend svc.Backend, tenancyBri Backend: backend, ACLResolver: b.aclResolver, TenancyBridge: tenancyBridge, - UseV2Tenancy: b.useV2Tenancy, } } diff --git a/agent/grpc-external/services/resource/testing/testing_ce.go b/agent/grpc-external/services/resource/testing/testing_ce.go index 926acf6d38..023fa5189c 100644 --- a/agent/grpc-external/services/resource/testing/testing_ce.go +++ b/agent/grpc-external/services/resource/testing/testing_ce.go @@ -6,19 +6,7 @@ package testing import ( - "context" - "errors" - "time" - - "github.com/oklog/ulid/v2" - "google.golang.org/protobuf/types/known/anypb" - "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/internal/resource" - "github.com/hashicorp/consul/internal/storage" - "github.com/hashicorp/consul/internal/storage/inmem" - "github.com/hashicorp/consul/proto-public/pbresource" - pbtenancy "github.com/hashicorp/consul/proto-public/pbtenancy/v2beta1" ) func FillEntMeta(entMeta *acl.EnterpriseMeta) { @@ -28,36 +16,3 @@ func FillEntMeta(entMeta *acl.EnterpriseMeta) { func FillAuthorizerContext(authzContext *acl.AuthorizerContext) { // nothing to to in CE. } - -// initTenancy creates the builtin v2 namespace resource only. The builtin -// v2 partition is not created because we're in CE. -func initTenancy(ctx context.Context, b *inmem.Backend) error { - nsData, err := anypb.New(&pbtenancy.Namespace{Description: "default namespace in default partition"}) - if err != nil { - return err - } - nsID := &pbresource.ID{ - Type: pbtenancy.NamespaceType, - Name: resource.DefaultNamespaceName, - Tenancy: resource.DefaultPartitionedTenancy(), - Uid: ulid.Make().String(), - } - read, err := b.Read(ctx, storage.StrongConsistency, nsID) - if err != nil && !errors.Is(err, storage.ErrNotFound) { - return err - } - if read == nil && errors.Is(err, storage.ErrNotFound) { - _, err = b.WriteCAS(ctx, &pbresource.Resource{ - Id: nsID, - Generation: ulid.Make().String(), - Data: nsData, - Metadata: map[string]string{ - "generated_at": time.Now().Format(time.RFC3339), - }, - }) - if err != nil { - return err - } - } - return nil -} diff --git a/agent/grpc-external/services/resource/watch.go b/agent/grpc-external/services/resource/watch.go index 511802f2cc..246ae4e296 100644 --- a/agent/grpc-external/services/resource/watch.go +++ b/agent/grpc-external/services/resource/watch.go @@ -130,10 +130,6 @@ func (s *Server) ensureWatchListRequestValid(req *pbresource.WatchListRequest) ( req.Tenancy = wildcardTenancyFor(reg.Scope) } - if err = checkV2Tenancy(s.UseV2Tenancy, req.Type); err != nil { - return nil, err - } - if err := validateWildcardTenancy(req.Tenancy, req.NamePrefix); err != nil { return nil, err } diff --git a/agent/grpc-external/services/resource/watch_test.go b/agent/grpc-external/services/resource/watch_test.go index 5ccdb609ba..73f164e1a9 100644 --- a/agent/grpc-external/services/resource/watch_test.go +++ b/agent/grpc-external/services/resource/watch_test.go @@ -27,8 +27,6 @@ import ( "github.com/hashicorp/consul/proto/private/prototest" ) -// TODO: Update all tests to use true/false table test for v2tenancy - func TestWatchList_InputValidation(t *testing.T) { client := svctest.NewResourceServiceBuilder(). WithRegisterFns(demo.RegisterTypes). diff --git a/agent/grpc-external/services/resource/write_status_test.go b/agent/grpc-external/services/resource/write_status_test.go index 57431eac54..4c52443025 100644 --- a/agent/grpc-external/services/resource/write_status_test.go +++ b/agent/grpc-external/services/resource/write_status_test.go @@ -23,8 +23,6 @@ import ( "github.com/hashicorp/consul/proto-public/pbresource" ) -// TODO: Update all tests to use true/false table test for v2tenancy - func TestWriteStatus_ACL(t *testing.T) { type testCase struct { authz resolver.Result @@ -371,66 +369,6 @@ func TestWriteStatus_Tenancy_Defaults(t *testing.T) { } } -func TestWriteStatus_Tenancy_NotFound(t *testing.T) { - for desc, tc := range map[string]struct { - scope resource.Scope - modFn func(req *pbresource.WriteStatusRequest) - errCode codes.Code - errContains string - }{ - "namespaced resource provides nonexistant partition": { - scope: resource.ScopeNamespace, - modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Tenancy.Partition = "bad" }, - errCode: codes.InvalidArgument, - errContains: "partition", - }, - "namespaced resource provides nonexistant namespace": { - scope: resource.ScopeNamespace, - modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Tenancy.Namespace = "bad" }, - errCode: codes.InvalidArgument, - errContains: "namespace", - }, - "partitioned resource provides nonexistant partition": { - scope: resource.ScopePartition, - modFn: func(req *pbresource.WriteStatusRequest) { req.Id.Tenancy.Partition = "bad" }, - errCode: codes.InvalidArgument, - errContains: "partition", - }, - } { - t.Run(desc, func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(true). - WithRegisterFns(demo.RegisterTypes). - Run(t) - - // Pick resource based on scope of type in testcase. - var res *pbresource.Resource - var err error - switch tc.scope { - case resource.ScopeNamespace: - res, err = demo.GenerateV2Artist() - case resource.ScopePartition: - res, err = demo.GenerateV1RecordLabel("looney-tunes") - } - require.NoError(t, err) - - // Fill in required fields so validation continues until tenancy is checked - req := validWriteStatusRequest(t, res) - req.Id.Uid = ulid.Make().String() - req.Status.ObservedGeneration = ulid.Make().String() - - // Write status with tenancy modded by testcase. - tc.modFn(req) - _, err = client.WriteStatus(testContext(t), req) - - // Verify non-existant tenancy field is the cause of the error. - require.Error(t, err) - require.Equal(t, tc.errCode.String(), status.Code(err).String()) - require.Contains(t, err.Error(), tc.errContains) - }) - } -} - func TestWriteStatus_CASFailure(t *testing.T) { client := svctest.NewResourceServiceBuilder(). WithRegisterFns(demo.RegisterTypes). diff --git a/agent/grpc-external/services/resource/write_test.go b/agent/grpc-external/services/resource/write_test.go index beb47b6f22..c14aaad0e1 100644 --- a/agent/grpc-external/services/resource/write_test.go +++ b/agent/grpc-external/services/resource/write_test.go @@ -4,16 +4,13 @@ package resource_test import ( - "context" "testing" - "time" "github.com/oklog/ulid/v2" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" - "google.golang.org/protobuf/types/known/anypb" "github.com/hashicorp/consul/acl/resolver" svc "github.com/hashicorp/consul/agent/grpc-external/services/resource" @@ -22,14 +19,11 @@ import ( "github.com/hashicorp/consul/internal/resource/demo" rtest "github.com/hashicorp/consul/internal/resource/resourcetest" "github.com/hashicorp/consul/proto-public/pbresource" - pbdemo "github.com/hashicorp/consul/proto/private/pbdemo/v1" pbdemov1 "github.com/hashicorp/consul/proto/private/pbdemo/v1" pbdemov2 "github.com/hashicorp/consul/proto/private/pbdemo/v2" "github.com/hashicorp/consul/proto/private/prototest" ) -// TODO: Update all tests to use true/false table test for v2tenancy - func TestWrite_InputValidation(t *testing.T) { client := svctest.NewResourceServiceBuilder(). WithRegisterFns(demo.RegisterTypes). @@ -186,46 +180,6 @@ func TestWrite_Create_Success(t *testing.T) { } } -func TestWrite_Create_Tenancy_NotFound(t *testing.T) { - for desc, tc := range mavOrWriteTenancyNotFoundTestCases(t) { - t.Run(desc, func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(true). - WithRegisterFns(demo.RegisterTypes). - Run(t) - - recordLabel, err := demo.GenerateV1RecordLabel("looney-tunes") - require.NoError(t, err) - - artist, err := demo.GenerateV2Artist() - require.NoError(t, err) - - _, err = client.Write(testContext(t), &pbresource.WriteRequest{Resource: tc.modFn(artist, recordLabel)}) - require.Error(t, err) - require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.Contains(t, err.Error(), tc.errContains) - }) - } -} - -func TestWrite_Create_With_DeletionTimestamp_Fails(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(true). - WithRegisterFns(demo.RegisterTypes). - Run(t) - - res := rtest.Resource(demo.TypeV1Artist, "blur"). - WithTenancy(resource.DefaultNamespacedTenancy()). - WithData(t, &pbdemov1.Artist{Name: "Blur"}). - WithMeta(resource.DeletionTimestampKey, time.Now().Format(time.RFC3339)). - Build() - - _, err := client.Write(testContext(t), &pbresource.WriteRequest{Resource: res}) - require.Error(t, err) - require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.Contains(t, err.Error(), resource.DeletionTimestampKey) -} - func TestWrite_Create_With_TenancyMarkedForDeletion_Fails(t *testing.T) { for desc, tc := range mavOrWriteTenancyMarkedForDeletionTestCases(t) { t.Run(desc, func(t *testing.T) { @@ -690,239 +644,3 @@ func TestEnsureFinalizerRemoved(t *testing.T) { }) } } - -func TestWrite_ResourceFrozenAfterMarkedForDeletion(t *testing.T) { - type testCase struct { - modFn func(res *pbresource.Resource) - errContains string - } - testCases := map[string]testCase{ - "no-op write rejected": { - modFn: func(res *pbresource.Resource) {}, - errContains: "cannot no-op write resource marked for deletion", - }, - "remove one finalizer": { - modFn: func(res *pbresource.Resource) { - resource.RemoveFinalizer(res, "finalizer1") - }, - }, - "remove all finalizers": { - modFn: func(res *pbresource.Resource) { - resource.RemoveFinalizer(res, "finalizer1") - resource.RemoveFinalizer(res, "finalizer2") - }, - }, - "adding finalizer fails": { - modFn: func(res *pbresource.Resource) { - resource.AddFinalizer(res, "finalizer3") - }, - errContains: "expected at least one finalizer to be removed", - }, - "remove deletionTimestamp fails": { - modFn: func(res *pbresource.Resource) { - delete(res.Metadata, resource.DeletionTimestampKey) - }, - errContains: "cannot remove deletionTimestamp", - }, - "modify deletionTimestamp fails": { - modFn: func(res *pbresource.Resource) { - res.Metadata[resource.DeletionTimestampKey] = "bad" - }, - errContains: "cannot modify deletionTimestamp", - }, - "modify data fails": { - modFn: func(res *pbresource.Resource) { - var err error - res.Data, err = anypb.New(&pbdemo.Artist{Name: "New Order"}) - require.NoError(t, err) - }, - errContains: "cannot modify data", - }, - } - - for desc, tc := range testCases { - t.Run(desc, func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(true). - WithRegisterFns(demo.RegisterTypes). - Run(t) - - // Create a resource with finalizers - res := rtest.Resource(demo.TypeV1Artist, "joydivision"). - WithTenancy(resource.DefaultNamespacedTenancy()). - WithData(t, &pbdemo.Artist{Name: "Joy Division"}). - WithMeta(resource.FinalizerKey, "finalizer1 finalizer2"). - Write(t, client) - - // Mark for deletion - resource should now be frozen - _, err := client.Delete(context.Background(), &pbresource.DeleteRequest{Id: res.Id}) - require.NoError(t, err) - - // Verify marked for deletion - rsp, err := client.Read(context.Background(), &pbresource.ReadRequest{Id: res.Id}) - require.NoError(t, err) - require.True(t, resource.IsMarkedForDeletion(rsp.Resource)) - - // Apply test case mods - tc.modFn(rsp.Resource) - - // Verify write results - _, err = client.Write(context.Background(), &pbresource.WriteRequest{Resource: rsp.Resource}) - if tc.errContains == "" { - require.NoError(t, err) - } else { - require.Error(t, err) - require.Equal(t, codes.InvalidArgument.String(), status.Code(err).String()) - require.ErrorContains(t, err, tc.errContains) - } - }) - } -} - -func TestWrite_NonCASWritePreservesFinalizers(t *testing.T) { - type testCase struct { - existingMeta map[string]string - inputMeta map[string]string - expectedMeta map[string]string - } - testCases := map[string]testCase{ - "input nil metadata preserves existing finalizers": { - inputMeta: nil, - existingMeta: map[string]string{resource.FinalizerKey: "finalizer1 finalizer2"}, - expectedMeta: map[string]string{resource.FinalizerKey: "finalizer1 finalizer2"}, - }, - "input metadata and no finalizer key preserves existing finalizers": { - inputMeta: map[string]string{}, - existingMeta: map[string]string{resource.FinalizerKey: "finalizer1 finalizer2"}, - expectedMeta: map[string]string{resource.FinalizerKey: "finalizer1 finalizer2"}, - }, - "input metadata and with empty finalizer key overwrites existing finalizers": { - inputMeta: map[string]string{resource.FinalizerKey: ""}, - existingMeta: map[string]string{resource.FinalizerKey: "finalizer1 finalizer2"}, - expectedMeta: map[string]string{resource.FinalizerKey: ""}, - }, - "input metadata with one finalizer key overwrites multiple existing finalizers": { - inputMeta: map[string]string{resource.FinalizerKey: "finalizer2"}, - existingMeta: map[string]string{resource.FinalizerKey: "finalizer1 finalizer2"}, - expectedMeta: map[string]string{resource.FinalizerKey: "finalizer2"}, - }, - } - - for desc, tc := range testCases { - t.Run(desc, func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(true). - WithRegisterFns(demo.RegisterTypes). - Run(t) - - // Create the resource based on tc.existingMetadata - builder := rtest.Resource(demo.TypeV1Artist, "joydivision"). - WithTenancy(resource.DefaultNamespacedTenancy()). - WithData(t, &pbdemo.Artist{Name: "Joy"}) - - if tc.existingMeta != nil { - for k, v := range tc.existingMeta { - builder.WithMeta(k, v) - } - } - res := builder.Write(t, client) - - // Build resource for user write based on tc.inputMetadata - builder = rtest.Resource(demo.TypeV1Artist, res.Id.Name). - WithTenancy(resource.DefaultNamespacedTenancy()). - WithData(t, &pbdemo.Artist{Name: "Joy Division"}) - - if tc.inputMeta != nil { - for k, v := range tc.inputMeta { - builder.WithMeta(k, v) - } - } - userRes := builder.Build() - - // Perform the user write - rsp, err := client.Write(context.Background(), &pbresource.WriteRequest{Resource: userRes}) - require.NoError(t, err) - - // Verify write result preserved metadata based on testcase.expecteMetadata - for k := range tc.expectedMeta { - require.Equal(t, tc.expectedMeta[k], rsp.Resource.Metadata[k]) - } - require.Equal(t, len(tc.expectedMeta), len(rsp.Resource.Metadata)) - }) - } -} - -func TestWrite_NonCASWritePreservesDeletionTimestamp(t *testing.T) { - type testCase struct { - existingMeta map[string]string - inputMeta map[string]string - expectedMeta map[string]string - } - - // deletionTimestamp has to be generated via Delete() call and can't be embedded in testdata - // even though testcase desc refers to it. - testCases := map[string]testCase{ - "input metadata no deletion timestamp preserves existing deletion timestamp and removes single finalizer": { - inputMeta: map[string]string{resource.FinalizerKey: "finalizer1"}, - existingMeta: map[string]string{resource.FinalizerKey: "finalizer1 finalizer2"}, - expectedMeta: map[string]string{resource.FinalizerKey: "finalizer1"}, - }, - "input metadata no deletion timestamp preserves existing deletion timestamp and removes all finalizers": { - inputMeta: map[string]string{resource.FinalizerKey: ""}, - existingMeta: map[string]string{resource.FinalizerKey: "finalizer1 finalizer2"}, - expectedMeta: map[string]string{resource.FinalizerKey: ""}, - }, - } - - for desc, tc := range testCases { - t.Run(desc, func(t *testing.T) { - client := svctest.NewResourceServiceBuilder(). - WithV2Tenancy(true). - WithRegisterFns(demo.RegisterTypes). - Run(t) - - // Create the resource based on tc.existingMetadata - builder := rtest.Resource(demo.TypeV1Artist, "joydivision"). - WithTenancy(resource.DefaultNamespacedTenancy()). - WithData(t, &pbdemo.Artist{Name: "Joy Division"}) - - if tc.existingMeta != nil { - for k, v := range tc.existingMeta { - builder.WithMeta(k, v) - } - } - res := builder.Write(t, client) - - // Mark for deletion - _, err := client.Delete(context.Background(), &pbresource.DeleteRequest{Id: res.Id}) - require.NoError(t, err) - - // Re-read the deleted res for future comparison of deletionTimestamp - delRsp, err := client.Read(context.Background(), &pbresource.ReadRequest{Id: res.Id}) - require.NoError(t, err) - - // Build resource for user write based on tc.inputMetadata - builder = rtest.Resource(demo.TypeV1Artist, res.Id.Name). - WithTenancy(resource.DefaultNamespacedTenancy()). - WithData(t, &pbdemo.Artist{Name: "Joy Division"}) - - if tc.inputMeta != nil { - for k, v := range tc.inputMeta { - builder.WithMeta(k, v) - } - } - userRes := builder.Build() - - // Perform the non-CAS user write - rsp, err := client.Write(context.Background(), &pbresource.WriteRequest{Resource: userRes}) - require.NoError(t, err) - - // Verify write result preserved metadata based on testcase.expectedMetadata - for k := range tc.expectedMeta { - require.Equal(t, tc.expectedMeta[k], rsp.Resource.Metadata[k]) - } - // Verify deletion timestamp preserved even though it wasn't passed in to the write - require.Equal(t, delRsp.Resource.Metadata[resource.DeletionTimestampKey], rsp.Resource.Metadata[resource.DeletionTimestampKey]) - }) - } -} diff --git a/agent/health_endpoint.go b/agent/health_endpoint.go index 1ce464d91c..0001c35a11 100644 --- a/agent/health_endpoint.go +++ b/agent/health_endpoint.go @@ -188,6 +188,10 @@ func (s *HTTPHandlers) healthServiceNodes(resp http.ResponseWriter, req *http.Re } s.parsePeerName(req, &args) + s.parseSamenessGroup(req, &args) + if args.SamenessGroup != "" && args.PeerName != "" { + return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "peer-name and sameness-group are mutually exclusive"} + } // Check for tags params := req.URL.Query() @@ -214,12 +218,24 @@ func (s *HTTPHandlers) healthServiceNodes(resp http.ResponseWriter, req *http.Re prefix = "/v1/health/service/" } - // Pull out the service name + // Parse the service name from the query params args.ServiceName = strings.TrimPrefix(req.URL.Path, prefix) if args.ServiceName == "" { return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Missing service name"} } + // Parse the passing flag from the query params and use to set the health filter type + // to HealthFilterIncludeOnlyPassing if it is present. Otherwise, do not filter by health. + passing, err := getBoolQueryParam(params, api.HealthPassing) + if err != nil { + return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Invalid value for ?passing"} + } + healthFilterType := structs.HealthFilterIncludeAll + if passing { + healthFilterType = structs.HealthFilterIncludeOnlyPassing + } + args.HealthFilterType = healthFilterType + out, md, err := s.agent.rpcClientHealth.ServiceNodes(req.Context(), args) if err != nil { return nil, err @@ -229,19 +245,7 @@ func (s *HTTPHandlers) healthServiceNodes(resp http.ResponseWriter, req *http.Re setCacheMeta(resp, &md) } out.QueryMeta.ConsistencyLevel = args.QueryOptions.ConsistencyLevel() - setMeta(resp, &out.QueryMeta) - - // FIXME: argument parsing should be done before performing the rpc - // Filter to only passing if specified - filter, err := getBoolQueryParam(params, api.HealthPassing) - if err != nil { - return nil, HTTPError{StatusCode: http.StatusBadRequest, Reason: "Invalid value for ?passing"} - } - - // FIXME: remove filterNonPassing, replace with nodes.Filter, which is used by DNSServer - if filter { - out.Nodes = filterNonPassing(out.Nodes) - } + _ = setMeta(resp, &out.QueryMeta) // Translate addresses after filtering so we don't waste effort. s.agent.TranslateAddresses(args.Datacenter, out.Nodes, dnsutil.TranslateAddressAcceptAny) @@ -290,25 +294,3 @@ func getBoolQueryParam(params url.Values, key string) (bool, error) { } return param, nil } - -// filterNonPassing is used to filter out any nodes that have check that are not passing -func filterNonPassing(nodes structs.CheckServiceNodes) structs.CheckServiceNodes { - n := len(nodes) - - // Make a copy of the cached nodes rather than operating on the cache directly - out := append(nodes[:0:0], nodes...) - -OUTER: - for i := 0; i < n; i++ { - node := out[i] - for _, check := range node.Checks { - if check.Status != api.HealthPassing { - out[i], out[n-1] = out[n-1], structs.CheckServiceNode{} - n-- - i-- - continue OUTER - } - } - } - return out[:n] -} diff --git a/agent/health_endpoint_ce_test.go b/agent/health_endpoint_ce_test.go new file mode 100644 index 0000000000..fd02d5a553 --- /dev/null +++ b/agent/health_endpoint_ce_test.go @@ -0,0 +1,30 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: BUSL-1.1 + +//go:build !consulent + +package agent + +import ( + "github.com/hashicorp/consul/testrpc" + "github.com/stretchr/testify/require" + "net/http" + "net/http/httptest" + "testing" +) + +func TestHealthServiceNodes_SamenessGroup_ErrorsOnCE(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, "") + defer a.Shutdown() + testrpc.WaitForTestAgent(t, a.RPC, "dc1") + + req, _ := http.NewRequest("GET", "/v1/health/service/consul?dc=dc1&sameness-group=foo", nil) + resp := httptest.NewRecorder() + _, err := a.srv.HealthServiceNodes(resp, req) + require.ErrorContains(t, err, "sameness groups are not supported in consul CE") +} diff --git a/agent/health_endpoint_test.go b/agent/health_endpoint_test.go index 86dd7be70f..0768cbb223 100644 --- a/agent/health_endpoint_test.go +++ b/agent/health_endpoint_test.go @@ -9,17 +9,17 @@ import ( "net/http" "net/http/httptest" "net/url" - "reflect" "strconv" "strings" "testing" "time" "github.com/armon/go-metrics" - "github.com/hashicorp/serf/coordinate" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/hashicorp/serf/coordinate" + "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/sdk/testutil" @@ -28,25 +28,6 @@ import ( "github.com/hashicorp/consul/types" ) -func TestHealthEndpointsFailInV2(t *testing.T) { - t.Parallel() - - a := NewTestAgent(t, `experiments = ["resource-apis"]`) - - checkRequest := func(method, url string) { - t.Run(method+" "+url, func(t *testing.T) { - assertV1CatalogEndpointDoesNotWorkWithV2(t, a, method, url, "{}") - }) - } - - checkRequest("GET", "/v1/health/node/web") - checkRequest("GET", "/v1/health/checks/web") - checkRequest("GET", "/v1/health/state/web") - checkRequest("GET", "/v1/health/service/web") - checkRequest("GET", "/v1/health/connect/web") - checkRequest("GET", "/v1/health/ingress/web") -} - func TestHealthChecksInState(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") @@ -627,13 +608,54 @@ func TestHealthServiceChecks_DistanceSort(t *testing.T) { }) } +type queryBackendConfiguration struct { + name string + config string + cached bool + queryBackend string +} + +var queryBackendConfigs = []*queryBackendConfiguration{ + { + name: "blocking-query-without-cache", + config: `use_streaming_backend=false`, + cached: false, + queryBackend: "blocking-query", + }, + { + name: "blocking-query-with-cache", + config: `use_streaming_backend=false`, + cached: true, + queryBackend: "blocking-query", + }, + { + name: "streaming-with-cache-setting", + config: ` + rpc{ + enable_streaming=true + } + use_streaming_backend=true + `, + cached: true, + queryBackend: "streaming", + }, +} + func TestHealthServiceNodes(t *testing.T) { + for _, cfg := range queryBackendConfigs { + t.Run(cfg.name, func(t *testing.T) { + testHealthServiceNodes(t, cfg) + }) + } +} + +func testHealthServiceNodes(t *testing.T, backendCfg *queryBackendConfiguration) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() - a := StartTestAgent(t, TestAgent{HCL: ``, Overrides: `peering = { test_allow_peer_registrations = true }`}) + a := StartTestAgent(t, TestAgent{HCL: backendCfg.config, Overrides: `peering = { test_allow_peer_registrations = true }`}) defer a.Shutdown() testrpc.WaitForTestAgent(t, a.RPC, "dc1") @@ -718,30 +740,44 @@ func TestHealthServiceNodes(t *testing.T) { // Test caching { // List instances with cache enabled - req, err := http.NewRequest("GET", "/v1/health/service/test?cached"+peerQuerySuffix(peerName), nil) + req, err := http.NewRequest("GET", "/v1/health/service/test?"+peerQuerySuffix(peerName), nil) require.NoError(t, err) + if backendCfg.cached { + addQueryParam(req, "cached", "true") + } resp := httptest.NewRecorder() obj, err := a.srv.HealthServiceNodes(resp, req) require.NoError(t, err) nodes := obj.(structs.CheckServiceNodes) verify(t, peerName, nodes) - // Should be a cache miss - require.Equal(t, "MISS", resp.Header().Get("X-Cache")) + if backendCfg.cached { + // Should be a cache miss + require.Equal(t, "MISS", resp.Header().Get("X-Cache")) + } + + require.Equal(t, backendCfg.queryBackend, resp.Header().Get("X-Consul-Query-Backend")) } { // List instances with cache enabled - req, err := http.NewRequest("GET", "/v1/health/service/test?cached"+peerQuerySuffix(peerName), nil) + req, err := http.NewRequest("GET", "/v1/health/service/test?"+peerQuerySuffix(peerName), nil) require.NoError(t, err) + if backendCfg.cached { + addQueryParam(req, "cached", "true") + } resp := httptest.NewRecorder() obj, err := a.srv.HealthServiceNodes(resp, req) require.NoError(t, err) nodes := obj.(structs.CheckServiceNodes) verify(t, peerName, nodes) - // Should be a cache HIT now! - require.Equal(t, "HIT", resp.Header().Get("X-Cache")) + if backendCfg.cached { + // Should be a cache HIT now! + require.Equal(t, "HIT", resp.Header().Get("X-Cache")) + } + + require.Equal(t, backendCfg.queryBackend, resp.Header().Get("X-Consul-Query-Backend")) } } @@ -761,8 +797,11 @@ func TestHealthServiceNodes(t *testing.T) { for _, peerName := range testingPeerNames { retry.Run(t, func(r *retry.R) { // List it again - req, err := http.NewRequest("GET", "/v1/health/service/test?cached"+peerQuerySuffix(peerName), nil) + req, err := http.NewRequest("GET", "/v1/health/service/test?"+peerQuerySuffix(peerName), nil) require.NoError(r, err) + if backendCfg.cached { + addQueryParam(req, "cached", "true") + } resp := httptest.NewRecorder() obj, err := a.srv.HealthServiceNodes(resp, req) require.NoError(r, err) @@ -777,12 +816,15 @@ func TestHealthServiceNodes(t *testing.T) { _, err = strconv.ParseUint(header, 10, 64) require.NoError(r, err) - // Should be a cache hit! The data should've updated in the cache - // in the background so this should've been fetched directly from - // the cache. - if resp.Header().Get("X-Cache") != "HIT" { - r.Fatalf("should be a cache hit") + if backendCfg.cached { + // Should be a cache hit! The data should've updated in the cache + // in the background so this should've been fetched directly from + // the cache. + if resp.Header().Get("X-Cache") != "HIT" { + r.Fatalf("should be a cache hit") + } } + require.Equal(r, backendCfg.queryBackend, resp.Header().Get("X-Consul-Query-Backend")) }) } } @@ -1237,12 +1279,20 @@ func TestHealthServiceNodes_NodeMetaFilter(t *testing.T) { } func TestHealthServiceNodes_Filter(t *testing.T) { + for _, cfg := range queryBackendConfigs { + t.Run(cfg.name, func(t *testing.T) { + testHealthServiceNodes_Filter(t, cfg) + }) + } +} + +func testHealthServiceNodes_Filter(t *testing.T, backendCfg *queryBackendConfiguration) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() - a := NewTestAgent(t, "") + a := NewTestAgent(t, backendCfg.config) defer a.Shutdown() testrpc.WaitForTestAgent(t, a.RPC, "dc1") @@ -1290,6 +1340,9 @@ func TestHealthServiceNodes_Filter(t *testing.T) { require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) req, _ = http.NewRequest("GET", "/v1/health/service/consul?dc=dc1&filter="+url.QueryEscape("Node.Node == `test-health-node`"), nil) + if backendCfg.cached { + addQueryParam(req, "cached", "true") + } resp = httptest.NewRecorder() obj, err = a.srv.HealthServiceNodes(resp, req) require.NoError(t, err) @@ -1300,6 +1353,8 @@ func TestHealthServiceNodes_Filter(t *testing.T) { nodes = obj.(structs.CheckServiceNodes) require.Len(t, nodes, 1) require.Len(t, nodes[0].Checks, 1) + + require.Equal(t, backendCfg.queryBackend, resp.Header().Get("X-Consul-Query-Backend")) } func TestHealthServiceNodes_DistanceSort(t *testing.T) { @@ -1386,12 +1441,20 @@ func TestHealthServiceNodes_DistanceSort(t *testing.T) { } func TestHealthServiceNodes_PassingFilter(t *testing.T) { + for _, cfg := range queryBackendConfigs { + t.Run(cfg.name, func(t *testing.T) { + testHealthServiceNodes_PassingFilter(t, cfg) + }) + } +} + +func testHealthServiceNodes_PassingFilter(t *testing.T, backendCfg *queryBackendConfiguration) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() - a := NewTestAgent(t, "") + a := NewTestAgent(t, backendCfg.config) defer a.Shutdown() dc := "dc1" @@ -1417,6 +1480,9 @@ func TestHealthServiceNodes_PassingFilter(t *testing.T) { t.Run("bc_no_query_value", func(t *testing.T) { req, _ := http.NewRequest("GET", "/v1/health/service/consul?passing", nil) + if backendCfg.cached { + addQueryParam(req, "cached", "") + } resp := httptest.NewRecorder() obj, err := a.srv.HealthServiceNodes(resp, req) if err != nil { @@ -1428,12 +1494,16 @@ func TestHealthServiceNodes_PassingFilter(t *testing.T) { // Should be 0 health check for consul nodes := obj.(structs.CheckServiceNodes) if len(nodes) != 0 { - t.Fatalf("bad: %v", obj) + t.Fatalf("bad: %v", nodes) } + require.Equal(t, backendCfg.queryBackend, resp.Header().Get("X-Consul-Query-Backend")) }) t.Run("passing_true", func(t *testing.T) { req, _ := http.NewRequest("GET", "/v1/health/service/consul?passing=true", nil) + if backendCfg.cached { + addQueryParam(req, "cached", "") + } resp := httptest.NewRecorder() obj, err := a.srv.HealthServiceNodes(resp, req) if err != nil { @@ -1447,10 +1517,14 @@ func TestHealthServiceNodes_PassingFilter(t *testing.T) { if len(nodes) != 0 { t.Fatalf("bad: %v", obj) } + require.Equal(t, backendCfg.queryBackend, resp.Header().Get("X-Consul-Query-Backend")) }) t.Run("passing_false", func(t *testing.T) { req, _ := http.NewRequest("GET", "/v1/health/service/consul?passing=false", nil) + if backendCfg.cached { + addQueryParam(req, "cached", "") + } resp := httptest.NewRecorder() obj, err := a.srv.HealthServiceNodes(resp, req) if err != nil { @@ -1465,16 +1539,21 @@ func TestHealthServiceNodes_PassingFilter(t *testing.T) { if len(nodes) != 1 { t.Fatalf("bad: %v", obj) } + require.Equal(t, backendCfg.queryBackend, resp.Header().Get("X-Consul-Query-Backend")) }) t.Run("passing_bad", func(t *testing.T) { req, _ := http.NewRequest("GET", "/v1/health/service/consul?passing=nope-nope-nope", nil) + if backendCfg.cached { + addQueryParam(req, "cached", "") + } resp := httptest.NewRecorder() _, err := a.srv.HealthServiceNodes(resp, req) require.True(t, isHTTPBadRequest(err), fmt.Sprintf("Expected bad request HTTP error but got %v", err)) if !strings.Contains(err.Error(), "Invalid value for ?passing") { t.Errorf("bad %s", err.Error()) } + require.Equal(t, "", resp.Header().Get("X-Consul-Query-Backend")) }) } @@ -1626,13 +1705,21 @@ func TestHealthServiceNodes_WanTranslation(t *testing.T) { } func TestHealthConnectServiceNodes(t *testing.T) { + for _, cfg := range queryBackendConfigs { + t.Run(cfg.name, func(t *testing.T) { + testHealthConnectServiceNodes(t, cfg) + }) + } +} + +func testHealthConnectServiceNodes(t *testing.T, backendCfg *queryBackendConfiguration) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() - a := NewTestAgent(t, "") + a := NewTestAgent(t, backendCfg.config) defer a.Shutdown() // Register @@ -1643,6 +1730,9 @@ func TestHealthConnectServiceNodes(t *testing.T) { // Request req, _ := http.NewRequest("GET", fmt.Sprintf( "/v1/health/connect/%s?dc=dc1", args.Service.Proxy.DestinationServiceName), nil) + if backendCfg.cached { + addQueryParam(req, "cached", "true") + } resp := httptest.NewRecorder() obj, err := a.srv.HealthConnectServiceNodes(resp, req) assert.Nil(t, err) @@ -1652,6 +1742,8 @@ func TestHealthConnectServiceNodes(t *testing.T) { nodes := obj.(structs.CheckServiceNodes) assert.Len(t, nodes, 1) assert.Len(t, nodes[0].Checks, 0) + + require.Equal(t, backendCfg.queryBackend, resp.Header().Get("X-Consul-Query-Backend")) } func TestHealthIngressServiceNodes(t *testing.T) { @@ -1771,13 +1863,21 @@ func testHealthIngressServiceNodes(t *testing.T, agentHCL string) { } func TestHealthConnectServiceNodes_Filter(t *testing.T) { + for _, cfg := range queryBackendConfigs { + t.Run(cfg.name, func(t *testing.T) { + testHealthConnectServiceNodes_Filter(t, cfg) + }) + } +} + +func testHealthConnectServiceNodes_Filter(t *testing.T, backendCfg *queryBackendConfiguration) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() - a := NewTestAgent(t, "") + a := NewTestAgent(t, backendCfg.config) defer a.Shutdown() testrpc.WaitForLeader(t, a.RPC, "dc1") @@ -1788,6 +1888,12 @@ func TestHealthConnectServiceNodes_Filter(t *testing.T) { require.NoError(t, a.RPC(context.Background(), "Catalog.Register", args, &out)) args = structs.TestRegisterRequestProxy(t) + // when using streaming these come back as empty slices rather than nil. + // rather than changing structs.TestRegisterRequestProxy(t) and the many places + // it is referenced without using caching, we'll just set them to empty slices here. + args.Service.Proxy.EnvoyExtensions = []structs.EnvoyExtension{} + args.Service.Proxy.Expose.Paths = []structs.ExposePath{} + args.Service.Address = "127.0.0.55" args.Service.Meta = map[string]string{ "version": "2", @@ -1800,6 +1906,9 @@ func TestHealthConnectServiceNodes_Filter(t *testing.T) { "/v1/health/connect/%s?filter=%s", args.Service.Proxy.DestinationServiceName, url.QueryEscape("Service.Meta.version == 2")), nil) + if backendCfg.cached { + addQueryParam(req, "cached", "true") + } resp := httptest.NewRecorder() obj, err := a.srv.HealthConnectServiceNodes(resp, req) require.NoError(t, err) @@ -1810,16 +1919,26 @@ func TestHealthConnectServiceNodes_Filter(t *testing.T) { require.Equal(t, structs.ServiceKindConnectProxy, nodes[0].Service.Kind) require.Equal(t, args.Service.Address, nodes[0].Service.Address) require.Equal(t, args.Service.Proxy, nodes[0].Service.Proxy) + + require.Equal(t, backendCfg.queryBackend, resp.Header().Get("X-Consul-Query-Backend")) } func TestHealthConnectServiceNodes_PassingFilter(t *testing.T) { + for _, cfg := range queryBackendConfigs { + t.Run(cfg.name, func(t *testing.T) { + testHealthConnectServiceNodes_PassingFilter(t, cfg) + }) + } +} + +func testHealthConnectServiceNodes_PassingFilter(t *testing.T, backendCfg *queryBackendConfiguration) { if testing.Short() { t.Skip("too slow for testing.Short") } t.Parallel() - a := NewTestAgent(t, "") + a := NewTestAgent(t, backendCfg.config) defer a.Shutdown() // Register @@ -1836,6 +1955,9 @@ func TestHealthConnectServiceNodes_PassingFilter(t *testing.T) { t.Run("bc_no_query_value", func(t *testing.T) { req, _ := http.NewRequest("GET", fmt.Sprintf( "/v1/health/connect/%s?passing", args.Service.Proxy.DestinationServiceName), nil) + if backendCfg.cached { + addQueryParam(req, "cached", "true") + } resp := httptest.NewRecorder() obj, err := a.srv.HealthConnectServiceNodes(resp, req) assert.Nil(t, err) @@ -1844,11 +1966,16 @@ func TestHealthConnectServiceNodes_PassingFilter(t *testing.T) { // Should be 0 health check for consul nodes := obj.(structs.CheckServiceNodes) assert.Len(t, nodes, 0) + + require.Equal(t, backendCfg.queryBackend, resp.Header().Get("X-Consul-Query-Backend")) }) t.Run("passing_true", func(t *testing.T) { req, _ := http.NewRequest("GET", fmt.Sprintf( "/v1/health/connect/%s?passing=true", args.Service.Proxy.DestinationServiceName), nil) + if backendCfg.cached { + addQueryParam(req, "cached", "true") + } resp := httptest.NewRecorder() obj, err := a.srv.HealthConnectServiceNodes(resp, req) assert.Nil(t, err) @@ -1857,11 +1984,16 @@ func TestHealthConnectServiceNodes_PassingFilter(t *testing.T) { // Should be 0 health check for consul nodes := obj.(structs.CheckServiceNodes) assert.Len(t, nodes, 0) + + require.Equal(t, backendCfg.queryBackend, resp.Header().Get("X-Consul-Query-Backend")) }) t.Run("passing_false", func(t *testing.T) { req, _ := http.NewRequest("GET", fmt.Sprintf( "/v1/health/connect/%s?passing=false", args.Service.Proxy.DestinationServiceName), nil) + if backendCfg.cached { + addQueryParam(req, "cached", "true") + } resp := httptest.NewRecorder() obj, err := a.srv.HealthConnectServiceNodes(resp, req) assert.Nil(t, err) @@ -1870,57 +2002,25 @@ func TestHealthConnectServiceNodes_PassingFilter(t *testing.T) { // Should be 1 nodes := obj.(structs.CheckServiceNodes) assert.Len(t, nodes, 1) + require.Equal(t, backendCfg.queryBackend, resp.Header().Get("X-Consul-Query-Backend")) }) t.Run("passing_bad", func(t *testing.T) { req, _ := http.NewRequest("GET", fmt.Sprintf( "/v1/health/connect/%s?passing=nope-nope", args.Service.Proxy.DestinationServiceName), nil) + if backendCfg.cached { + addQueryParam(req, "cached", "true") + } resp := httptest.NewRecorder() _, err := a.srv.HealthConnectServiceNodes(resp, req) assert.NotNil(t, err) assert.True(t, isHTTPBadRequest(err)) assert.True(t, strings.Contains(err.Error(), "Invalid value for ?passing")) + require.Equal(t, "", resp.Header().Get("X-Consul-Query-Backend")) }) } -func TestFilterNonPassing(t *testing.T) { - t.Parallel() - nodes := structs.CheckServiceNodes{ - structs.CheckServiceNode{ - Checks: structs.HealthChecks{ - &structs.HealthCheck{ - Status: api.HealthCritical, - }, - &structs.HealthCheck{ - Status: api.HealthCritical, - }, - }, - }, - structs.CheckServiceNode{ - Checks: structs.HealthChecks{ - &structs.HealthCheck{ - Status: api.HealthCritical, - }, - &structs.HealthCheck{ - Status: api.HealthCritical, - }, - }, - }, - structs.CheckServiceNode{ - Checks: structs.HealthChecks{ - &structs.HealthCheck{ - Status: api.HealthPassing, - }, - }, - }, - } - out := filterNonPassing(nodes) - if len(out) != 1 && reflect.DeepEqual(out[0], nodes[2]) { - t.Fatalf("bad: %v", out) - } -} - func TestListHealthyServiceNodes_MergeCentralConfig(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") diff --git a/agent/http.go b/agent/http.go index d828ed04c1..506377074a 100644 --- a/agent/http.go +++ b/agent/http.go @@ -6,6 +6,7 @@ package agent import ( "encoding/json" "fmt" + "github.com/hashicorp/go-hclog" "io" "net" "net/http" @@ -22,12 +23,13 @@ import ( "github.com/NYTimes/gziphandler" "github.com/armon/go-metrics" "github.com/armon/go-metrics/prometheus" - "github.com/hashicorp/go-cleanhttp" "github.com/mitchellh/mapstructure" "github.com/pkg/errors" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/cache" "github.com/hashicorp/consul/agent/config" @@ -42,6 +44,11 @@ import ( "github.com/hashicorp/consul/proto/private/pbcommon" ) +const ( + contentTypeHeader = "Content-Type" + plainContentType = "text/plain; charset=utf-8" +) + var HTTPSummaries = []prometheus.SummaryDefinition{ { Name: []string{"api", "http"}, @@ -219,6 +226,7 @@ func (s *HTTPHandlers) handler() http.Handler { // If enableDebug register wrapped pprof handlers if !s.agent.enableDebug.Load() && s.checkACLDisabled() { resp.WriteHeader(http.StatusNotFound) + resp.Header().Set(contentTypeHeader, plainContentType) return } @@ -227,6 +235,7 @@ func (s *HTTPHandlers) handler() http.Handler { authz, err := s.agent.delegate.ResolveTokenAndDefaultMeta(token, nil, nil) if err != nil { + resp.Header().Set(contentTypeHeader, plainContentType) resp.WriteHeader(http.StatusForbidden) return } @@ -236,6 +245,7 @@ func (s *HTTPHandlers) handler() http.Handler { // TODO(partitions): should this be possible in a partition? // TODO(acl-error-enhancements): We should return error details somehow here. if authz.OperatorRead(nil) != acl.Allow { + resp.Header().Set(contentTypeHeader, plainContentType) resp.WriteHeader(http.StatusForbidden) return } @@ -316,6 +326,8 @@ func (s *HTTPHandlers) handler() http.Handler { } h = withRemoteAddrHandler(h) + h = ensureContentTypeHeader(h, s.agent.logger) + s.h = &wrappedMux{ mux: mux, handler: h, @@ -336,6 +348,20 @@ func withRemoteAddrHandler(next http.Handler) http.Handler { }) } +// Injects content type explicitly if not already set into response to prevent XSS +func ensureContentTypeHeader(next http.Handler, logger hclog.Logger) http.Handler { + + return http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { + next.ServeHTTP(resp, req) + + val := resp.Header().Get(contentTypeHeader) + if val == "" { + resp.Header().Set(contentTypeHeader, plainContentType) + logger.Debug("warning: content-type header not explicitly set.", "request-path", req.URL) + } + }) +} + // nodeName returns the node name of the agent func (s *HTTPHandlers) nodeName() string { return s.agent.config.NodeName @@ -379,6 +405,8 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc "from", req.RemoteAddr, "error", err, ) + //set response type to plain to prevent XSS + resp.Header().Set(contentTypeHeader, plainContentType) resp.WriteHeader(http.StatusInternalServerError) return } @@ -397,11 +425,6 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc } logURL = aclEndpointRE.ReplaceAllString(logURL, "$1$4") - rejectCatalogV1Endpoint := false - if s.agent.baseDeps.UseV2Resources() { - rejectCatalogV1Endpoint = isV1CatalogRequest(req.URL.Path) - } - if s.denylist.Block(req.URL.Path) { errMsg := "Endpoint is blocked by agent configuration" httpLogger.Error("Request error", @@ -410,6 +433,8 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc "from", req.RemoteAddr, "error", errMsg, ) + //set response type to plain to prevent XSS + resp.Header().Set(contentTypeHeader, plainContentType) resp.WriteHeader(http.StatusForbidden) fmt.Fprint(resp, errMsg) return @@ -463,14 +488,6 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc return strings.Contains(err.Error(), rate.ErrRetryLater.Error()) } - isUsingV2CatalogExperiment := func(err error) bool { - if err == nil { - return false - } - - return structs.IsErrUsingV2CatalogExperiment(err) - } - isMethodNotAllowed := func(err error) bool { _, ok := err.(MethodNotAllowedError) return ok @@ -506,10 +523,6 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc msg = s.Message() } - if isUsingV2CatalogExperiment(err) && !isHTTPError(err) { - err = newRejectV1RequestWhenV2EnabledError() - } - switch { case isForbidden(err): resp.WriteHeader(http.StatusForbidden) @@ -586,12 +599,7 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc if err == nil { // Invoke the handler - if rejectCatalogV1Endpoint { - obj = nil - err = s.rejectV1RequestWhenV2Enabled() - } else { - obj, err = handler(resp, req) - } + obj, err = handler(resp, req) } } contentType := "application/json" @@ -606,6 +614,8 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc resp.Header().Add("X-Consul-Reason", errPayload.Reason) } } else { + //set response type to plain to prevent XSS + resp.Header().Set(contentTypeHeader, plainContentType) handleErr(err) return } @@ -617,6 +627,8 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc if contentType == "application/json" { buf, err = s.marshalJSON(req, obj) if err != nil { + //set response type to plain to prevent XSS + resp.Header().Set(contentTypeHeader, plainContentType) handleErr(err) return } @@ -627,52 +639,12 @@ func (s *HTTPHandlers) wrap(handler endpoint, methods []string) http.HandlerFunc } } } - resp.Header().Set("Content-Type", contentType) + resp.Header().Set(contentTypeHeader, contentType) resp.WriteHeader(httpCode) resp.Write(buf) } } -func isV1CatalogRequest(logURL string) bool { - switch { - case strings.HasPrefix(logURL, "/v1/catalog/"), - strings.HasPrefix(logURL, "/v1/health/"), - strings.HasPrefix(logURL, "/v1/config/"): - return true - - case strings.HasPrefix(logURL, "/v1/agent/token/"), - logURL == "/v1/agent/self", - logURL == "/v1/agent/host", - logURL == "/v1/agent/version", - logURL == "/v1/agent/reload", - logURL == "/v1/agent/monitor", - logURL == "/v1/agent/metrics", - logURL == "/v1/agent/metrics/stream", - logURL == "/v1/agent/members", - strings.HasPrefix(logURL, "/v1/agent/join/"), - logURL == "/v1/agent/leave", - strings.HasPrefix(logURL, "/v1/agent/force-leave/"), - logURL == "/v1/agent/connect/authorize", - logURL == "/v1/agent/connect/ca/roots", - strings.HasPrefix(logURL, "/v1/agent/connect/ca/leaf/"): - return false - - case strings.HasPrefix(logURL, "/v1/agent/"): - return true - - case logURL == "/v1/internal/acl/authorize", - logURL == "/v1/internal/service-virtual-ip", - logURL == "/v1/internal/ui/oidc-auth-methods", - strings.HasPrefix(logURL, "/v1/internal/ui/metrics-proxy/"): - return false - - case strings.HasPrefix(logURL, "/v1/internal/"): - return true - default: - return false - } -} - // marshalJSON marshals the object into JSON, respecting the user's pretty-ness // configuration. func (s *HTTPHandlers) marshalJSON(req *http.Request, obj interface{}) ([]byte, error) { @@ -739,7 +711,7 @@ func decodeBody(body io.Reader, out interface{}) error { return lib.DecodeJSON(body, out) } -// decodeBodyDeprecated is deprecated, please ues decodeBody above. +// decodeBodyDeprecated is deprecated, please use decodeBody above. // decodeBodyDeprecated is used to decode a JSON request body func decodeBodyDeprecated(req *http.Request, out interface{}, cb func(interface{}) error) error { // This generally only happens in tests since real HTTP requests set @@ -1149,20 +1121,6 @@ func (s *HTTPHandlers) parseToken(req *http.Request, token *string) { s.parseTokenWithDefault(req, token) } -func (s *HTTPHandlers) rejectV1RequestWhenV2Enabled() error { - if s.agent.baseDeps.UseV2Resources() { - return newRejectV1RequestWhenV2EnabledError() - } - return nil -} - -func newRejectV1RequestWhenV2EnabledError() error { - return HTTPError{ - StatusCode: http.StatusBadRequest, - Reason: structs.ErrUsingV2CatalogExperiment.Error(), - } -} - func sourceAddrFromRequest(req *http.Request) string { xff := req.Header.Get("X-Forwarded-For") forwardHosts := strings.Split(xff, ",") @@ -1208,6 +1166,15 @@ func (s *HTTPHandlers) parsePeerName(req *http.Request, args *structs.ServiceSpe } } +func (s *HTTPHandlers) parseSamenessGroup(req *http.Request, args *structs.ServiceSpecificRequest) { + if sg := req.URL.Query().Get("sg"); sg != "" { + args.SamenessGroup = sg + } + if sg := req.URL.Query().Get("sameness-group"); sg != "" { + args.SamenessGroup = sg + } +} + // parseMetaFilter is used to parse the ?node-meta=key:value query parameter, used for // filtering results to nodes with the given metadata key/value func (s *HTTPHandlers) parseMetaFilter(req *http.Request) map[string]string { diff --git a/agent/http_test.go b/agent/http_test.go index 607061d868..497789f689 100644 --- a/agent/http_test.go +++ b/agent/http_test.go @@ -639,14 +639,14 @@ func TestHTTPAPIResponseHeaders(t *testing.T) { `) defer a.Shutdown() - requireHasHeadersSet(t, a, "/v1/agent/self") + requireHasHeadersSet(t, a, "/v1/agent/self", "application/json") // Check the Index page that just renders a simple message with UI disabled // also gets the right headers. - requireHasHeadersSet(t, a, "/") + requireHasHeadersSet(t, a, "/", "text/plain; charset=utf-8") } -func requireHasHeadersSet(t *testing.T, a *TestAgent, path string) { +func requireHasHeadersSet(t *testing.T, a *TestAgent, path string, contentType string) { t.Helper() resp := httptest.NewRecorder() @@ -661,6 +661,9 @@ func requireHasHeadersSet(t *testing.T, a *TestAgent, path string) { require.Equal(t, "1; mode=block", hdrs.Get("X-XSS-Protection"), "X-XSS-Protection header value incorrect") + + require.Equal(t, contentType, hdrs.Get("Content-Type"), + "") } func TestUIResponseHeaders(t *testing.T) { @@ -680,7 +683,28 @@ func TestUIResponseHeaders(t *testing.T) { `) defer a.Shutdown() - requireHasHeadersSet(t, a, "/ui") + //response header for the UI appears to be being handled by the UI itself. + requireHasHeadersSet(t, a, "/ui", "text/plain; charset=utf-8") +} + +func TestErrorContentTypeHeaderSet(t *testing.T) { + if testing.Short() { + t.Skip("too slow for testing.Short") + } + + t.Parallel() + a := NewTestAgent(t, ` + http_config { + response_headers = { + "Access-Control-Allow-Origin" = "*" + "X-XSS-Protection" = "1; mode=block" + "X-Frame-Options" = "SAMEORIGIN" + } + } + `) + defer a.Shutdown() + + requireHasHeadersSet(t, a, "/fake-path-doesn't-exist", "text/plain; charset=utf-8") } func TestAcceptEncodingGzip(t *testing.T) { diff --git a/agent/kvs_endpoint.go b/agent/kvs_endpoint.go index e60567cd5b..65ec443078 100644 --- a/agent/kvs_endpoint.go +++ b/agent/kvs_endpoint.go @@ -293,7 +293,7 @@ func conflictingFlags(resp http.ResponseWriter, req *http.Request, flags ...stri if _, ok := params[conflict]; ok { if found { resp.WriteHeader(http.StatusBadRequest) - fmt.Fprint(resp, "Conflicting flags: "+params.Encode()) + fmt.Fprintf(resp, "Conflicting flags: %v\n", params.Encode()) return true } found = true diff --git a/agent/leafcert/generate.go b/agent/leafcert/generate.go index 19dbdbbaf4..dc9c3b2871 100644 --- a/agent/leafcert/generate.go +++ b/agent/leafcert/generate.go @@ -230,15 +230,6 @@ func (m *Manager) generateNewLeaf( var ipAddresses []net.IP switch { - case req.WorkloadIdentity != "": - id = &connect.SpiffeIDWorkloadIdentity{ - TrustDomain: roots.TrustDomain, - Partition: req.TargetPartition(), - Namespace: req.TargetNamespace(), - WorkloadIdentity: req.WorkloadIdentity, - } - dnsNames = append(dnsNames, req.DNSSAN...) - case req.Service != "": id = &connect.SpiffeIDService{ Host: roots.TrustDomain, @@ -281,7 +272,7 @@ func (m *Manager) generateNewLeaf( dnsNames = append(dnsNames, connect.PeeringServerSAN(req.Datacenter, roots.TrustDomain)) default: - return nil, newState, errors.New("URI must be either workload identity, service, agent, server, or kind") + return nil, newState, errors.New("URI must be either service, agent, server, or kind") } // Create a new private key diff --git a/agent/leafcert/leafcert_test_helpers.go b/agent/leafcert/leafcert_test_helpers.go index 0779033dcc..5b0b3226cb 100644 --- a/agent/leafcert/leafcert_test_helpers.go +++ b/agent/leafcert/leafcert_test_helpers.go @@ -180,16 +180,10 @@ func (s *TestSigner) SignCert(ctx context.Context, req *structs.CASignRequest) ( return nil, fmt.Errorf("error parsing CSR URI: %w", err) } - var isService bool var serviceID *connect.SpiffeIDService - var workloadID *connect.SpiffeIDWorkloadIdentity - switch spiffeID.(type) { case *connect.SpiffeIDService: - isService = true serviceID = spiffeID.(*connect.SpiffeIDService) - case *connect.SpiffeIDWorkloadIdentity: - workloadID = spiffeID.(*connect.SpiffeIDWorkloadIdentity) default: return nil, fmt.Errorf("unexpected spiffeID type %T", spiffeID) } @@ -270,35 +264,19 @@ func (s *TestSigner) SignCert(ctx context.Context, req *structs.CASignRequest) ( } index := s.nextIndex() - if isService { - // Service Spiffe ID case - return &structs.IssuedCert{ - SerialNumber: connect.EncodeSerialNumber(leafCert.SerialNumber), - CertPEM: leafPEM, - Service: serviceID.Service, - ServiceURI: leafCert.URIs[0].String(), - ValidAfter: leafCert.NotBefore, - ValidBefore: leafCert.NotAfter, - RaftIndex: structs.RaftIndex{ - CreateIndex: index, - ModifyIndex: index, - }, - }, nil - } else { - // Workload identity Spiffe ID case - return &structs.IssuedCert{ - SerialNumber: connect.EncodeSerialNumber(leafCert.SerialNumber), - CertPEM: leafPEM, - WorkloadIdentity: workloadID.WorkloadIdentity, - WorkloadIdentityURI: leafCert.URIs[0].String(), - ValidAfter: leafCert.NotBefore, - ValidBefore: leafCert.NotAfter, - RaftIndex: structs.RaftIndex{ - CreateIndex: index, - ModifyIndex: index, - }, - }, nil - } + // Service Spiffe ID case + return &structs.IssuedCert{ + SerialNumber: connect.EncodeSerialNumber(leafCert.SerialNumber), + CertPEM: leafPEM, + Service: serviceID.Service, + ServiceURI: leafCert.URIs[0].String(), + ValidAfter: leafCert.NotBefore, + ValidBefore: leafCert.NotAfter, + RaftIndex: structs.RaftIndex{ + CreateIndex: index, + ModifyIndex: index, + }, + }, nil } type testRootsReader struct { diff --git a/agent/leafcert/structs.go b/agent/leafcert/structs.go index 685756c8dc..8cd7375731 100644 --- a/agent/leafcert/structs.go +++ b/agent/leafcert/structs.go @@ -31,27 +31,16 @@ type ConnectCALeafRequest struct { // The following flags indicate the entity we are requesting a cert for. // Only one of these must be specified. - WorkloadIdentity string // Given a WorkloadIdentity name, the request is for a SpiffeIDWorkload. - Service string // Given a Service name, not ID, the request is for a SpiffeIDService. - Agent string // Given an Agent name, not ID, the request is for a SpiffeIDAgent. - Kind structs.ServiceKind // Given "mesh-gateway", the request is for a SpiffeIDMeshGateway. No other kinds supported. - Server bool // If true, the request is for a SpiffeIDServer. + Service string // Given a Service name, not ID, the request is for a SpiffeIDService. + Agent string // Given an Agent name, not ID, the request is for a SpiffeIDAgent. + Kind structs.ServiceKind // Given "mesh-gateway", the request is for a SpiffeIDMeshGateway. No other kinds supported. + Server bool // If true, the request is for a SpiffeIDServer. } func (r *ConnectCALeafRequest) Key() string { r.EnterpriseMeta.Normalize() switch { - case r.WorkloadIdentity != "": - v, err := hashstructure.Hash([]any{ - r.WorkloadIdentity, - r.EnterpriseMeta, - r.DNSSAN, - r.IPSAN, - }, nil) - if err == nil { - return fmt.Sprintf("workloadidentity:%d", v) - } case r.Agent != "": v, err := hashstructure.Hash([]any{ r.Agent, diff --git a/agent/metrics_test.go b/agent/metrics_test.go index fa1fc55aa2..c65fcc802d 100644 --- a/agent/metrics_test.go +++ b/agent/metrics_test.go @@ -229,6 +229,7 @@ func TestHTTPHandlers_AgentMetrics_LeaderShipMetrics(t *testing.T) { t.Run("check that metric isLeader is set properly on server", func(t *testing.T) { metricsPrefix1 := getUniqueMetricsPrefix() metricsPrefix2 := getUniqueMetricsPrefix() + metricsPrefix3 := getUniqueMetricsPrefix() hcl1 := fmt.Sprintf(` server = true @@ -248,16 +249,29 @@ func TestHTTPHandlers_AgentMetrics_LeaderShipMetrics(t *testing.T) { } `, metricsPrefix2) + hcl3 := fmt.Sprintf(` + server = true + telemetry = { + prometheus_retention_time = "25s", + disable_hostname = true + metrics_prefix = "%s" + } + `, metricsPrefix3) + overrides := ` bootstrap = false - bootstrap_expect = 2 + bootstrap_expect = 3 ` s1 := StartTestAgent(t, TestAgent{Name: "s1", HCL: hcl1, Overrides: overrides}) - s2 := StartTestAgent(t, TestAgent{Name: "s2", HCL: hcl2, Overrides: overrides}) defer s1.Shutdown() + + s2 := StartTestAgent(t, TestAgent{Name: "s2", HCL: hcl2, Overrides: overrides}) defer s2.Shutdown() + s3 := StartTestAgent(t, TestAgent{Name: "s3", HCL: hcl3, Overrides: overrides}) + defer s3.Shutdown() + // agent hasn't become a leader retry.RunWith(retry.ThirtySeconds(), t, func(r *testretry.R) { respRec := httptest.NewRecorder() @@ -268,8 +282,12 @@ func TestHTTPHandlers_AgentMetrics_LeaderShipMetrics(t *testing.T) { _, err := s2.JoinLAN([]string{s1.Config.SerfBindAddrLAN.String()}, nil) require.NoError(t, err) + _, err = s3.JoinLAN([]string{s1.Config.SerfBindAddrLAN.String()}, nil) + require.NoError(t, err) + testrpc.WaitForLeader(t, s1.RPC, "dc1") testrpc.WaitForLeader(t, s2.RPC, "dc1") + testrpc.WaitForLeader(t, s3.RPC, "dc1") // Verify agent's isLeader metrics is 1 retry.RunWith(retry.ThirtySeconds(), t, func(r *testretry.R) { @@ -281,7 +299,11 @@ func TestHTTPHandlers_AgentMetrics_LeaderShipMetrics(t *testing.T) { recordPromMetrics(r, s2, respRec2) found2 := strings.Contains(respRec2.Body.String(), metricsPrefix2+"_server_isLeader 1") - require.True(r, found1 || found2, "leader server should have isLeader 1") + respRec3 := httptest.NewRecorder() + recordPromMetrics(r, s3, respRec3) + found3 := strings.Contains(respRec3.Body.String(), metricsPrefix3+"_server_isLeader 1") + + require.True(r, found1 || found2 || found3, "leader server should have isLeader 1") }) }) } diff --git a/agent/peering_endpoint_test.go b/agent/peering_endpoint_test.go index ba3b704b8b..925033f334 100644 --- a/agent/peering_endpoint_test.go +++ b/agent/peering_endpoint_test.go @@ -556,6 +556,8 @@ func TestHTTP_Peering_Read(t *testing.T) { _, err = a.rpcClientPeering.PeeringWrite(ctx, bar) require.NoError(t, err) + var lastIndex uint64 + t.Run("return foo", func(t *testing.T) { req, err := http.NewRequest("GET", "/v1/peering/foo", nil) require.NoError(t, err) @@ -578,6 +580,8 @@ func TestHTTP_Peering_Read(t *testing.T) { require.Equal(t, 0, len(apiResp.StreamStatus.ImportedServices)) require.Equal(t, 0, len(apiResp.StreamStatus.ExportedServices)) + + lastIndex = getIndex(t, resp) }) t.Run("not found", func(t *testing.T) { @@ -588,6 +592,43 @@ func TestHTTP_Peering_Read(t *testing.T) { require.Equal(t, http.StatusNotFound, resp.Code) require.Equal(t, "Peering not found for \"baz\"", resp.Body.String()) }) + + const timeout = 5 * time.Second + t.Run("read blocking query result", func(t *testing.T) { + var ( + // out and resp are not safe to read until reading from errCh + out api.Peering + resp = httptest.NewRecorder() + errCh = make(chan error, 1) + ) + go func() { + url := fmt.Sprintf("/v1/peering/foo?index=%d&wait=%s", lastIndex, timeout) + req, err := http.NewRequest("GET", url, nil) + if err != nil { + errCh <- err + return + } + + a.srv.h.ServeHTTP(resp, req) + require.Equal(t, http.StatusOK, resp.Code) + err = json.NewDecoder(resp.Body).Decode(&out) + errCh <- err + }() + + time.Sleep(200 * time.Millisecond) + + // update peering + foo.Peering.Meta["spooky-key"] = "boo!" + _, err = a.rpcClientPeering.PeeringWrite(ctx, foo) + require.NoError(t, err) + + if err := <-errCh; err != nil { + require.NoError(t, err) + } + + require.Equal(t, "boo!", out.Meta["spooky-key"]) + require.Equal(t, "blocking-query", resp.Header().Get("X-Consul-Query-Backend")) + }) } func TestHTTP_Peering_Delete(t *testing.T) { diff --git a/agent/proxycfg-glue/health_blocking.go b/agent/proxycfg-glue/health_blocking.go index 8ed384f837..0b2bcba8b9 100644 --- a/agent/proxycfg-glue/health_blocking.go +++ b/agent/proxycfg-glue/health_blocking.go @@ -128,7 +128,8 @@ func (h *serverHealthBlocking) Notify(ctx context.Context, args *structs.Service if err != nil { return 0, nil, err } - thisReply.Nodes = raw.(structs.CheckServiceNodes) + filteredNodes := raw.(structs.CheckServiceNodes) + thisReply.Nodes = filteredNodes.Filter(structs.CheckServiceNodeFilterOptions{FilterType: args.HealthFilterType}) // Note: we filter the results with ACLs *after* applying the user-supplied // bexpr filter, to ensure QueryMeta.ResultsFilteredByACLs does not include diff --git a/agent/proxycfg-sources/catalog/config_source.go b/agent/proxycfg-sources/catalog/config_source.go index deb1bbeac8..ec4aabeeb1 100644 --- a/agent/proxycfg-sources/catalog/config_source.go +++ b/agent/proxycfg-sources/catalog/config_source.go @@ -17,8 +17,6 @@ import ( "github.com/hashicorp/consul/agent/local" "github.com/hashicorp/consul/agent/proxycfg" "github.com/hashicorp/consul/agent/structs" - proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot" - "github.com/hashicorp/consul/proto-public/pbresource" ) const source proxycfg.ProxySource = "catalog" @@ -53,13 +51,11 @@ func NewConfigSource(cfg Config) *ConfigSource { // Watch wraps the underlying proxycfg.Manager and dynamically registers // services from the catalog with it when requested by the xDS server. -func (m *ConfigSource) Watch(id *pbresource.ID, nodeName string, token string) (<-chan proxysnapshot.ProxySnapshot, limiter.SessionTerminatedChan, proxycfg.SrcTerminatedChan, proxysnapshot.CancelFunc, error) { - // Create service ID - serviceID := structs.NewServiceID(id.Name, GetEnterpriseMetaFromResourceID(id)) +func (m *ConfigSource) Watch(serviceID structs.ServiceID, nodeName string, token string) (<-chan *proxycfg.ConfigSnapshot, limiter.SessionTerminatedChan, proxycfg.SrcTerminatedChan, context.CancelFunc, error) { // If the service is registered to the local agent, use the LocalConfigSource // rather than trying to configure it from the catalog. if nodeName == m.NodeName && m.LocalState.ServiceExists(serviceID) { - return m.LocalConfigSource.Watch(id, nodeName, token) + return m.LocalConfigSource.Watch(serviceID, nodeName, token) } // Begin a session with the xDS session concurrency limiter. @@ -290,7 +286,7 @@ type Config struct { //go:generate mockery --name ConfigManager --inpackage type ConfigManager interface { - Watch(req proxycfg.ProxyID) (<-chan proxysnapshot.ProxySnapshot, proxysnapshot.CancelFunc) + Watch(req proxycfg.ProxyID) (<-chan *proxycfg.ConfigSnapshot, context.CancelFunc) Register(proxyID proxycfg.ProxyID, service *structs.NodeService, source proxycfg.ProxySource, token string, overwrite bool) error Deregister(proxyID proxycfg.ProxyID, source proxycfg.ProxySource) } @@ -303,7 +299,7 @@ type Store interface { //go:generate mockery --name Watcher --inpackage type Watcher interface { - Watch(proxyID *pbresource.ID, nodeName string, token string) (<-chan proxysnapshot.ProxySnapshot, limiter.SessionTerminatedChan, proxycfg.SrcTerminatedChan, proxysnapshot.CancelFunc, error) + Watch(proxyID structs.ServiceID, nodeName string, token string) (<-chan *proxycfg.ConfigSnapshot, limiter.SessionTerminatedChan, proxycfg.SrcTerminatedChan, context.CancelFunc, error) } //go:generate mockery --name SessionLimiter --inpackage diff --git a/agent/proxycfg-sources/catalog/config_source_oss.go b/agent/proxycfg-sources/catalog/config_source_oss.go deleted file mode 100644 index 233ad64cee..0000000000 --- a/agent/proxycfg-sources/catalog/config_source_oss.go +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: BUSL-1.1 - -//go:build !consulent - -package catalog - -import ( - "github.com/hashicorp/consul/acl" - "github.com/hashicorp/consul/proto-public/pbresource" -) - -func GetEnterpriseMetaFromResourceID(id *pbresource.ID) *acl.EnterpriseMeta { - return acl.DefaultEnterpriseMeta() -} diff --git a/agent/proxycfg-sources/catalog/config_source_test.go b/agent/proxycfg-sources/catalog/config_source_test.go index 7b267023a6..79a7a85789 100644 --- a/agent/proxycfg-sources/catalog/config_source_test.go +++ b/agent/proxycfg-sources/catalog/config_source_test.go @@ -10,10 +10,11 @@ import ( "testing" "time" - "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/consul/agent/consul/state" "github.com/hashicorp/consul/agent/consul/stream" "github.com/hashicorp/consul/agent/grpc-external/limiter" @@ -21,9 +22,6 @@ import ( "github.com/hashicorp/consul/agent/proxycfg" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/agent/token" - proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot" - rtest "github.com/hashicorp/consul/internal/resource/resourcetest" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" ) func TestConfigSource_Success(t *testing.T) { @@ -80,15 +78,15 @@ func TestConfigSource_Success(t *testing.T) { }) t.Cleanup(mgr.Shutdown) - snapCh, termCh, _, cancelWatch1, err := mgr.Watch(rtest.Resource(pbmesh.ProxyConfigurationType, serviceID.ID).ID(), nodeName, token) + snapCh, termCh, _, cancelWatch1, err := mgr.Watch(serviceID, nodeName, token) require.NoError(t, err) require.Equal(t, session1TermCh, termCh) // Expect Register to have been called with the proxy's inital port. select { case snap := <-snapCh: - require.Equal(t, 9999, snap.(*proxycfg.ConfigSnapshot).Port) - require.Equal(t, token, snap.(*proxycfg.ConfigSnapshot).ProxyID.Token) + require.Equal(t, 9999, snap.Port) + require.Equal(t, token, snap.ProxyID.Token) case <-time.After(100 * time.Millisecond): t.Fatal("timeout waiting for snapshot") } @@ -112,7 +110,7 @@ func TestConfigSource_Success(t *testing.T) { // Expect Register to have been called again with the proxy's new port. select { case snap := <-snapCh: - require.Equal(t, 8888, snap.(*proxycfg.ConfigSnapshot).Port) + require.Equal(t, 8888, snap.Port) case <-time.After(100 * time.Millisecond): t.Fatal("timeout waiting for snapshot") } @@ -131,13 +129,13 @@ func TestConfigSource_Success(t *testing.T) { require.Equal(t, map[string]any{ "local_connect_timeout_ms": 123, "max_inbound_connections": 321, - }, snap.(*proxycfg.ConfigSnapshot).Proxy.Config) + }, snap.Proxy.Config) case <-time.After(100 * time.Millisecond): t.Fatal("timeout waiting for snapshot") } // Start another watch. - _, termCh2, _, cancelWatch2, err := mgr.Watch(rtest.Resource(pbmesh.ProxyConfigurationType, serviceID.ID).ID(), nodeName, token) + _, termCh2, _, cancelWatch2, err := mgr.Watch(serviceID, nodeName, token) require.NoError(t, err) require.Equal(t, session2TermCh, termCh2) @@ -171,7 +169,7 @@ func TestConfigSource_Success(t *testing.T) { func TestConfigSource_LocallyManagedService(t *testing.T) { serviceID := structs.NewServiceID("web-sidecar-proxy-1", nil) - proxyID := rtest.Resource(pbmesh.ProxyConfigurationType, serviceID.ID).ID() + proxyID := serviceID nodeName := "node-1" token := "token" @@ -180,7 +178,7 @@ func TestConfigSource_LocallyManagedService(t *testing.T) { localWatcher := NewMockWatcher(t) localWatcher.On("Watch", proxyID, nodeName, token). - Return(make(<-chan proxysnapshot.ProxySnapshot), nil, nil, proxysnapshot.CancelFunc(func() {}), nil) + Return(make(<-chan *proxycfg.ConfigSnapshot), nil, nil, context.CancelFunc(func() {}), nil) mgr := NewConfigSource(Config{ NodeName: nodeName, @@ -214,12 +212,12 @@ func TestConfigSource_ErrorRegisteringService(t *testing.T) { })) var canceledWatch bool - cancel := proxysnapshot.CancelFunc(func() { canceledWatch = true }) + cancel := context.CancelFunc(func() { canceledWatch = true }) cfgMgr := NewMockConfigManager(t) cfgMgr.On("Watch", mock.Anything). - Return(make(<-chan proxysnapshot.ProxySnapshot), cancel) + Return(make(<-chan *proxycfg.ConfigSnapshot), cancel) cfgMgr.On("Register", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything). Return(errors.New("KABOOM")) @@ -239,7 +237,7 @@ func TestConfigSource_ErrorRegisteringService(t *testing.T) { }) t.Cleanup(mgr.Shutdown) - _, _, _, _, err := mgr.Watch(rtest.Resource(pbmesh.ProxyConfigurationType, serviceID.ID).ID(), nodeName, token) + _, _, _, _, err := mgr.Watch(serviceID, nodeName, token) require.Error(t, err) require.True(t, canceledWatch, "watch should've been canceled") @@ -276,9 +274,9 @@ func TestConfigSource_ErrorInSyncLoop(t *testing.T) { NodeName: nodeName, Token: token, } - snapCh := make(chan proxysnapshot.ProxySnapshot, 1) + snapCh := make(chan *proxycfg.ConfigSnapshot, 1) cfgMgr.On("Watch", proxyID). - Return((<-chan proxysnapshot.ProxySnapshot)(snapCh), proxysnapshot.CancelFunc(func() {}), nil) + Return((<-chan *proxycfg.ConfigSnapshot)(snapCh), context.CancelFunc(func() {}), nil) // Answer the register call successfully for session 1 starting (Repeatability = 1). // Session 2 should not have caused a re-register to happen. @@ -330,21 +328,21 @@ func TestConfigSource_ErrorInSyncLoop(t *testing.T) { }) t.Cleanup(mgr.Shutdown) - snapCh, termCh, cfgSrcTerminated1, cancelWatch1, err := mgr.Watch(rtest.Resource(pbmesh.ProxyConfigurationType, serviceID.ID).ID(), nodeName, token) + snapCh, termCh, cfgSrcTerminated1, cancelWatch1, err := mgr.Watch(serviceID, nodeName, token) require.NoError(t, err) require.Equal(t, session1TermCh, termCh) // Expect Register to have been called with the proxy's inital port. select { case snap := <-snapCh: - require.Equal(t, 9999, snap.(*proxycfg.ConfigSnapshot).Port) - require.Equal(t, token, snap.(*proxycfg.ConfigSnapshot).ProxyID.Token) + require.Equal(t, 9999, snap.Port) + require.Equal(t, token, snap.ProxyID.Token) case <-time.After(100 * time.Millisecond): t.Fatal("timeout waiting for snapshot") } // Start another watch. - _, termCh2, cfgSrcTerminated2, cancelWatch2, err := mgr.Watch(rtest.Resource(pbmesh.ProxyConfigurationType, serviceID.ID).ID(), nodeName, token) + _, termCh2, cfgSrcTerminated2, cancelWatch2, err := mgr.Watch(serviceID, nodeName, token) require.NoError(t, err) require.Equal(t, session2TermCh, termCh2) @@ -424,12 +422,12 @@ func TestConfigSource_NotProxyService(t *testing.T) { })) var canceledWatch bool - cancel := proxysnapshot.CancelFunc(func() { canceledWatch = true }) + cancel := context.CancelFunc(func() { canceledWatch = true }) cfgMgr := NewMockConfigManager(t) cfgMgr.On("Watch", mock.Anything). - Return(make(<-chan proxysnapshot.ProxySnapshot), cancel) + Return(make(<-chan *proxycfg.ConfigSnapshot), cancel) mgr := NewConfigSource(Config{ Manager: cfgMgr, @@ -440,7 +438,7 @@ func TestConfigSource_NotProxyService(t *testing.T) { }) t.Cleanup(mgr.Shutdown) - _, _, _, _, err := mgr.Watch(rtest.Resource(pbmesh.ProxyConfigurationType, serviceID.ID).ID(), nodeName, token) + _, _, _, _, err := mgr.Watch(serviceID, nodeName, token) require.Error(t, err) require.Contains(t, err.Error(), "must be a sidecar proxy or gateway") require.True(t, canceledWatch, "watch should've been canceled") @@ -457,7 +455,7 @@ func TestConfigSource_SessionLimiterError(t *testing.T) { t.Cleanup(src.Shutdown) _, _, _, _, err := src.Watch( - rtest.Resource(pbmesh.ProxyConfigurationType, "web-sidecar-proxy-1").ID(), + structs.NewServiceID("web-sidecar-proxy-1", nil), "node-name", "token", ) @@ -475,9 +473,9 @@ func testConfigManager(t *testing.T, serviceID structs.ServiceID, nodeName strin Token: token, } - snapCh := make(chan proxysnapshot.ProxySnapshot, 1) + snapCh := make(chan *proxycfg.ConfigSnapshot, 1) cfgMgr.On("Watch", proxyID). - Return((<-chan proxysnapshot.ProxySnapshot)(snapCh), proxysnapshot.CancelFunc(func() {}), nil) + Return((<-chan *proxycfg.ConfigSnapshot)(snapCh), context.CancelFunc(func() {}), nil) cfgMgr.On("Register", mock.Anything, mock.Anything, source, token, false). Run(func(args mock.Arguments) { diff --git a/agent/proxycfg-sources/catalog/mock_ConfigManager.go b/agent/proxycfg-sources/catalog/mock_ConfigManager.go index 2c1608f241..dbd82e702b 100644 --- a/agent/proxycfg-sources/catalog/mock_ConfigManager.go +++ b/agent/proxycfg-sources/catalog/mock_ConfigManager.go @@ -5,8 +5,8 @@ package catalog import ( proxycfg "github.com/hashicorp/consul/agent/proxycfg" mock "github.com/stretchr/testify/mock" + "context" - proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot" structs "github.com/hashicorp/consul/agent/structs" ) @@ -36,27 +36,27 @@ func (_m *MockConfigManager) Register(proxyID proxycfg.ProxyID, service *structs } // Watch provides a mock function with given fields: req -func (_m *MockConfigManager) Watch(req proxycfg.ProxyID) (<-chan proxysnapshot.ProxySnapshot, proxysnapshot.CancelFunc) { +func (_m *MockConfigManager) Watch(req proxycfg.ProxyID) (<-chan *proxycfg.ConfigSnapshot, context.CancelFunc) { ret := _m.Called(req) - var r0 <-chan proxysnapshot.ProxySnapshot - var r1 proxysnapshot.CancelFunc - if rf, ok := ret.Get(0).(func(proxycfg.ProxyID) (<-chan proxysnapshot.ProxySnapshot, proxysnapshot.CancelFunc)); ok { + var r0 <-chan *proxycfg.ConfigSnapshot + var r1 context.CancelFunc + if rf, ok := ret.Get(0).(func(proxycfg.ProxyID) (<-chan *proxycfg.ConfigSnapshot, context.CancelFunc)); ok { return rf(req) } - if rf, ok := ret.Get(0).(func(proxycfg.ProxyID) <-chan proxysnapshot.ProxySnapshot); ok { + if rf, ok := ret.Get(0).(func(proxycfg.ProxyID) <-chan *proxycfg.ConfigSnapshot); ok { r0 = rf(req) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(<-chan proxysnapshot.ProxySnapshot) + r0 = ret.Get(0).(<-chan *proxycfg.ConfigSnapshot) } } - if rf, ok := ret.Get(1).(func(proxycfg.ProxyID) proxysnapshot.CancelFunc); ok { + if rf, ok := ret.Get(1).(func(proxycfg.ProxyID) context.CancelFunc); ok { r1 = rf(req) } else { if ret.Get(1) != nil { - r1 = ret.Get(1).(proxysnapshot.CancelFunc) + r1 = ret.Get(1).(context.CancelFunc) } } diff --git a/agent/proxycfg-sources/catalog/mock_Watcher.go b/agent/proxycfg-sources/catalog/mock_Watcher.go index f77ca13283..1fc6ba7c6e 100644 --- a/agent/proxycfg-sources/catalog/mock_Watcher.go +++ b/agent/proxycfg-sources/catalog/mock_Watcher.go @@ -5,12 +5,9 @@ package catalog import ( limiter "github.com/hashicorp/consul/agent/grpc-external/limiter" mock "github.com/stretchr/testify/mock" - - pbresource "github.com/hashicorp/consul/proto-public/pbresource" - + "github.com/hashicorp/consul/agent/structs" proxycfg "github.com/hashicorp/consul/agent/proxycfg" - - proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot" + "context" ) // MockWatcher is an autogenerated mock type for the Watcher type @@ -19,26 +16,26 @@ type MockWatcher struct { } // Watch provides a mock function with given fields: proxyID, nodeName, token -func (_m *MockWatcher) Watch(proxyID *pbresource.ID, nodeName string, token string) (<-chan proxysnapshot.ProxySnapshot, limiter.SessionTerminatedChan, proxycfg.SrcTerminatedChan, proxysnapshot.CancelFunc, error) { +func (_m *MockWatcher) Watch(proxyID structs.ServiceID, nodeName string, token string) (<-chan *proxycfg.ConfigSnapshot, limiter.SessionTerminatedChan, proxycfg.SrcTerminatedChan, context.CancelFunc, error) { ret := _m.Called(proxyID, nodeName, token) - var r0 <-chan proxysnapshot.ProxySnapshot + var r0 <-chan *proxycfg.ConfigSnapshot var r1 limiter.SessionTerminatedChan var r2 proxycfg.SrcTerminatedChan - var r3 proxysnapshot.CancelFunc + var r3 context.CancelFunc var r4 error - if rf, ok := ret.Get(0).(func(*pbresource.ID, string, string) (<-chan proxysnapshot.ProxySnapshot, limiter.SessionTerminatedChan, proxycfg.SrcTerminatedChan, proxysnapshot.CancelFunc, error)); ok { + if rf, ok := ret.Get(0).(func(structs.ServiceID, string, string) (<-chan *proxycfg.ConfigSnapshot, limiter.SessionTerminatedChan, proxycfg.SrcTerminatedChan, context.CancelFunc, error)); ok { return rf(proxyID, nodeName, token) } - if rf, ok := ret.Get(0).(func(*pbresource.ID, string, string) <-chan proxysnapshot.ProxySnapshot); ok { + if rf, ok := ret.Get(0).(func(structs.ServiceID, string, string) <-chan *proxycfg.ConfigSnapshot); ok { r0 = rf(proxyID, nodeName, token) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(<-chan proxysnapshot.ProxySnapshot) + r0 = ret.Get(0).(<-chan *proxycfg.ConfigSnapshot) } } - if rf, ok := ret.Get(1).(func(*pbresource.ID, string, string) limiter.SessionTerminatedChan); ok { + if rf, ok := ret.Get(1).(func(structs.ServiceID, string, string) limiter.SessionTerminatedChan); ok { r1 = rf(proxyID, nodeName, token) } else { if ret.Get(1) != nil { @@ -46,7 +43,7 @@ func (_m *MockWatcher) Watch(proxyID *pbresource.ID, nodeName string, token stri } } - if rf, ok := ret.Get(2).(func(*pbresource.ID, string, string) proxycfg.SrcTerminatedChan); ok { + if rf, ok := ret.Get(2).(func(structs.ServiceID, string, string) proxycfg.SrcTerminatedChan); ok { r2 = rf(proxyID, nodeName, token) } else { if ret.Get(2) != nil { @@ -54,15 +51,15 @@ func (_m *MockWatcher) Watch(proxyID *pbresource.ID, nodeName string, token stri } } - if rf, ok := ret.Get(3).(func(*pbresource.ID, string, string) proxysnapshot.CancelFunc); ok { + if rf, ok := ret.Get(3).(func(structs.ServiceID, string, string) context.CancelFunc); ok { r3 = rf(proxyID, nodeName, token) } else { if ret.Get(3) != nil { - r3 = ret.Get(3).(proxysnapshot.CancelFunc) + r3 = ret.Get(3).(context.CancelFunc) } } - if rf, ok := ret.Get(4).(func(*pbresource.ID, string, string) error); ok { + if rf, ok := ret.Get(4).(func(structs.ServiceID, string, string) error); ok { r4 = rf(proxyID, nodeName, token) } else { r4 = ret.Error(4) diff --git a/agent/proxycfg-sources/local/config_source.go b/agent/proxycfg-sources/local/config_source.go index d30edc1b7b..e3176c597d 100644 --- a/agent/proxycfg-sources/local/config_source.go +++ b/agent/proxycfg-sources/local/config_source.go @@ -4,12 +4,11 @@ package local import ( + "context" + "github.com/hashicorp/consul/agent/grpc-external/limiter" "github.com/hashicorp/consul/agent/proxycfg" - "github.com/hashicorp/consul/agent/proxycfg-sources/catalog" structs "github.com/hashicorp/consul/agent/structs" - proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot" - "github.com/hashicorp/consul/proto-public/pbresource" ) // ConfigSource wraps a proxycfg.Manager to create watches on services @@ -23,14 +22,13 @@ func NewConfigSource(cfgMgr ConfigManager) *ConfigSource { return &ConfigSource{cfgMgr} } -func (m *ConfigSource) Watch(proxyID *pbresource.ID, nodeName string, _ string) ( - <-chan proxysnapshot.ProxySnapshot, +func (m *ConfigSource) Watch(serviceID structs.ServiceID, nodeName string, _ string) ( + <-chan *proxycfg.ConfigSnapshot, limiter.SessionTerminatedChan, proxycfg.SrcTerminatedChan, - proxysnapshot.CancelFunc, + context.CancelFunc, error, ) { - serviceID := structs.NewServiceID(proxyID.Name, catalog.GetEnterpriseMetaFromResourceID(proxyID)) watchCh, cancelWatch := m.manager.Watch(proxycfg.ProxyID{ ServiceID: serviceID, NodeName: nodeName, diff --git a/agent/proxycfg-sources/local/mock_ConfigManager.go b/agent/proxycfg-sources/local/mock_ConfigManager.go index e3b2d3a445..66b204d131 100644 --- a/agent/proxycfg-sources/local/mock_ConfigManager.go +++ b/agent/proxycfg-sources/local/mock_ConfigManager.go @@ -5,8 +5,8 @@ package local import ( proxycfg "github.com/hashicorp/consul/agent/proxycfg" mock "github.com/stretchr/testify/mock" + "context" - proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot" structs "github.com/hashicorp/consul/agent/structs" ) @@ -52,27 +52,27 @@ func (_m *MockConfigManager) RegisteredProxies(source proxycfg.ProxySource) []pr } // Watch provides a mock function with given fields: id -func (_m *MockConfigManager) Watch(id proxycfg.ProxyID) (<-chan proxysnapshot.ProxySnapshot, proxysnapshot.CancelFunc) { +func (_m *MockConfigManager) Watch(id proxycfg.ProxyID) (<-chan *proxycfg.ConfigSnapshot, context.CancelFunc) { ret := _m.Called(id) - var r0 <-chan proxysnapshot.ProxySnapshot - var r1 proxysnapshot.CancelFunc - if rf, ok := ret.Get(0).(func(proxycfg.ProxyID) (<-chan proxysnapshot.ProxySnapshot, proxysnapshot.CancelFunc)); ok { + var r0 <-chan *proxycfg.ConfigSnapshot + var r1 context.CancelFunc + if rf, ok := ret.Get(0).(func(proxycfg.ProxyID) (<-chan *proxycfg.ConfigSnapshot, context.CancelFunc)); ok { return rf(id) } - if rf, ok := ret.Get(0).(func(proxycfg.ProxyID) <-chan proxysnapshot.ProxySnapshot); ok { + if rf, ok := ret.Get(0).(func(proxycfg.ProxyID) <-chan *proxycfg.ConfigSnapshot); ok { r0 = rf(id) } else { if ret.Get(0) != nil { - r0 = ret.Get(0).(<-chan proxysnapshot.ProxySnapshot) + r0 = ret.Get(0).(<-chan *proxycfg.ConfigSnapshot) } } - if rf, ok := ret.Get(1).(func(proxycfg.ProxyID) proxysnapshot.CancelFunc); ok { + if rf, ok := ret.Get(1).(func(proxycfg.ProxyID) context.CancelFunc); ok { r1 = rf(id) } else { if ret.Get(1) != nil { - r1 = ret.Get(1).(proxysnapshot.CancelFunc) + r1 = ret.Get(1).(context.CancelFunc) } } diff --git a/agent/proxycfg-sources/local/sync.go b/agent/proxycfg-sources/local/sync.go index 54d95e6594..a8047c82f1 100644 --- a/agent/proxycfg-sources/local/sync.go +++ b/agent/proxycfg-sources/local/sync.go @@ -7,8 +7,6 @@ import ( "context" "time" - proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot" - "github.com/hashicorp/go-hclog" "github.com/hashicorp/consul/agent/local" @@ -148,7 +146,7 @@ func sync(cfg SyncConfig) { //go:generate mockery --name ConfigManager --inpackage type ConfigManager interface { - Watch(id proxycfg.ProxyID) (<-chan proxysnapshot.ProxySnapshot, proxysnapshot.CancelFunc) + Watch(id proxycfg.ProxyID) (<-chan *proxycfg.ConfigSnapshot, context.CancelFunc) Register(proxyID proxycfg.ProxyID, service *structs.NodeService, source proxycfg.ProxySource, token string, overwrite bool) error Deregister(proxyID proxycfg.ProxyID, source proxycfg.ProxySource) RegisteredProxies(source proxycfg.ProxySource) []proxycfg.ProxyID diff --git a/agent/proxycfg/manager.go b/agent/proxycfg/manager.go index 4d3dd6cbc7..f2f7978a0a 100644 --- a/agent/proxycfg/manager.go +++ b/agent/proxycfg/manager.go @@ -4,17 +4,17 @@ package proxycfg import ( + "context" "errors" "runtime/debug" "sync" - "github.com/hashicorp/consul/lib/channels" - - "github.com/hashicorp/go-hclog" "golang.org/x/time/rate" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/consul/agent/structs" - proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot" + "github.com/hashicorp/consul/lib/channels" "github.com/hashicorp/consul/tlsutil" ) @@ -58,7 +58,7 @@ type Manager struct { mu sync.Mutex proxies map[ProxyID]*state - watchers map[ProxyID]map[uint64]chan proxysnapshot.ProxySnapshot + watchers map[ProxyID]map[uint64]chan *ConfigSnapshot maxWatchID uint64 } @@ -109,7 +109,7 @@ func NewManager(cfg ManagerConfig) (*Manager, error) { m := &Manager{ ManagerConfig: cfg, proxies: make(map[ProxyID]*state), - watchers: make(map[ProxyID]map[uint64]chan proxysnapshot.ProxySnapshot), + watchers: make(map[ProxyID]map[uint64]chan *ConfigSnapshot), rateLimiter: rate.NewLimiter(cfg.UpdateRateLimit, 1), } return m, nil @@ -265,12 +265,12 @@ func (m *Manager) notify(snap *ConfigSnapshot) { // it will drain the chan and then re-attempt delivery so that a slow consumer // gets the latest config earlier. This MUST be called from a method where m.mu // is held to be safe since it assumes we are the only goroutine sending on ch. -func (m *Manager) deliverLatest(snap proxysnapshot.ProxySnapshot, ch chan proxysnapshot.ProxySnapshot) { - m.Logger.Trace("delivering latest proxy snapshot to proxy", "proxyID", snap.(*ConfigSnapshot).ProxyID) +func (m *Manager) deliverLatest(snap *ConfigSnapshot, ch chan *ConfigSnapshot) { + m.Logger.Trace("delivering latest proxy snapshot to proxy", "proxyID", snap.ProxyID) err := channels.DeliverLatest(snap, ch) if err != nil { m.Logger.Error("failed to deliver proxyState to proxy", - "proxy", snap.(*ConfigSnapshot).ProxyID, + "proxy", snap.ProxyID, ) } @@ -280,16 +280,16 @@ func (m *Manager) deliverLatest(snap proxysnapshot.ProxySnapshot, ch chan proxys // will not fail, but no updates will be delivered until the proxy is // registered. If there is already a valid snapshot in memory, it will be // delivered immediately. -func (m *Manager) Watch(id ProxyID) (<-chan proxysnapshot.ProxySnapshot, proxysnapshot.CancelFunc) { +func (m *Manager) Watch(id ProxyID) (<-chan *ConfigSnapshot, context.CancelFunc) { m.mu.Lock() defer m.mu.Unlock() // This buffering is crucial otherwise we'd block immediately trying to // deliver the current snapshot below if we already have one. - ch := make(chan proxysnapshot.ProxySnapshot, 1) + ch := make(chan *ConfigSnapshot, 1) watchers, ok := m.watchers[id] if !ok { - watchers = make(map[uint64]chan proxysnapshot.ProxySnapshot) + watchers = make(map[uint64]chan *ConfigSnapshot) } watchID := m.maxWatchID m.maxWatchID++ diff --git a/agent/proxycfg/manager_test.go b/agent/proxycfg/manager_test.go index 7c83b5c770..e751364ddb 100644 --- a/agent/proxycfg/manager_test.go +++ b/agent/proxycfg/manager_test.go @@ -17,7 +17,6 @@ import ( "github.com/hashicorp/consul/agent/proxycfg/internal/watch" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" - proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot" "github.com/hashicorp/consul/proto/private/pbpeering" "github.com/hashicorp/consul/sdk/testutil" ) @@ -471,7 +470,7 @@ func testManager_BasicLifecycle( require.Len(t, m.watchers, 0) } -func assertWatchChanBlocks(t *testing.T, ch <-chan proxysnapshot.ProxySnapshot) { +func assertWatchChanBlocks(t *testing.T, ch <-chan *ConfigSnapshot) { t.Helper() select { @@ -481,7 +480,7 @@ func assertWatchChanBlocks(t *testing.T, ch <-chan proxysnapshot.ProxySnapshot) } } -func assertWatchChanRecvs(t *testing.T, ch <-chan proxysnapshot.ProxySnapshot, expect proxysnapshot.ProxySnapshot) { +func assertWatchChanRecvs(t *testing.T, ch <-chan *ConfigSnapshot, expect *ConfigSnapshot) { t.Helper() select { @@ -519,7 +518,7 @@ func TestManager_deliverLatest(t *testing.T) { } // test 1 buffered chan - ch1 := make(chan proxysnapshot.ProxySnapshot, 1) + ch1 := make(chan *ConfigSnapshot, 1) // Sending to an unblocked chan should work m.deliverLatest(snap1, ch1) @@ -535,7 +534,7 @@ func TestManager_deliverLatest(t *testing.T) { require.Equal(t, snap2, <-ch1) // Same again for 5-buffered chan - ch5 := make(chan proxysnapshot.ProxySnapshot, 5) + ch5 := make(chan *ConfigSnapshot, 5) // Sending to an unblocked chan should work m.deliverLatest(snap1, ch5) diff --git a/agent/proxycfg/proxycfg.deepcopy.go b/agent/proxycfg/proxycfg.deepcopy.go index 669a519094..1922810fe5 100644 --- a/agent/proxycfg/proxycfg.deepcopy.go +++ b/agent/proxycfg/proxycfg.deepcopy.go @@ -1,4 +1,4 @@ -// generated by deep-copy -pointer-receiver -o ./proxycfg.deepcopy.go -type ConfigSnapshot -type ConfigSnapshotUpstreams -type PeerServersValue -type PeeringServiceValue -type configSnapshotAPIGateway -type configSnapshotConnectProxy -type configSnapshotIngressGateway -type configSnapshotMeshGateway -type configSnapshotTerminatingGateway ./; DO NOT EDIT. +// Code generated by deep-copy -pointer-receiver -o ./proxycfg.deepcopy.go -type ConfigSnapshot -type ConfigSnapshotUpstreams -type PeerServersValue -type PeeringServiceValue -type configSnapshotAPIGateway -type configSnapshotConnectProxy -type configSnapshotIngressGateway -type configSnapshotMeshGateway -type configSnapshotTerminatingGateway ./; DO NOT EDIT. package proxycfg diff --git a/agent/proxycfg_test.go b/agent/proxycfg_test.go index 568d616486..d692c25789 100644 --- a/agent/proxycfg_test.go +++ b/agent/proxycfg_test.go @@ -4,6 +4,7 @@ package agent import ( + "context" "encoding/json" "net/http" "net/http/httptest" @@ -13,11 +14,9 @@ import ( "github.com/stretchr/testify/require" "github.com/hashicorp/consul/agent/grpc-external/limiter" + "github.com/hashicorp/consul/agent/proxycfg" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" - proxysnapshot "github.com/hashicorp/consul/internal/mesh/proxy-snapshot" - rtest "github.com/hashicorp/consul/internal/resource/resourcetest" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" "github.com/hashicorp/consul/testrpc" ) @@ -64,9 +63,9 @@ func TestAgent_local_proxycfg(t *testing.T) { var ( firstTime = true - ch <-chan proxysnapshot.ProxySnapshot + ch <-chan *proxycfg.ConfigSnapshot stc limiter.SessionTerminatedChan - cancel proxysnapshot.CancelFunc + cancel context.CancelFunc ) defer func() { if cancel != nil { @@ -87,7 +86,7 @@ func TestAgent_local_proxycfg(t *testing.T) { // Prior to fixes in https://github.com/hashicorp/consul/pull/16497 // this call to Watch() would deadlock. var err error - ch, stc, _, cancel, err = cfg.Watch(rtest.Resource(pbmesh.ProxyConfigurationType, sid.ID).ID(), a.config.NodeName, token) + ch, stc, _, cancel, err = cfg.Watch(sid, a.config.NodeName, token) require.NoError(t, err) } select { diff --git a/agent/router/router.go b/agent/router/router.go index c261b6ed7c..af6b0987cf 100644 --- a/agent/router/router.go +++ b/agent/router/router.go @@ -14,7 +14,7 @@ import ( "github.com/hashicorp/consul/agent/metadata" "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/logging" "github.com/hashicorp/consul/types" ) @@ -578,7 +578,7 @@ func (r *Router) GetDatacentersByDistance() ([]string, error) { // It's OK to get a nil coordinate back, ComputeDistance // will put the RTT at positive infinity. other, _ := info.cluster.GetCachedCoordinate(parts.Name) - rtt := lib.ComputeDistance(coord, other) + rtt := librtt.ComputeDistance(coord, other) index[parts.Datacenter] = append(existing, rtt) } } diff --git a/agent/router/router_test.go b/agent/router/router_test.go index 206b0befe8..452f1c5a49 100644 --- a/agent/router/router_test.go +++ b/agent/router/router_test.go @@ -12,15 +12,15 @@ import ( "testing" "time" + "github.com/stretchr/testify/require" + "github.com/hashicorp/serf/coordinate" "github.com/hashicorp/serf/serf" "github.com/hashicorp/consul/agent/structs" - "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/internal/gossip/librtt" "github.com/hashicorp/consul/sdk/testutil" "github.com/hashicorp/consul/types" - - "github.com/stretchr/testify/require" ) type mockCluster struct { @@ -109,12 +109,12 @@ func (m *mockCluster) AddLANMember(dc, name, role string, coord *coordinate.Coor // mysterious dcX with no nodes with known coordinates. func testCluster(self string) *mockCluster { c := newMockCluster(self) - c.AddMember("dc0", "node0", lib.GenerateCoordinate(10*time.Millisecond)) - c.AddMember("dc1", "node1", lib.GenerateCoordinate(3*time.Millisecond)) - c.AddMember("dc1", "node2", lib.GenerateCoordinate(2*time.Millisecond)) - c.AddMember("dc1", "node3", lib.GenerateCoordinate(5*time.Millisecond)) + c.AddMember("dc0", "node0", librtt.GenerateCoordinate(10*time.Millisecond)) + c.AddMember("dc1", "node1", librtt.GenerateCoordinate(3*time.Millisecond)) + c.AddMember("dc1", "node2", librtt.GenerateCoordinate(2*time.Millisecond)) + c.AddMember("dc1", "node3", librtt.GenerateCoordinate(5*time.Millisecond)) c.AddMember("dc1", "node4", nil) - c.AddMember("dc2", "node1", lib.GenerateCoordinate(8*time.Millisecond)) + c.AddMember("dc2", "node1", librtt.GenerateCoordinate(8*time.Millisecond)) c.AddMember("dcX", "node1", nil) return c } @@ -427,8 +427,8 @@ func TestRouter_GetDatacentersByDistance(t *testing.T) { // Now add another area with a closer route for dc1. otherID := types.AreaID("other") other := newMockCluster(self) - other.AddMember("dc0", "node0", lib.GenerateCoordinate(20*time.Millisecond)) - other.AddMember("dc1", "node1", lib.GenerateCoordinate(21*time.Millisecond)) + other.AddMember("dc0", "node0", librtt.GenerateCoordinate(20*time.Millisecond)) + other.AddMember("dc1", "node1", librtt.GenerateCoordinate(21*time.Millisecond)) if err := r.AddArea(otherID, other, &fauxConnPool{}); err != nil { t.Fatalf("err: %v", err) } @@ -468,7 +468,7 @@ func TestRouter_GetDatacenterMaps(t *testing.T) { Coordinates: structs.Coordinates{ &structs.Coordinate{ Node: "node0.dc0", - Coord: lib.GenerateCoordinate(10 * time.Millisecond), + Coord: librtt.GenerateCoordinate(10 * time.Millisecond), }, }, }) { @@ -481,15 +481,15 @@ func TestRouter_GetDatacenterMaps(t *testing.T) { Coordinates: structs.Coordinates{ &structs.Coordinate{ Node: "node1.dc1", - Coord: lib.GenerateCoordinate(3 * time.Millisecond), + Coord: librtt.GenerateCoordinate(3 * time.Millisecond), }, &structs.Coordinate{ Node: "node2.dc1", - Coord: lib.GenerateCoordinate(2 * time.Millisecond), + Coord: librtt.GenerateCoordinate(2 * time.Millisecond), }, &structs.Coordinate{ Node: "node3.dc1", - Coord: lib.GenerateCoordinate(5 * time.Millisecond), + Coord: librtt.GenerateCoordinate(5 * time.Millisecond), }, }, }) { @@ -502,7 +502,7 @@ func TestRouter_GetDatacenterMaps(t *testing.T) { Coordinates: structs.Coordinates{ &structs.Coordinate{ Node: "node1.dc2", - Coord: lib.GenerateCoordinate(8 * time.Millisecond), + Coord: librtt.GenerateCoordinate(8 * time.Millisecond), }, }, }) { @@ -518,9 +518,9 @@ func TestRouter_FindLANServer(t *testing.T) { r := testRouter(t, "dc0") lan := newMockCluster("node4.dc0") - lan.AddLANMember("dc0", "node0", "consul", lib.GenerateCoordinate(10*time.Millisecond)) - lan.AddLANMember("dc0", "node1", "", lib.GenerateCoordinate(20*time.Millisecond)) - lan.AddLANMember("dc0", "node2", "", lib.GenerateCoordinate(21*time.Millisecond)) + lan.AddLANMember("dc0", "node0", "consul", librtt.GenerateCoordinate(10*time.Millisecond)) + lan.AddLANMember("dc0", "node1", "", librtt.GenerateCoordinate(20*time.Millisecond)) + lan.AddLANMember("dc0", "node2", "", librtt.GenerateCoordinate(21*time.Millisecond)) require.NoError(t, r.AddArea(types.AreaLAN, lan, &fauxConnPool{})) diff --git a/agent/rpc/peering/service_test.go b/agent/rpc/peering/service_test.go index efc3bff697..f385a11c28 100644 --- a/agent/rpc/peering/service_test.go +++ b/agent/rpc/peering/service_test.go @@ -16,8 +16,6 @@ import ( "time" "github.com/google/tcpproxy" - "github.com/hashicorp/go-hclog" - "github.com/hashicorp/go-uuid" "github.com/stretchr/testify/require" gogrpc "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -26,6 +24,9 @@ import ( grpcstatus "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-uuid" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/connect" "github.com/hashicorp/consul/agent/consul" @@ -1835,7 +1836,7 @@ func newTestServer(t *testing.T, cb func(conf *consul.Config)) testingServer { deps := newDefaultDeps(t, conf) externalGRPCServer := external.NewServer(deps.Logger, nil, deps.TLSConfigurator, rate.NullRequestLimitsHandler(), keepalive.ServerParameters{}, nil) - server, err := consul.NewServer(conf, deps, externalGRPCServer, nil, deps.Logger, nil) + server, err := consul.NewServer(conf, deps, externalGRPCServer, nil, deps.Logger) require.NoError(t, err) t.Cleanup(func() { require.NoError(t, server.Shutdown()) diff --git a/agent/rpcclient/health/health.go b/agent/rpcclient/health/health.go index 6732b2f88e..c20daa2e82 100644 --- a/agent/rpcclient/health/health.go +++ b/agent/rpcclient/health/health.go @@ -105,7 +105,12 @@ func (c *Client) useStreaming(req structs.ServiceSpecificRequest) bool { // Streaming is incompatible with NearestN queries (due to lack of ordering), // so we can only use it if the NearestN would never work (Node == "") // or if we explicitly say to ignore the Node field for queries (agentless xDS). - (req.Source.Node == "" || req.Source.DisableNode) + (req.Source.Node == "" || req.Source.DisableNode) && + // Streaming is incompatible with SamenessGroup queries at the moment because + // the subscribe functionality maps to queries based on the service name and tenancy information + // it does not support the ability to subscribe to the same service in different partitions or peers + // and materialize the results into a single view with the first healthy sameness group member. + req.SamenessGroup == "" } func (c *Client) newServiceRequest(req structs.ServiceSpecificRequest) serviceRequest { diff --git a/agent/rpcclient/health/health_test.go b/agent/rpcclient/health/health_test.go index 30900bc04c..24c1de120f 100644 --- a/agent/rpcclient/health/health_test.go +++ b/agent/rpcclient/health/health_test.go @@ -98,6 +98,17 @@ func TestClient_ServiceNodes_BackendRouting(t *testing.T) { }, expected: useRPC, }, + { + name: "rpc if sameness group", + req: structs.ServiceSpecificRequest{ + Datacenter: "dc1", + ServiceName: "web1", + SamenessGroup: "sg1", + MergeCentralConfig: false, + QueryOptions: structs.QueryOptions{MinQueryIndex: 22}, + }, + expected: useRPC, + }, } for _, tc := range testCases { @@ -246,6 +257,15 @@ func TestClient_Notify_BackendRouting(t *testing.T) { }, expected: useCache, }, + { + name: "use cache for sameness group request", + req: structs.ServiceSpecificRequest{ + Datacenter: "dc1", + ServiceName: "web1", + SamenessGroup: "test-group", + }, + expected: useCache, + }, } for _, tc := range testCases { diff --git a/agent/rpcclient/health/view.go b/agent/rpcclient/health/view.go index 8e08ba801e..2f5b43a4d0 100644 --- a/agent/rpcclient/health/view.go +++ b/agent/rpcclient/health/view.go @@ -46,10 +46,11 @@ func NewHealthView(req structs.ServiceSpecificRequest) (*HealthView, error) { return nil, err } return &HealthView{ - state: make(map[string]structs.CheckServiceNode), - filter: fe, - connect: req.Connect, - kind: req.ServiceKind, + state: make(map[string]structs.CheckServiceNode), + filter: fe, + connect: req.Connect, + kind: req.ServiceKind, + healthFilterType: req.HealthFilterType, }, nil } @@ -61,10 +62,11 @@ var _ submatview.View = (*HealthView)(nil) // (IndexedCheckServiceNodes) and update it in place for each event - that // involves re-sorting each time etc. though. type HealthView struct { - connect bool - kind structs.ServiceKind - state map[string]structs.CheckServiceNode - filter filterEvaluator + connect bool + kind structs.ServiceKind + state map[string]structs.CheckServiceNode + filter filterEvaluator + healthFilterType structs.HealthFilterType } // Update implements View @@ -87,6 +89,11 @@ func (s *HealthView) Update(events []*pbsubscribe.Event) error { return errors.New("check service node was unexpectedly nil") } + if csn.ExcludeBasedOnChecks(structs.CheckServiceNodeFilterOptions{FilterType: s.healthFilterType}) { + delete(s.state, id) + continue + } + // check if we intentionally need to skip the filter if s.skipFilter(csn) { s.state[id] = *csn diff --git a/agent/setup.go b/agent/setup.go index e9103fb75a..ce64aa7eb2 100644 --- a/agent/setup.go +++ b/agent/setup.go @@ -28,7 +28,6 @@ import ( "github.com/hashicorp/consul/agent/consul/stream" "github.com/hashicorp/consul/agent/consul/usagemetrics" "github.com/hashicorp/consul/agent/consul/xdscapacity" - "github.com/hashicorp/consul/agent/discovery" "github.com/hashicorp/consul/agent/grpc-external/limiter" grpcInt "github.com/hashicorp/consul/agent/grpc-internal" "github.com/hashicorp/consul/agent/grpc-internal/balancer" @@ -437,7 +436,6 @@ func getPrometheusDefs(cfg *config.RuntimeConfig, isServer bool) ([]prometheus.G consul.CatalogCounters, consul.ClientCounters, consul.RPCCounters, - discovery.DNSCounters, grpcWare.StatsCounters, local.StateCounters, xds.StatsCounters, diff --git a/agent/structs/acl.go b/agent/structs/acl.go index d856ce0af2..579e8d231e 100644 --- a/agent/structs/acl.go +++ b/agent/structs/acl.go @@ -13,13 +13,12 @@ import ( "strings" "time" - "github.com/hashicorp/consul/api" - "github.com/hashicorp/consul/lib/stringslice" - "golang.org/x/crypto/blake2b" "github.com/hashicorp/consul/acl" + "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/lib/stringslice" ) type ACLMode string @@ -63,10 +62,6 @@ agent_prefix "" { event_prefix "" { policy = "%[1]s" } -identity_prefix "" { - policy = "%[1]s" - intentions = "%[1]s" -} key_prefix "" { policy = "%[1]s" } diff --git a/agent/structs/acl_templated_policy.go b/agent/structs/acl_templated_policy.go index 04c85515ff..076d6ae256 100644 --- a/agent/structs/acl_templated_policy.go +++ b/agent/structs/acl_templated_policy.go @@ -9,12 +9,13 @@ import ( "fmt" "hash" "hash/fnv" - "html/template" + "text/template" - "github.com/hashicorp/go-multierror" "github.com/xeipuuv/gojsonschema" "golang.org/x/exp/slices" + "github.com/hashicorp/go-multierror" + "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/lib/stringslice" @@ -26,30 +27,26 @@ var ACLTemplatedPolicyNodeSchema string //go:embed acltemplatedpolicy/schemas/service.json var ACLTemplatedPolicyServiceSchema string -//go:embed acltemplatedpolicy/schemas/workload-identity.json -var ACLTemplatedPolicyWorkloadIdentitySchema string - //go:embed acltemplatedpolicy/schemas/api-gateway.json var ACLTemplatedPolicyAPIGatewaySchema string type ACLTemplatedPolicies []*ACLTemplatedPolicy const ( - ACLTemplatedPolicyServiceID = "00000000-0000-0000-0000-000000000003" - ACLTemplatedPolicyNodeID = "00000000-0000-0000-0000-000000000004" - ACLTemplatedPolicyDNSID = "00000000-0000-0000-0000-000000000005" - ACLTemplatedPolicyNomadServerID = "00000000-0000-0000-0000-000000000006" - ACLTemplatedPolicyWorkloadIdentityID = "00000000-0000-0000-0000-000000000007" - ACLTemplatedPolicyAPIGatewayID = "00000000-0000-0000-0000-000000000008" - ACLTemplatedPolicyNomadClientID = "00000000-0000-0000-0000-000000000009" + ACLTemplatedPolicyServiceID = "00000000-0000-0000-0000-000000000003" + ACLTemplatedPolicyNodeID = "00000000-0000-0000-0000-000000000004" + ACLTemplatedPolicyDNSID = "00000000-0000-0000-0000-000000000005" + ACLTemplatedPolicyNomadServerID = "00000000-0000-0000-0000-000000000006" + _ = "00000000-0000-0000-0000-000000000007" // formerly workload identity + ACLTemplatedPolicyAPIGatewayID = "00000000-0000-0000-0000-000000000008" + ACLTemplatedPolicyNomadClientID = "00000000-0000-0000-0000-000000000009" - ACLTemplatedPolicyServiceDescription = "Gives the token or role permissions to register a service and discover services in the Consul catalog. It also gives the specified service's sidecar proxy the permission to discover and route traffic to other services." - ACLTemplatedPolicyNodeDescription = "Gives the token or role permissions for a register an agent/node into the catalog. A node is typically a consul agent but can also be a physical server, cloud instance or a container." - ACLTemplatedPolicyDNSDescription = "Gives the token or role permissions for the Consul DNS to query services in the network." - ACLTemplatedPolicyNomadServerDescription = "Gives the token or role permissions required for integration with a nomad server." - ACLTemplatedPolicyWorkloadIdentityDescription = "Gives the token or role permissions for a specific workload identity." - ACLTemplatedPolicyAPIGatewayDescription = "Gives the token or role permissions for a Consul api gateway" - ACLTemplatedPolicyNomadClientDescription = "Gives the token or role permissions required for integration with a nomad client." + ACLTemplatedPolicyServiceDescription = "Gives the token or role permissions to register a service and discover services in the Consul catalog. It also gives the specified service's sidecar proxy the permission to discover and route traffic to other services." + ACLTemplatedPolicyNodeDescription = "Gives the token or role permissions for a register an agent/node into the catalog. A node is typically a consul agent but can also be a physical server, cloud instance or a container." + ACLTemplatedPolicyDNSDescription = "Gives the token or role permissions for the Consul DNS to query services in the network." + ACLTemplatedPolicyNomadServerDescription = "Gives the token or role permissions required for integration with a nomad server." + ACLTemplatedPolicyAPIGatewayDescription = "Gives the token or role permissions for a Consul api gateway" + ACLTemplatedPolicyNomadClientDescription = "Gives the token or role permissions required for integration with a nomad client." ACLTemplatedPolicyNoRequiredVariablesSchema = "" // catch-all schema for all templated policy that don't require a schema ) @@ -96,13 +93,6 @@ var ( Template: ACLTemplatedPolicyNomadServer, Description: ACLTemplatedPolicyNomadServerDescription, }, - api.ACLTemplatedPolicyWorkloadIdentityName: { - TemplateID: ACLTemplatedPolicyWorkloadIdentityID, - TemplateName: api.ACLTemplatedPolicyWorkloadIdentityName, - Schema: ACLTemplatedPolicyWorkloadIdentitySchema, - Template: ACLTemplatedPolicyWorkloadIdentity, - Description: ACLTemplatedPolicyWorkloadIdentityDescription, - }, api.ACLTemplatedPolicyAPIGatewayName: { TemplateID: ACLTemplatedPolicyAPIGatewayID, TemplateName: api.ACLTemplatedPolicyAPIGatewayName, diff --git a/agent/structs/acl_templated_policy_ce.go b/agent/structs/acl_templated_policy_ce.go index 23e656f0fb..3cbaa22217 100644 --- a/agent/structs/acl_templated_policy_ce.go +++ b/agent/structs/acl_templated_policy_ce.go @@ -19,9 +19,6 @@ var ACLTemplatedPolicyDNS string //go:embed acltemplatedpolicy/policies/ce/nomad-server.hcl var ACLTemplatedPolicyNomadServer string -//go:embed acltemplatedpolicy/policies/ce/workload-identity.hcl -var ACLTemplatedPolicyWorkloadIdentity string - //go:embed acltemplatedpolicy/policies/ce/api-gateway.hcl var ACLTemplatedPolicyAPIGateway string diff --git a/agent/structs/acl_templated_policy_ce_test.go b/agent/structs/acl_templated_policy_ce_test.go index f212928062..63f42ca83e 100644 --- a/agent/structs/acl_templated_policy_ce_test.go +++ b/agent/structs/acl_templated_policy_ce_test.go @@ -80,21 +80,6 @@ service_prefix "" { } query_prefix "" { policy = "read" -}`, - }, - }, - "workload-identity-template": { - templatedPolicy: &ACLTemplatedPolicy{ - TemplateID: ACLTemplatedPolicyWorkloadIdentityID, - TemplateName: api.ACLTemplatedPolicyWorkloadIdentityName, - TemplateVariables: &ACLTemplatedPolicyVariables{ - Name: "api", - }, - }, - expectedPolicy: &ACLPolicy{ - Description: "synthetic policy generated from templated policy: builtin/workload-identity", - Rules: `identity "api" { - policy = "write" }`, }, }, diff --git a/agent/structs/acltemplatedpolicy/policies/ce/workload-identity.hcl b/agent/structs/acltemplatedpolicy/policies/ce/workload-identity.hcl deleted file mode 100644 index ccd1e05646..0000000000 --- a/agent/structs/acltemplatedpolicy/policies/ce/workload-identity.hcl +++ /dev/null @@ -1,3 +0,0 @@ -identity "{{.Name}}" { - policy = "write" -} \ No newline at end of file diff --git a/agent/structs/acltemplatedpolicy/schemas/workload-identity.json b/agent/structs/acltemplatedpolicy/schemas/workload-identity.json deleted file mode 100644 index 31064f36af..0000000000 --- a/agent/structs/acltemplatedpolicy/schemas/workload-identity.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "object", - "properties": { - "name": { "type": "string", "$ref": "#/definitions/min-length-one" } - }, - "required": ["name"], - "definitions": { - "min-length-one": { - "type": "string", - "minLength": 1 - } - } -} \ No newline at end of file diff --git a/agent/structs/config_entry.go b/agent/structs/config_entry.go index 5d419e0832..32b4e0c89d 100644 --- a/agent/structs/config_entry.go +++ b/agent/structs/config_entry.go @@ -12,12 +12,11 @@ import ( "time" "github.com/miekg/dns" - - "github.com/hashicorp/go-multierror" "github.com/mitchellh/hashstructure" "github.com/mitchellh/mapstructure" "github.com/hashicorp/consul-net-rpc/go-msgpack/codec" + "github.com/hashicorp/go-multierror" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/cache" @@ -269,6 +268,12 @@ func (e *ServiceConfigEntry) Validate() error { validationErr = multierror.Append(validationErr, fmt.Errorf("invalid value for balance_inbound_connections: %v", e.BalanceInboundConnections)) } + switch e.Protocol { + case "", "http", "http2", "grpc", "tcp": + default: + validationErr = multierror.Append(validationErr, fmt.Errorf("invalid value for protocol: %v", e.Protocol)) + } + // External endpoints are invalid with an existing service's upstream configuration if e.UpstreamConfig != nil && e.Destination != nil { validationErr = multierror.Append(validationErr, errors.New("UpstreamConfig and Destination are mutually exclusive for service defaults")) diff --git a/agent/structs/config_entry_intentions.go b/agent/structs/config_entry_intentions.go index d06a260a59..6da3160dd6 100644 --- a/agent/structs/config_entry_intentions.go +++ b/agent/structs/config_entry_intentions.go @@ -426,13 +426,15 @@ func (p *IntentionHTTPPermission) Clone() *IntentionHTTPPermission { } type IntentionHTTPHeaderPermission struct { - Name string - Present bool `json:",omitempty"` - Exact string `json:",omitempty"` - Prefix string `json:",omitempty"` - Suffix string `json:",omitempty"` - Regex string `json:",omitempty"` - Invert bool `json:",omitempty"` + Name string + Present bool `json:",omitempty"` + Exact string `json:",omitempty"` + Prefix string `json:",omitempty"` + Suffix string `json:",omitempty"` + Contains string `json:",omitempty"` + Regex string `json:",omitempty"` + Invert bool `json:",omitempty"` + IgnoreCase bool `json:",omitempty" alias:"ignore_case"` } func cloneStringStringMap(m map[string]string) map[string]string { @@ -880,8 +882,14 @@ func (e *ServiceIntentionsConfigEntry) validate(legacyWrite bool) error { if hdr.Suffix != "" { hdrParts++ } + if hdr.Contains != "" { + hdrParts++ + } if hdrParts != 1 { - return fmt.Errorf(errorPrefix+".Header[%d] should only contain one of Present, Exact, Prefix, Suffix, or Regex", i, j, k) + return fmt.Errorf(errorPrefix+".Header[%d] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex", i, j, k) + } + if hdr.IgnoreCase && (hdr.Present || hdr.Regex != "") { + return fmt.Errorf(errorPrefix+".Header[%d] should set one of Exact, Prefix, Suffix, or Contains when using IgnoreCase", i, j, k) } permParts++ } diff --git a/agent/structs/config_entry_intentions_test.go b/agent/structs/config_entry_intentions_test.go index ea8703de05..9449dc9b77 100644 --- a/agent/structs/config_entry_intentions_test.go +++ b/agent/structs/config_entry_intentions_test.go @@ -574,323 +574,164 @@ func TestServiceIntentionsConfigEntry(t *testing.T) { validateErr: `Sources[0].Permissions[0].HTTP.Header[0] missing required Name field`, }, "permission header has too many parts (1)": { - entry: &ServiceIntentionsConfigEntry{ - Kind: ServiceIntentions, - Name: "test", - Sources: []*SourceIntention{ - { - Name: "foo", - Permissions: []*IntentionPermission{ - { - Action: IntentionActionAllow, - HTTP: &IntentionHTTPPermission{ - Header: []IntentionHTTPHeaderPermission{ - { - Name: "x-abc", - Present: true, - Exact: "foo", - // Regex: "foo", - // Prefix: "foo", - // Suffix: "foo", - }, - }, - }, - }, - }, - }, + entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{ + { + Name: "x-abc", + // Present: true, + Exact: "foo", + // Regex: "foo", + // Prefix: "foo", + Suffix: "foo", + // Contains: "foo", }, - }, - validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`, + }), + validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`, }, "permission header has too many parts (2)": { - entry: &ServiceIntentionsConfigEntry{ - Kind: ServiceIntentions, - Name: "test", - Sources: []*SourceIntention{ - { - Name: "foo", - Permissions: []*IntentionPermission{ - { - Action: IntentionActionAllow, - HTTP: &IntentionHTTPPermission{ - Header: []IntentionHTTPHeaderPermission{ - { - Name: "x-abc", - Present: true, - // Exact: "foo", - Regex: "foo", - // Prefix: "foo", - // Suffix: "foo", - }, - }, - }, - }, - }, - }, + entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{ + { + Name: "x-abc", + // Present: true, + // Exact: "foo", + Regex: "foo", + Prefix: "foo", + // Suffix: "foo", + // Contains: "foo", }, - }, - validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`, + }), + validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`, }, "permission header has too many parts (3)": { - entry: &ServiceIntentionsConfigEntry{ - Kind: ServiceIntentions, - Name: "test", - Sources: []*SourceIntention{ - { - Name: "foo", - Permissions: []*IntentionPermission{ - { - Action: IntentionActionAllow, - HTTP: &IntentionHTTPPermission{ - Header: []IntentionHTTPHeaderPermission{ - { - Name: "x-abc", - Present: true, - // Exact: "foo", - // Regex: "foo", - Prefix: "foo", - // Suffix: "foo", - }, - }, - }, - }, - }, - }, + entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{ + { + Name: "x-abc", + // Present: true, + // Exact: "foo", + // Regex: "foo", + Prefix: "foo", + Suffix: "foo", + // Contains: "foo", }, - }, - validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`, + }), + validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`, }, "permission header has too many parts (4)": { - entry: &ServiceIntentionsConfigEntry{ - Kind: ServiceIntentions, - Name: "test", - Sources: []*SourceIntention{ - { - Name: "foo", - Permissions: []*IntentionPermission{ - { - Action: IntentionActionAllow, - HTTP: &IntentionHTTPPermission{ - Header: []IntentionHTTPHeaderPermission{ - { - Name: "x-abc", - Present: true, - // Exact: "foo", - // Regex: "foo", - // Prefix: "foo", - Suffix: "foo", - }, - }, - }, - }, - }, - }, + entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{ + { + Name: "x-abc", + // Present: true, + Exact: "foo", + // Regex: "foo", + Prefix: "foo", + Suffix: "foo", + // Contains: "foo", }, - }, - validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`, + }), + validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`, }, "permission header has too many parts (5)": { - entry: &ServiceIntentionsConfigEntry{ - Kind: ServiceIntentions, - Name: "test", - Sources: []*SourceIntention{ - { - Name: "foo", - Permissions: []*IntentionPermission{ - { - Action: IntentionActionAllow, - HTTP: &IntentionHTTPPermission{ - Header: []IntentionHTTPHeaderPermission{ - { - Name: "x-abc", - // Present: true, - Exact: "foo", - Regex: "foo", - // Prefix: "foo", - // Suffix: "foo", - }, - }, - }, - }, - }, - }, + entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{ + { + Name: "x-abc", + Present: true, + Exact: "foo", + Regex: "foo", + Prefix: "foo", + Suffix: "foo", + Contains: "foo", }, - }, - validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`, + }), + validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`, }, "permission header has too many parts (6)": { - entry: &ServiceIntentionsConfigEntry{ - Kind: ServiceIntentions, - Name: "test", - Sources: []*SourceIntention{ - { - Name: "foo", - Permissions: []*IntentionPermission{ - { - Action: IntentionActionAllow, - HTTP: &IntentionHTTPPermission{ - Header: []IntentionHTTPHeaderPermission{ - { - Name: "x-abc", - // Present: true, - Exact: "foo", - // Regex: "foo", - Prefix: "foo", - // Suffix: "foo", - }, - }, - }, - }, - }, - }, + entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{ + { + Name: "x-abc", + Present: true, + Exact: "foo", + // Regex: "foo", + Prefix: "foo", + Suffix: "foo", + // Contains: "foo", }, - }, - validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`, + }), + validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`, }, "permission header has too many parts (7)": { - entry: &ServiceIntentionsConfigEntry{ - Kind: ServiceIntentions, - Name: "test", - Sources: []*SourceIntention{ - { - Name: "foo", - Permissions: []*IntentionPermission{ - { - Action: IntentionActionAllow, - HTTP: &IntentionHTTPPermission{ - Header: []IntentionHTTPHeaderPermission{ - { - Name: "x-abc", - // Present: true, - Exact: "foo", - // Regex: "foo", - // Prefix: "foo", - Suffix: "foo", - }, - }, - }, - }, - }, - }, + entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{ + { + Name: "x-abc", + // Present: true, + Exact: "foo", + // Regex: "foo", + Prefix: "foo", + Suffix: "foo", + // Contains: "foo", }, - }, - validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`, + }), + validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`, }, "permission header has too many parts (8)": { - entry: &ServiceIntentionsConfigEntry{ - Kind: ServiceIntentions, - Name: "test", - Sources: []*SourceIntention{ - { - Name: "foo", - Permissions: []*IntentionPermission{ - { - Action: IntentionActionAllow, - HTTP: &IntentionHTTPPermission{ - Header: []IntentionHTTPHeaderPermission{ - { - Name: "x-abc", - // Present: true, - // Exact: "foo", - Regex: "foo", - Prefix: "foo", - // Suffix: "foo", - }, - }, - }, - }, - }, - }, + entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{ + { + Name: "x-abc", + // Present: true, + // Exact: "foo", + Regex: "foo", + Prefix: "foo", + // Suffix: "foo", + // Contains: "foo", }, - }, - validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`, + }), + validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`, }, "permission header has too many parts (9)": { - entry: &ServiceIntentionsConfigEntry{ - Kind: ServiceIntentions, - Name: "test", - Sources: []*SourceIntention{ - { - Name: "foo", - Permissions: []*IntentionPermission{ - { - Action: IntentionActionAllow, - HTTP: &IntentionHTTPPermission{ - Header: []IntentionHTTPHeaderPermission{ - { - Name: "x-abc", - // Present: true, - // Exact: "foo", - Regex: "foo", - // Prefix: "foo", - Suffix: "foo", - }, - }, - }, - }, - }, - }, + entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{ + { + Name: "x-abc", + // Present: true, + // Exact: "foo", + Regex: "foo", + // Prefix: "foo", + Suffix: "foo", + // Contains: "foo", }, - }, - validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`, + }), + validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`, }, "permission header has too many parts (10)": { - entry: &ServiceIntentionsConfigEntry{ - Kind: ServiceIntentions, - Name: "test", - Sources: []*SourceIntention{ - { - Name: "foo", - Permissions: []*IntentionPermission{ - { - Action: IntentionActionAllow, - HTTP: &IntentionHTTPPermission{ - Header: []IntentionHTTPHeaderPermission{ - { - Name: "x-abc", - // Present: true, - // Exact: "foo", - // Regex: "foo", - Prefix: "foo", - Suffix: "foo", - }, - }, - }, - }, - }, - }, + entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{ + { + Name: "x-abc", + // Present: true, + // Exact: "foo", + // Regex: "foo", + Prefix: "foo", + Suffix: "foo", + // Contains: "foo", }, - }, - validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`, + }), + validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, Contains, or Regex`, }, - "permission header has too many parts (11)": { - entry: &ServiceIntentionsConfigEntry{ - Kind: ServiceIntentions, - Name: "test", - Sources: []*SourceIntention{ - { - Name: "foo", - Permissions: []*IntentionPermission{ - { - Action: IntentionActionAllow, - HTTP: &IntentionHTTPPermission{ - Header: []IntentionHTTPHeaderPermission{ - { - Name: "x-abc", - Present: true, - Exact: "foo", - Regex: "foo", - Prefix: "foo", - Suffix: "foo", - }, - }, - }, - }, - }, - }, + "permission header invalid ignore case (1)": { + entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{ + { + Name: "x-abc", + Present: true, + IgnoreCase: true, }, - }, - validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should only contain one of Present, Exact, Prefix, Suffix, or Regex`, + }), + validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should set one of Exact, Prefix, Suffix, or Contains when using IgnoreCase`, + }, + "permission header invalid ignore case (2)": { + entry: httpHeaderPermissionEntry([]IntentionHTTPHeaderPermission{ + { + Name: "x-abc", + Regex: "qux", + IgnoreCase: true, + }, + }), + validateErr: `Sources[0].Permissions[0].HTTP.Header[0] should set one of Exact, Prefix, Suffix, or Contains when using IgnoreCase`, }, "permission invalid method": { entry: &ServiceIntentionsConfigEntry{ @@ -1677,3 +1518,25 @@ func TestMigrateIntentions(t *testing.T) { }) } } + +// httpHeaderPermissionEntry is a helper to generate a ServiceIntentionsConfigEntry for testing +// IntentionHTTPHeaderPermission values. +func httpHeaderPermissionEntry(header []IntentionHTTPHeaderPermission) *ServiceIntentionsConfigEntry { + return &ServiceIntentionsConfigEntry{ + Kind: ServiceIntentions, + Name: "test", + Sources: []*SourceIntention{ + { + Name: "foo", + Permissions: []*IntentionPermission{ + { + Action: IntentionActionAllow, + HTTP: &IntentionHTTPPermission{ + Header: header, + }, + }, + }, + }, + }, + } +} diff --git a/agent/structs/config_entry_mesh.go b/agent/structs/config_entry_mesh.go index c16cbf4243..0fa36d0854 100644 --- a/agent/structs/config_entry_mesh.go +++ b/agent/structs/config_entry_mesh.go @@ -6,6 +6,7 @@ package structs import ( "encoding/json" "fmt" + "strings" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/types" @@ -20,6 +21,14 @@ type MeshConfigEntry struct { // MutualTLSMode=permissive in either service-defaults or proxy-defaults. AllowEnablingPermissiveMutualTLS bool `json:",omitempty" alias:"allow_enabling_permissive_mutual_tls"` + // ValidateClusters controls whether the clusters the route table refers to are validated. The default value is + // false. When set to false and a route refers to a cluster that does not exist, the route table loads and routing + // to a non-existent cluster results in a 404. When set to true and the route is set to a cluster that do not exist, + // the route table will not load. For more information, refer to + // [HTTP route configuration in the Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route.proto#envoy-v3-api-field-config-route-v3-routeconfiguration-validate-clusters) + // for more details. + ValidateClusters bool `json:",omitempty" alias:"validate_clusters"` + TLS *MeshTLSConfig `json:",omitempty"` HTTP *MeshHTTPConfig `json:",omitempty"` @@ -64,6 +73,17 @@ type MeshDirectionalTLSConfig struct { type MeshHTTPConfig struct { SanitizeXForwardedClientCert bool `alias:"sanitize_x_forwarded_client_cert"` + // Incoming configures settings for incoming HTTP traffic to mesh proxies. + Incoming *MeshDirectionalHTTPConfig `json:",omitempty"` + // There is not currently an outgoing MeshDirectionalHTTPConfig, as + // the only required config for either direction at present is inbound + // request normalization. +} + +// MeshDirectionalHTTPConfig holds mesh configuration specific to HTTP +// requests for a given traffic direction. +type MeshDirectionalHTTPConfig struct { + RequestNormalization *RequestNormalizationMeshConfig `json:",omitempty" alias:"request_normalization"` } // PeeringMeshConfig contains cluster-wide options pertaining to peering. @@ -76,6 +96,104 @@ type PeeringMeshConfig struct { PeerThroughMeshGateways bool `alias:"peer_through_mesh_gateways"` } +// RequestNormalizationMeshConfig contains options pertaining to the +// normalization of HTTP requests processed by mesh proxies. +type RequestNormalizationMeshConfig struct { + // InsecureDisablePathNormalization sets the value of the \`normalize_path\` option in the Envoy listener's + // `HttpConnectionManager`. The default value is \`false\`. When set to \`true\` in Consul, \`normalize_path\` is + // set to \`false\` for the Envoy proxy. This parameter disables the normalization of request URL paths according to + // RFC 3986, conversion of \`\\\` to \`/\`, and decoding non-reserved %-encoded characters. When using L7 intentions + // with path match rules, we recommend enabling path normalization in order to avoid match rule circumvention with + // non-normalized path values. + InsecureDisablePathNormalization bool `alias:"insecure_disable_path_normalization"` + // MergeSlashes sets the value of the \`merge_slashes\` option in the Envoy listener's \`HttpConnectionManager\`. + // The default value is \`false\`. This option controls the normalization of request URL paths by merging + // consecutive \`/\` characters. This normalization is not part of RFC 3986. When using L7 intentions with path + // match rules, we recommend enabling this setting to avoid match rule circumvention through non-normalized path + // values, unless legitimate service traffic depends on allowing for repeat \`/\` characters, or upstream services + // are configured to differentiate between single and multiple slashes. + MergeSlashes bool `alias:"merge_slashes"` + // PathWithEscapedSlashesAction sets the value of the \`path_with_escaped_slashes_action\` option in the Envoy + // listener's \`HttpConnectionManager\`. The default value of this option is empty, which is equivalent to + // \`IMPLEMENTATION_SPECIFIC_DEFAULT\`. This parameter controls the action taken in response to request URL paths + // with escaped slashes in the path. When using L7 intentions with path match rules, we recommend enabling this + // setting to avoid match rule circumvention through non-normalized path values, unless legitimate service traffic + // depends on allowing for escaped \`/\` or \`\\\` characters, or upstream services are configured to differentiate + // between escaped and unescaped slashes. Refer to the Envoy documentation for more information on available + // options. + PathWithEscapedSlashesAction PathWithEscapedSlashesAction `alias:"path_with_escaped_slashes_action"` + // HeadersWithUnderscoresAction sets the value of the \`headers_with_underscores_action\` option in the Envoy + // listener's \`HttpConnectionManager\` under \`common_http_protocol_options\`. The default value of this option is + // empty, which is equivalent to \`ALLOW\`. Refer to the Envoy documentation for more information on available + // options. + HeadersWithUnderscoresAction HeadersWithUnderscoresAction `alias:"headers_with_underscores_action"` +} + +// PathWithEscapedSlashesAction is an enum that defines the action to take when +// a request path contains escaped slashes. It mirrors exactly the set of options +// in Envoy's UriPathNormalizationOptions.PathWithEscapedSlashesAction enum. +type PathWithEscapedSlashesAction string + +// See github.com/envoyproxy/go-control-plane envoy_http_v3.HttpConnectionManager_PathWithEscapedSlashesAction. +const ( + PathWithEscapedSlashesActionDefault PathWithEscapedSlashesAction = "IMPLEMENTATION_SPECIFIC_DEFAULT" + PathWithEscapedSlashesActionKeep PathWithEscapedSlashesAction = "KEEP_UNCHANGED" + PathWithEscapedSlashesActionReject PathWithEscapedSlashesAction = "REJECT_REQUEST" + PathWithEscapedSlashesActionUnescapeAndRedirect PathWithEscapedSlashesAction = "UNESCAPE_AND_REDIRECT" + PathWithEscapedSlashesActionUnescapeAndForward PathWithEscapedSlashesAction = "UNESCAPE_AND_FORWARD" +) + +// PathWithEscapedSlashesActionStrings returns an ordered slice of all PathWithEscapedSlashesAction values as strings. +func PathWithEscapedSlashesActionStrings() []string { + return []string{ + string(PathWithEscapedSlashesActionDefault), + string(PathWithEscapedSlashesActionKeep), + string(PathWithEscapedSlashesActionReject), + string(PathWithEscapedSlashesActionUnescapeAndRedirect), + string(PathWithEscapedSlashesActionUnescapeAndForward), + } +} + +// pathWithEscapedSlashesActions contains the canonical set of PathWithEscapedSlashesActionValues values. +var pathWithEscapedSlashesActions = (func() map[PathWithEscapedSlashesAction]struct{} { + m := make(map[PathWithEscapedSlashesAction]struct{}) + for _, v := range PathWithEscapedSlashesActionStrings() { + m[PathWithEscapedSlashesAction(v)] = struct{}{} + } + return m +})() + +// HeadersWithUnderscoresAction is an enum that defines the action to take when +// a request contains headers with underscores. It mirrors exactly the set of +// options in Envoy's HttpProtocolOptions.HeadersWithUnderscoresAction enum. +type HeadersWithUnderscoresAction string + +// See github.com/envoyproxy/go-control-plane envoy_core_v3.HttpProtocolOptions_HeadersWithUnderscoresAction. +const ( + HeadersWithUnderscoresActionAllow HeadersWithUnderscoresAction = "ALLOW" + HeadersWithUnderscoresActionRejectRequest HeadersWithUnderscoresAction = "REJECT_REQUEST" + HeadersWithUnderscoresActionDropHeader HeadersWithUnderscoresAction = "DROP_HEADER" +) + +// HeadersWithUnderscoresActionStrings returns an ordered slice of all HeadersWithUnderscoresAction values as strings +// for use in returning validation errors. +func HeadersWithUnderscoresActionStrings() []string { + return []string{ + string(HeadersWithUnderscoresActionAllow), + string(HeadersWithUnderscoresActionRejectRequest), + string(HeadersWithUnderscoresActionDropHeader), + } +} + +// headersWithUnderscoresActions contains the canonical set of HeadersWithUnderscoresAction values. +var headersWithUnderscoresActions = (func() map[HeadersWithUnderscoresAction]struct{} { + m := make(map[HeadersWithUnderscoresAction]struct{}) + for _, v := range HeadersWithUnderscoresActionStrings() { + m[HeadersWithUnderscoresAction(v)] = struct{}{} + } + return m +})() + func (e *MeshConfigEntry) GetKind() string { return MeshConfig } @@ -133,6 +251,10 @@ func (e *MeshConfigEntry) Validate() error { } } + if err := validateRequestNormalizationMeshConfig(e.GetHTTPIncomingRequestNormalization()); err != nil { + return fmt.Errorf("error in HTTP incoming request normalization configuration: %v", err) + } + return e.validateEnterpriseMeta() } @@ -185,6 +307,61 @@ func (e *MeshConfigEntry) PeerThroughMeshGateways() bool { return e.Peering.PeerThroughMeshGateways } +func (e *MeshConfigEntry) GetHTTP() *MeshHTTPConfig { + if e == nil { + return nil + } + return e.HTTP +} + +func (e *MeshHTTPConfig) GetIncoming() *MeshDirectionalHTTPConfig { + if e == nil { + return nil + } + return e.Incoming +} + +func (e *MeshDirectionalHTTPConfig) GetRequestNormalization() *RequestNormalizationMeshConfig { + if e == nil { + return nil + } + return e.RequestNormalization +} + +// GetHTTPIncomingRequestNormalization is a convenience accessor for mesh.http.incoming.request_normalization +// since no other fields currently exist under mesh.http.incoming. +func (e *MeshConfigEntry) GetHTTPIncomingRequestNormalization() *RequestNormalizationMeshConfig { + return e.GetHTTP().GetIncoming().GetRequestNormalization() +} + +func (r *RequestNormalizationMeshConfig) GetInsecureDisablePathNormalization() bool { + if r == nil { + return false + } + return r.InsecureDisablePathNormalization +} + +func (r *RequestNormalizationMeshConfig) GetMergeSlashes() bool { + if r == nil { + return false + } + return r.MergeSlashes +} + +func (r *RequestNormalizationMeshConfig) GetPathWithEscapedSlashesAction() PathWithEscapedSlashesAction { + if r == nil || r.PathWithEscapedSlashesAction == "" { + return PathWithEscapedSlashesActionDefault + } + return r.PathWithEscapedSlashesAction +} + +func (r *RequestNormalizationMeshConfig) GetHeadersWithUnderscoresAction() HeadersWithUnderscoresAction { + if r == nil || r.HeadersWithUnderscoresAction == "" { + return HeadersWithUnderscoresActionAllow + } + return r.HeadersWithUnderscoresAction +} + func validateMeshDirectionalTLSConfig(cfg *MeshDirectionalTLSConfig) error { if cfg == nil { return nil @@ -229,3 +406,36 @@ func validateTLSConfig( return nil } + +func validateRequestNormalizationMeshConfig(cfg *RequestNormalizationMeshConfig) error { + if cfg == nil { + return nil + } + if err := validatePathWithEscapedSlashesAction(cfg.PathWithEscapedSlashesAction); err != nil { + return err + } + if err := validateHeadersWithUnderscoresAction(cfg.HeadersWithUnderscoresAction); err != nil { + return err + } + return nil +} + +func validatePathWithEscapedSlashesAction(v PathWithEscapedSlashesAction) error { + if v == "" { + return nil + } + if _, ok := pathWithEscapedSlashesActions[v]; !ok { + return fmt.Errorf("no matching PathWithEscapedSlashesAction value found for %s, please specify one of [%s]", string(v), strings.Join(PathWithEscapedSlashesActionStrings(), ", ")) + } + return nil +} + +func validateHeadersWithUnderscoresAction(v HeadersWithUnderscoresAction) error { + if v == "" { + return nil + } + if _, ok := headersWithUnderscoresActions[v]; !ok { + return fmt.Errorf("no matching HeadersWithUnderscoresAction value found for %s, please specify one of [%s]", string(v), strings.Join(HeadersWithUnderscoresActionStrings(), ", ")) + } + return nil +} diff --git a/agent/structs/config_entry_mesh_test.go b/agent/structs/config_entry_mesh_test.go index f6eaea9e9c..a4afbc6c9f 100644 --- a/agent/structs/config_entry_mesh_test.go +++ b/agent/structs/config_entry_mesh_test.go @@ -47,3 +47,164 @@ func TestMeshConfigEntry_PeerThroughMeshGateways(t *testing.T) { }) } } + +func TestMeshConfigEntry_GetHTTPIncomingRequestNormalization(t *testing.T) { + tests := map[string]struct { + input *MeshConfigEntry + want *RequestNormalizationMeshConfig + }{ + // Ensure nil is gracefully handled at each level of config path. + "nil entry": { + input: nil, + want: nil, + }, + "nil http config": { + input: &MeshConfigEntry{ + HTTP: nil, + }, + want: nil, + }, + "nil http incoming config": { + input: &MeshConfigEntry{ + HTTP: &MeshHTTPConfig{ + Incoming: nil, + }, + }, + want: nil, + }, + "nil http incoming request normalization config": { + input: &MeshConfigEntry{ + HTTP: &MeshHTTPConfig{ + Incoming: &MeshDirectionalHTTPConfig{ + RequestNormalization: nil, + }, + }, + }, + want: nil, + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.want, tc.input.GetHTTPIncomingRequestNormalization()) + }) + } +} + +func TestMeshConfigEntry_RequestNormalizationMeshConfig(t *testing.T) { + tests := map[string]struct { + input *RequestNormalizationMeshConfig + getFn func(*RequestNormalizationMeshConfig) any + want any + }{ + // Ensure defaults are returned when config is not set. + "nil entry gets false GetInsecureDisablePathNormalization": { + input: nil, + getFn: func(c *RequestNormalizationMeshConfig) any { + return c.GetInsecureDisablePathNormalization() + }, + want: false, + }, + "nil entry gets false GetMergeSlashes": { + input: nil, + getFn: func(c *RequestNormalizationMeshConfig) any { + return c.GetMergeSlashes() + }, + want: false, + }, + "nil entry gets default GetPathWithEscapedSlashesAction": { + input: nil, + getFn: func(c *RequestNormalizationMeshConfig) any { + return c.GetPathWithEscapedSlashesAction() + }, + want: PathWithEscapedSlashesAction("IMPLEMENTATION_SPECIFIC_DEFAULT"), + }, + "nil entry gets default GetHeadersWithUnderscoresAction": { + input: nil, + getFn: func(c *RequestNormalizationMeshConfig) any { + return c.GetHeadersWithUnderscoresAction() + }, + want: HeadersWithUnderscoresAction("ALLOW"), + }, + "empty entry gets default GetPathWithEscapedSlashesAction": { + input: &RequestNormalizationMeshConfig{}, + getFn: func(c *RequestNormalizationMeshConfig) any { + return c.GetPathWithEscapedSlashesAction() + }, + want: PathWithEscapedSlashesAction("IMPLEMENTATION_SPECIFIC_DEFAULT"), + }, + "empty entry gets default GetHeadersWithUnderscoresAction": { + input: &RequestNormalizationMeshConfig{}, + getFn: func(c *RequestNormalizationMeshConfig) any { + return c.GetHeadersWithUnderscoresAction() + }, + want: HeadersWithUnderscoresAction("ALLOW"), + }, + // Ensure values are returned when set. + "non-default entry gets expected InsecureDisablePathNormalization": { + input: &RequestNormalizationMeshConfig{InsecureDisablePathNormalization: true}, + getFn: func(c *RequestNormalizationMeshConfig) any { + return c.GetInsecureDisablePathNormalization() + }, + want: true, + }, + "non-default entry gets expected MergeSlashes": { + input: &RequestNormalizationMeshConfig{MergeSlashes: true}, + getFn: func(c *RequestNormalizationMeshConfig) any { + return c.GetMergeSlashes() + }, + want: true, + }, + "non-default entry gets expected PathWithEscapedSlashesAction": { + input: &RequestNormalizationMeshConfig{PathWithEscapedSlashesAction: "UNESCAPE_AND_FORWARD"}, + getFn: func(c *RequestNormalizationMeshConfig) any { + return c.GetPathWithEscapedSlashesAction() + }, + want: PathWithEscapedSlashesAction("UNESCAPE_AND_FORWARD"), + }, + "non-default entry gets expected HeadersWithUnderscoresAction": { + input: &RequestNormalizationMeshConfig{HeadersWithUnderscoresAction: "REJECT_REQUEST"}, + getFn: func(c *RequestNormalizationMeshConfig) any { + return c.GetHeadersWithUnderscoresAction() + }, + want: HeadersWithUnderscoresAction("REJECT_REQUEST"), + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + assert.Equal(t, tc.want, tc.getFn(tc.input)) + }) + } +} + +func TestMeshConfigEntry_validateRequestNormalizationMeshConfig(t *testing.T) { + tests := map[string]struct { + input *RequestNormalizationMeshConfig + wantErr string + }{ + "nil entry is valid": { + input: nil, + wantErr: "", + }, + "invalid PathWithEscapedSlashesAction is rejected": { + input: &RequestNormalizationMeshConfig{ + PathWithEscapedSlashesAction: PathWithEscapedSlashesAction("INVALID"), + }, + wantErr: "no matching PathWithEscapedSlashesAction value found for INVALID, please specify one of [IMPLEMENTATION_SPECIFIC_DEFAULT, KEEP_UNCHANGED, REJECT_REQUEST, UNESCAPE_AND_REDIRECT, UNESCAPE_AND_FORWARD]", + }, + "invalid HeadersWithUnderscoresAction is rejected": { + input: &RequestNormalizationMeshConfig{ + HeadersWithUnderscoresAction: HeadersWithUnderscoresAction("INVALID"), + }, + wantErr: "no matching HeadersWithUnderscoresAction value found for INVALID, please specify one of [ALLOW, REJECT_REQUEST, DROP_HEADER]", + }, + } + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + if tc.wantErr == "" { + assert.NoError(t, validateRequestNormalizationMeshConfig(tc.input)) + } else { + assert.EqualError(t, validateRequestNormalizationMeshConfig(tc.input), tc.wantErr) + } + }) + } +} diff --git a/agent/structs/config_entry_test.go b/agent/structs/config_entry_test.go index e57e2c4041..978e808be4 100644 --- a/agent/structs/config_entry_test.go +++ b/agent/structs/config_entry_test.go @@ -10,12 +10,12 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/hcl" "github.com/mitchellh/copystructure" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/hashicorp/consul-net-rpc/go-msgpack/codec" + "github.com/hashicorp/hcl" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/cache" @@ -1910,6 +1910,10 @@ func TestDecodeConfigEntry(t *testing.T) { name = "hdr-suffix" suffix = "suffix" }, + { + name = "hdr-contains" + contains = "contains" + }, { name = "hdr-regex" regex = "regex" @@ -1918,7 +1922,12 @@ func TestDecodeConfigEntry(t *testing.T) { name = "hdr-absent" present = true invert = true - } + }, + { + name = "hdr-ignore-case" + exact = "exact" + ignore_case = true + }, ] } }, @@ -1987,6 +1996,10 @@ func TestDecodeConfigEntry(t *testing.T) { Name = "hdr-suffix" Suffix = "suffix" }, + { + Name = "hdr-contains" + Contains = "contains" + }, { Name = "hdr-regex" Regex = "regex" @@ -1995,6 +2008,11 @@ func TestDecodeConfigEntry(t *testing.T) { Name = "hdr-absent" Present = true Invert = true + }, + { + Name = "hdr-ignore-case" + Exact = "exact" + IgnoreCase = true } ] } @@ -2064,6 +2082,10 @@ func TestDecodeConfigEntry(t *testing.T) { Name: "hdr-suffix", Suffix: "suffix", }, + { + Name: "hdr-contains", + Contains: "contains", + }, { Name: "hdr-regex", Regex: "regex", @@ -2073,6 +2095,11 @@ func TestDecodeConfigEntry(t *testing.T) { Present: true, Invert: true, }, + { + Name: "hdr-ignore-case", + Exact: "exact", + IgnoreCase: true, + }, }, }, }, @@ -2134,7 +2161,7 @@ func TestDecodeConfigEntry(t *testing.T) { }, }, { - name: "mesh", + name: "mesh: kitchen sink", snake: ` kind = "mesh" meta { @@ -2145,6 +2172,7 @@ func TestDecodeConfigEntry(t *testing.T) { mesh_destinations_only = true } allow_enabling_permissive_mutual_tls = true + validate_clusters = true tls { incoming { tls_min_version = "TLSv1_1" @@ -2163,9 +2191,17 @@ func TestDecodeConfigEntry(t *testing.T) { ] } } - http { - sanitize_x_forwarded_client_cert = true - } + http { + sanitize_x_forwarded_client_cert = true + incoming { + request_normalization { + insecure_disable_path_normalization = true + merge_slashes = true + path_with_escaped_slashes_action = "UNESCAPE_AND_FORWARD" + headers_with_underscores_action = "DROP_HEADER" + } + } + } peering { peer_through_mesh_gateways = true } @@ -2180,6 +2216,7 @@ func TestDecodeConfigEntry(t *testing.T) { MeshDestinationsOnly = true } AllowEnablingPermissiveMutualTLS = true + ValidateClusters = true TLS { Incoming { TLSMinVersion = "TLSv1_1" @@ -2198,9 +2235,17 @@ func TestDecodeConfigEntry(t *testing.T) { ] } } - HTTP { - SanitizeXForwardedClientCert = true - } + HTTP { + SanitizeXForwardedClientCert = true + Incoming { + RequestNormalization { + InsecureDisablePathNormalization = true + MergeSlashes = true + PathWithEscapedSlashesAction = "UNESCAPE_AND_FORWARD" + HeadersWithUnderscoresAction = "DROP_HEADER" + } + } + } Peering { PeerThroughMeshGateways = true } @@ -2214,6 +2259,7 @@ func TestDecodeConfigEntry(t *testing.T) { MeshDestinationsOnly: true, }, AllowEnablingPermissiveMutualTLS: true, + ValidateClusters: true, TLS: &MeshTLSConfig{ Incoming: &MeshDirectionalTLSConfig{ TLSMinVersion: types.TLSv1_1, @@ -2234,6 +2280,14 @@ func TestDecodeConfigEntry(t *testing.T) { }, HTTP: &MeshHTTPConfig{ SanitizeXForwardedClientCert: true, + Incoming: &MeshDirectionalHTTPConfig{ + RequestNormalization: &RequestNormalizationMeshConfig{ + InsecureDisablePathNormalization: true, // note: this is the opposite of the recommended default + MergeSlashes: true, + PathWithEscapedSlashesAction: "UNESCAPE_AND_FORWARD", + HeadersWithUnderscoresAction: "DROP_HEADER", + }, + }, }, Peering: &PeeringMeshConfig{ PeerThroughMeshGateways: true, @@ -3225,6 +3279,14 @@ func TestServiceConfigEntry(t *testing.T) { }, validateErr: `Invalid MutualTLSMode "invalid-mtls-mode". Must be one of "", "strict", or "permissive".`, }, + "validate: invalid Protocol in service-defaults": { + entry: &ServiceConfigEntry{ + Kind: ServiceDefaults, + Name: "web", + Protocol: "blah", + }, + validateErr: `invalid value for protocol: blah`, + }, } testConfigEntryNormalizeAndValidate(t, cases) } diff --git a/agent/structs/connect_ca.go b/agent/structs/connect_ca.go index 267aeba5e6..5fa7b07715 100644 --- a/agent/structs/connect_ca.go +++ b/agent/structs/connect_ca.go @@ -8,12 +8,11 @@ import ( "reflect" "time" - "github.com/hashicorp/consul/lib/stringslice" - "github.com/mitchellh/mapstructure" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/lib" + "github.com/hashicorp/consul/lib/stringslice" ) const ( @@ -217,11 +216,6 @@ type IssuedCert struct { // PrivateKeyPEM is the PEM encoded private key associated with CertPEM. PrivateKeyPEM string `json:",omitempty"` - // WorkloadIdentity is the name of the workload identity for which the cert was issued. - WorkloadIdentity string `json:",omitempty"` - // WorkloadIdentityURI is the cert URI value. - WorkloadIdentityURI string `json:",omitempty"` - // Service is the name of the service for which the cert was issued. Service string `json:",omitempty"` // ServiceURI is the cert URI value. diff --git a/agent/structs/connect_proxy_config.go b/agent/structs/connect_proxy_config.go index d84953e1b0..3bd5276f82 100644 --- a/agent/structs/connect_proxy_config.go +++ b/agent/structs/connect_proxy_config.go @@ -11,7 +11,6 @@ import ( "github.com/hashicorp/consul/api" "github.com/hashicorp/consul/lib" - pbmesh "github.com/hashicorp/consul/proto-public/pbmesh/v2beta1" ) const ( @@ -181,39 +180,6 @@ type AccessLogsConfig struct { TextFormat string `json:",omitempty" alias:"text_format"` } -func (c *AccessLogsConfig) GetEnabled() bool { - return c.Enabled -} - -func (c *AccessLogsConfig) GetDisableListenerLogs() bool { - return c.DisableListenerLogs -} - -func (c *AccessLogsConfig) GetType() pbmesh.LogSinkType { - switch c.Type { - case FileLogSinkType: - return pbmesh.LogSinkType_LOG_SINK_TYPE_FILE - case StdErrLogSinkType: - return pbmesh.LogSinkType_LOG_SINK_TYPE_STDERR - case StdOutLogSinkType: - return pbmesh.LogSinkType_LOG_SINK_TYPE_STDOUT - } - - return pbmesh.LogSinkType_LOG_SINK_TYPE_DEFAULT -} - -func (c *AccessLogsConfig) GetPath() string { - return c.Path -} - -func (c *AccessLogsConfig) GetJsonFormat() string { - return c.JSONFormat -} - -func (c *AccessLogsConfig) GetTextFormat() string { - return c.TextFormat -} - func (c *AccessLogsConfig) IsZero() bool { if c == nil { return true @@ -839,12 +805,3 @@ func (e *ExposeConfig) Finalize() { } } } - -type AccessLogs interface { - GetEnabled() bool - GetDisableListenerLogs() bool - GetType() pbmesh.LogSinkType - GetPath() string - GetJsonFormat() string - GetTextFormat() string -} diff --git a/agent/structs/deep-copy.sh b/agent/structs/deep-copy.sh index 1bc4ededd6..242d4e5ab2 100755 --- a/agent/structs/deep-copy.sh +++ b/agent/structs/deep-copy.sh @@ -52,6 +52,7 @@ deep-copy \ -type ServiceRoute \ -type ServiceRouteDestination \ -type ServiceRouteMatch \ + -type ServiceSpecificRequest \ -type TCPRouteConfigEntry \ -type Upstream \ -type UpstreamConfiguration \ diff --git a/agent/structs/errors.go b/agent/structs/errors.go index 31a818bd62..029f958ec4 100644 --- a/agent/structs/errors.go +++ b/agent/structs/errors.go @@ -9,38 +9,40 @@ import ( ) const ( - errNoLeader = "No cluster leader" - errNoDCPath = "No path to datacenter" - errDCNotAvailable = "Remote DC has no server currently reachable" - errNoServers = "No known Consul servers" - errNotReadyForConsistentReads = "Not ready to serve consistent reads" - errSegmentsNotSupported = "Network segments are not supported in this version of Consul" - errRPCRateExceeded = "RPC rate limit exceeded" - errServiceNotFound = "Service not found: " - errQueryNotFound = "Query not found" - errLeaderNotTracked = "Raft leader not found in server lookup mapping" - errConnectNotEnabled = "Connect must be enabled in order to use this endpoint" - errRateLimited = "Rate limit reached, try again later" // Note: we depend on this error message in the gRPC ConnectCA.Sign endpoint (see: isRateLimitError). - errNotPrimaryDatacenter = "not the primary datacenter" - errStateReadOnly = "CA Provider State is read-only" - errUsingV2CatalogExperiment = "V1 catalog is disabled when V2 is enabled" + errNoLeader = "No cluster leader" + errNoDCPath = "No path to datacenter" + errDCNotAvailable = "Remote DC has no server currently reachable" + errNoServers = "No known Consul servers" + errNotReadyForConsistentReads = "Not ready to serve consistent reads" + errSegmentsNotSupported = "Network segments are not supported in this version of Consul" + errRPCRateExceeded = "RPC rate limit exceeded" + errServiceNotFound = "Service not found: " + errQueryNotFound = "Query not found" + errLeaderNotTracked = "Raft leader not found in server lookup mapping" + errConnectNotEnabled = "Connect must be enabled in order to use this endpoint" + errRateLimited = "Rate limit reached, try again later" // Note: we depend on this error message in the gRPC ConnectCA.Sign endpoint (see: isRateLimitError). + errNotPrimaryDatacenter = "not the primary datacenter" + errStateReadOnly = "CA Provider State is read-only" + errSamenessGroupNotFound = "Sameness Group not found" + errSamenessGroupMustBeDefaultForFailover = "Sameness Group must have DefaultForFailover set to true in order to use this endpoint" ) var ( - ErrNoLeader = errors.New(errNoLeader) - ErrNoDCPath = errors.New(errNoDCPath) - ErrNoServers = errors.New(errNoServers) - ErrNotReadyForConsistentReads = errors.New(errNotReadyForConsistentReads) - ErrSegmentsNotSupported = errors.New(errSegmentsNotSupported) - ErrRPCRateExceeded = errors.New(errRPCRateExceeded) - ErrDCNotAvailable = errors.New(errDCNotAvailable) - ErrQueryNotFound = errors.New(errQueryNotFound) - ErrLeaderNotTracked = errors.New(errLeaderNotTracked) - ErrConnectNotEnabled = errors.New(errConnectNotEnabled) - ErrRateLimited = errors.New(errRateLimited) // Note: we depend on this error message in the gRPC ConnectCA.Sign endpoint (see: isRateLimitError). - ErrNotPrimaryDatacenter = errors.New(errNotPrimaryDatacenter) - ErrStateReadOnly = errors.New(errStateReadOnly) - ErrUsingV2CatalogExperiment = errors.New(errUsingV2CatalogExperiment) + ErrNoLeader = errors.New(errNoLeader) + ErrNoDCPath = errors.New(errNoDCPath) + ErrNoServers = errors.New(errNoServers) + ErrNotReadyForConsistentReads = errors.New(errNotReadyForConsistentReads) + ErrSegmentsNotSupported = errors.New(errSegmentsNotSupported) + ErrRPCRateExceeded = errors.New(errRPCRateExceeded) + ErrDCNotAvailable = errors.New(errDCNotAvailable) + ErrQueryNotFound = errors.New(errQueryNotFound) + ErrLeaderNotTracked = errors.New(errLeaderNotTracked) + ErrConnectNotEnabled = errors.New(errConnectNotEnabled) + ErrRateLimited = errors.New(errRateLimited) // Note: we depend on this error message in the gRPC ConnectCA.Sign endpoint (see: isRateLimitError). + ErrNotPrimaryDatacenter = errors.New(errNotPrimaryDatacenter) + ErrStateReadOnly = errors.New(errStateReadOnly) + ErrSamenessGroupNotFound = errors.New(errSamenessGroupNotFound) + ErrSamenessGroupMustBeDefaultForFailover = errors.New(errSamenessGroupMustBeDefaultForFailover) ) func IsErrNoDCPath(err error) bool { @@ -59,10 +61,10 @@ func IsErrRPCRateExceeded(err error) bool { return err != nil && strings.Contains(err.Error(), errRPCRateExceeded) } -func IsErrServiceNotFound(err error) bool { - return err != nil && strings.Contains(err.Error(), errServiceNotFound) +func IsErrSamenessGroupNotFound(err error) bool { + return err != nil && strings.Contains(err.Error(), errSamenessGroupNotFound) } -func IsErrUsingV2CatalogExperiment(err error) bool { - return err != nil && strings.Contains(err.Error(), errUsingV2CatalogExperiment) +func IsErrSamenessGroupMustBeDefaultForFailover(err error) bool { + return err != nil && strings.Contains(err.Error(), errSamenessGroupMustBeDefaultForFailover) } diff --git a/agent/structs/structs.deepcopy.go b/agent/structs/structs.deepcopy.go index 9c9a7c8bc9..893514538a 100644 --- a/agent/structs/structs.deepcopy.go +++ b/agent/structs/structs.deepcopy.go @@ -1,4 +1,4 @@ -// generated by deep-copy -pointer-receiver -o ./structs.deepcopy.go -type APIGatewayListener -type BoundAPIGatewayListener -type CARoot -type CheckServiceNode -type CheckType -type CompiledDiscoveryChain -type ConnectProxyConfig -type DiscoveryFailover -type DiscoveryGraphNode -type DiscoveryResolver -type DiscoveryRoute -type DiscoverySplit -type ExposeConfig -type ExportedServicesConfigEntry -type FileSystemCertificateConfigEntry -type GatewayService -type GatewayServiceTLSConfig -type HTTPHeaderModifiers -type HTTPRouteConfigEntry -type HashPolicy -type HealthCheck -type IndexedCARoots -type IngressListener -type InlineCertificateConfigEntry -type Intention -type IntentionPermission -type LoadBalancer -type MeshConfigEntry -type MeshDirectionalTLSConfig -type MeshTLSConfig -type Node -type NodeService -type PeeringServiceMeta -type ServiceConfigEntry -type ServiceConfigResponse -type ServiceConnect -type ServiceDefinition -type ServiceResolverConfigEntry -type ServiceResolverFailover -type ServiceRoute -type ServiceRouteDestination -type ServiceRouteMatch -type TCPRouteConfigEntry -type Upstream -type UpstreamConfiguration -type Status -type BoundAPIGatewayConfigEntry ./; DO NOT EDIT. +// Code generated by deep-copy -pointer-receiver -o ./structs.deepcopy.go -type APIGatewayListener -type BoundAPIGatewayListener -type CARoot -type CheckServiceNode -type CheckType -type CompiledDiscoveryChain -type ConnectProxyConfig -type DiscoveryFailover -type DiscoveryGraphNode -type DiscoveryResolver -type DiscoveryRoute -type DiscoverySplit -type ExposeConfig -type ExportedServicesConfigEntry -type FileSystemCertificateConfigEntry -type GatewayService -type GatewayServiceTLSConfig -type HTTPHeaderModifiers -type HTTPRouteConfigEntry -type HashPolicy -type HealthCheck -type IndexedCARoots -type IngressListener -type InlineCertificateConfigEntry -type Intention -type IntentionPermission -type LoadBalancer -type MeshConfigEntry -type MeshDirectionalTLSConfig -type MeshTLSConfig -type Node -type NodeService -type PeeringServiceMeta -type ServiceConfigEntry -type ServiceConfigResponse -type ServiceConnect -type ServiceDefinition -type ServiceResolverConfigEntry -type ServiceResolverFailover -type ServiceRoute -type ServiceRouteDestination -type ServiceRouteMatch -type ServiceSpecificRequest -type TCPRouteConfigEntry -type Upstream -type UpstreamConfiguration -type Status -type BoundAPIGatewayConfigEntry ./; DO NOT EDIT. package structs @@ -809,6 +809,14 @@ func (o *MeshConfigEntry) DeepCopy() *MeshConfigEntry { if o.HTTP != nil { cp.HTTP = new(MeshHTTPConfig) *cp.HTTP = *o.HTTP + if o.HTTP.Incoming != nil { + cp.HTTP.Incoming = new(MeshDirectionalHTTPConfig) + *cp.HTTP.Incoming = *o.HTTP.Incoming + if o.HTTP.Incoming.RequestNormalization != nil { + cp.HTTP.Incoming.RequestNormalization = new(RequestNormalizationMeshConfig) + *cp.HTTP.Incoming.RequestNormalization = *o.HTTP.Incoming.RequestNormalization + } + } } if o.Peering != nil { cp.Peering = new(PeeringMeshConfig) @@ -1197,6 +1205,22 @@ func (o *ServiceRouteMatch) DeepCopy() *ServiceRouteMatch { return &cp } +// DeepCopy generates a deep copy of *ServiceSpecificRequest +func (o *ServiceSpecificRequest) DeepCopy() *ServiceSpecificRequest { + var cp ServiceSpecificRequest = *o + if o.NodeMetaFilters != nil { + cp.NodeMetaFilters = make(map[string]string, len(o.NodeMetaFilters)) + for k2, v2 := range o.NodeMetaFilters { + cp.NodeMetaFilters[k2] = v2 + } + } + if o.ServiceTags != nil { + cp.ServiceTags = make([]string, len(o.ServiceTags)) + copy(cp.ServiceTags, o.ServiceTags) + } + return &cp +} + // DeepCopy generates a deep copy of *TCPRouteConfigEntry func (o *TCPRouteConfigEntry) DeepCopy() *TCPRouteConfigEntry { var cp TCPRouteConfigEntry = *o diff --git a/agent/structs/structs.go b/agent/structs/structs.go index 0ac72b07fe..cae14af52b 100644 --- a/agent/structs/structs.go +++ b/agent/structs/structs.go @@ -13,6 +13,7 @@ import ( "os" "reflect" "regexp" + "slices" "sort" "strconv" "strings" @@ -752,16 +753,20 @@ type ServiceSpecificRequest struct { // The name of the peer that the requested service was imported from. PeerName string + // The name of the sameness group that should be the target of the query. + SamenessGroup string + NodeMetaFilters map[string]string ServiceName string ServiceKind ServiceKind // DEPRECATED (singular-service-tag) - remove this when backwards RPC compat // with 1.2.x is not required. - ServiceTag string - ServiceTags []string - ServiceAddress string - TagFilter bool // Controls tag filtering - Source QuerySource + ServiceTag string + ServiceTags []string + ServiceAddress string + TagFilter bool // Controls tag filtering + HealthFilterType HealthFilterType + Source QuerySource // Connect if true will only search for Connect-compatible services. Connect bool @@ -819,9 +824,11 @@ func (r *ServiceSpecificRequest) CacheInfo() cache.RequestInfo { r.Filter, r.EnterpriseMeta, r.PeerName, + r.SamenessGroup, r.Ingress, r.ServiceKind, r.MergeCentralConfig, + r.HealthFilterType, }, nil) if err == nil { // If there is an error, we don't set the key. A blank key forces @@ -2122,6 +2129,19 @@ func (csn *CheckServiceNode) Locality() *Locality { return nil } +func (csn *CheckServiceNode) ExcludeBasedOnChecks(opts CheckServiceNodeFilterOptions) bool { + for _, check := range csn.Checks { + if slices.Contains(opts.IgnoreCheckIDs, check.CheckID) { + // Skip this _check_ but keep looking at other checks for this node. + continue + } + if opts.FilterType.ExcludeBasedOnStatus(check.Status) { + return true + } + } + return false +} + type CheckServiceNodes []CheckServiceNode func (csns CheckServiceNodes) DeepCopy() CheckServiceNodes { @@ -2161,39 +2181,42 @@ func (nodes CheckServiceNodes) ShallowClone() CheckServiceNodes { return dup } +// HealthFilterType is used to filter nodes based on their health status. +type HealthFilterType int32 + +func (h HealthFilterType) ExcludeBasedOnStatus(status string) bool { + switch { + case h == HealthFilterExcludeCritical && status == api.HealthCritical: + return true + case h == HealthFilterIncludeOnlyPassing && status != api.HealthPassing: + return true + } + return false +} + +// These are listed from most to least inclusive. +const ( + HealthFilterIncludeAll HealthFilterType = 0 + HealthFilterExcludeCritical HealthFilterType = 1 + HealthFilterIncludeOnlyPassing HealthFilterType = 2 +) + +type CheckServiceNodeFilterOptions struct { + FilterType HealthFilterType + IgnoreCheckIDs []types.CheckID + disableReceiverModification bool +} + // Filter removes nodes that are failing health checks (and any non-passing // check if that option is selected). Note that this returns the filtered // results AND modifies the receiver for performance. -func (nodes CheckServiceNodes) Filter(onlyPassing bool) CheckServiceNodes { - return nodes.FilterIgnore(onlyPassing, nil) -} - -// FilterIgnore removes nodes that are failing health checks just like Filter. -// It also ignores the status of any check with an ID present in ignoreCheckIDs -// as if that check didn't exist. Note that this returns the filtered results -// AND modifies the receiver for performance. -func (nodes CheckServiceNodes) FilterIgnore(onlyPassing bool, - ignoreCheckIDs []types.CheckID) CheckServiceNodes { +func (nodes CheckServiceNodes) Filter(opts CheckServiceNodeFilterOptions) CheckServiceNodes { n := len(nodes) -OUTER: for i := 0; i < n; i++ { - node := nodes[i] - INNER: - for _, check := range node.Checks { - for _, ignore := range ignoreCheckIDs { - if check.CheckID == ignore { - // Skip this _check_ but keep looking at other checks for this node. - continue INNER - } - } - if check.Status == api.HealthCritical || - (onlyPassing && check.Status != api.HealthPassing) { - nodes[i], nodes[n-1] = nodes[n-1], CheckServiceNode{} - n-- - i-- - // Skip this _node_ now we've swapped it off the end of the list. - continue OUTER - } + if nodes[i].ExcludeBasedOnChecks(opts) { + nodes[i], nodes[n-1] = nodes[n-1], CheckServiceNode{} + n-- + i-- } } return nodes[:n] diff --git a/agent/structs/structs_test.go b/agent/structs/structs_test.go index bf909aa419..3f73e0cc28 100644 --- a/agent/structs/structs_test.go +++ b/agent/structs/structs_test.go @@ -1725,7 +1725,7 @@ func TestCheckServiceNodes_Filter(t *testing.T) { if n := copy(twiddle, nodes); n != len(nodes) { t.Fatalf("bad: %d", n) } - filtered := twiddle.Filter(false) + filtered := twiddle.Filter(CheckServiceNodeFilterOptions{FilterType: HealthFilterExcludeCritical}) expected := CheckServiceNodes{ nodes[0], nodes[1], @@ -1741,7 +1741,7 @@ func TestCheckServiceNodes_Filter(t *testing.T) { if n := copy(twiddle, nodes); n != len(nodes) { t.Fatalf("bad: %d", n) } - filtered := twiddle.Filter(true) + filtered := twiddle.Filter(CheckServiceNodeFilterOptions{FilterType: HealthFilterIncludeOnlyPassing}) expected := CheckServiceNodes{ nodes[1], } @@ -1757,7 +1757,7 @@ func TestCheckServiceNodes_Filter(t *testing.T) { if n := copy(twiddle, nodes); n != len(nodes) { t.Fatalf("bad: %d", n) } - filtered := twiddle.FilterIgnore(true, []types.CheckID{""}) + filtered := twiddle.Filter(CheckServiceNodeFilterOptions{FilterType: HealthFilterIncludeOnlyPassing, IgnoreCheckIDs: []types.CheckID{""}}) expected := CheckServiceNodes{ nodes[0], nodes[1], diff --git a/agent/testagent.go b/agent/testagent.go index a18dee1ead..5f0225c425 100644 --- a/agent/testagent.go +++ b/agent/testagent.go @@ -19,9 +19,10 @@ import ( "time" "github.com/armon/go-metrics" + "github.com/stretchr/testify/require" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-uuid" - "github.com/stretchr/testify/require" "github.com/hashicorp/consul/acl" "github.com/hashicorp/consul/agent/config" @@ -105,7 +106,7 @@ type TestAgentOpts struct { // NewTestAgent returns a started agent with the given configuration. It fails // the test if the Agent could not be started. -func NewTestAgent(t *testing.T, hcl string, opts ...TestAgentOpts) *TestAgent { +func NewTestAgent(t testing.TB, hcl string, opts ...TestAgentOpts) *TestAgent { // This varargs approach is used so that we don't have to modify all of the `NewTestAgent()` calls // in order to introduce more optional arguments. require.LessOrEqual(t, len(opts), 1, "NewTestAgent cannot accept more than one opts argument") @@ -133,7 +134,7 @@ func NewTestAgentWithConfigFile(t *testing.T, hcl string, configFiles []string) // // The caller is responsible for calling Shutdown() to stop the agent and remove // temporary directories. -func StartTestAgent(t *testing.T, a TestAgent) *TestAgent { +func StartTestAgent(t testing.TB, a TestAgent) *TestAgent { t.Helper() retry.RunWith(retry.ThreeTimes(), t, func(r *retry.R) { r.Helper() @@ -315,22 +316,6 @@ func (a *TestAgent) waitForUp() error { } } - if a.baseDeps.UseV2Resources() { - args := structs.DCSpecificRequest{ - Datacenter: "dc1", - } - var leader string - if err := a.RPC(context.Background(), "Status.Leader", args, &leader); err != nil { - retErr = fmt.Errorf("Status.Leader failed: %v", err) - continue // fail, try again - } - if leader == "" { - retErr = fmt.Errorf("No leader") - continue // fail, try again - } - return nil // success - } - // Ensure we have a leader and a node registration. args := &structs.DCSpecificRequest{ Datacenter: a.Config.Datacenter, diff --git a/agent/ui_endpoint_test.go b/agent/ui_endpoint_test.go index dd1c6d8134..2df3cb7849 100644 --- a/agent/ui_endpoint_test.go +++ b/agent/ui_endpoint_test.go @@ -18,10 +18,11 @@ import ( "testing" "time" - cleanhttp "github.com/hashicorp/go-cleanhttp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + cleanhttp "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/consul/agent/config" "github.com/hashicorp/consul/agent/structs" "github.com/hashicorp/consul/api" @@ -32,28 +33,6 @@ import ( "github.com/hashicorp/consul/types" ) -func TestUIEndpointsFailInV2(t *testing.T) { - t.Parallel() - - a := NewTestAgent(t, `experiments = ["resource-apis"]`) - - checkRequest := func(method, url string) { - t.Run(method+" "+url, func(t *testing.T) { - assertV1CatalogEndpointDoesNotWorkWithV2(t, a, method, url, "{}") - }) - } - - checkRequest("GET", "/v1/internal/ui/nodes") - checkRequest("GET", "/v1/internal/ui/node/web") - checkRequest("GET", "/v1/internal/ui/services") - checkRequest("GET", "/v1/internal/ui/exported-services") - checkRequest("GET", "/v1/internal/ui/catalog-overview") - checkRequest("GET", "/v1/internal/ui/gateway-services-nodes/web") - checkRequest("GET", "/v1/internal/ui/gateway-intentions/web") - checkRequest("GET", "/v1/internal/ui/service-topology/web") - checkRequest("PUT", "/v1/internal/service-virtual-ip") -} - func TestUIIndex(t *testing.T) { if testing.Short() { t.Skip("too slow for testing.Short") diff --git a/agent/uiserver/dist/assets/chunk.143.db9b393ab5086aec385b.css b/agent/uiserver/dist/assets/chunk.143.514eb80cfd7d0554773a.css similarity index 90% rename from agent/uiserver/dist/assets/chunk.143.db9b393ab5086aec385b.css rename to agent/uiserver/dist/assets/chunk.143.514eb80cfd7d0554773a.css index 0f89c12af7..792cf60b33 100644 --- a/agent/uiserver/dist/assets/chunk.143.db9b393ab5086aec385b.css +++ b/agent/uiserver/dist/assets/chunk.143.514eb80cfd7d0554773a.css @@ -1,3 +1,3 @@ .tippy-box[data-placement^=top]>.tippy-svg-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-svg-arrow:after,.tippy-box[data-placement^=top]>.tippy-svg-arrow>svg{top:16px;transform:rotate(180deg)}.tippy-box[data-placement^=bottom]>.tippy-svg-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-svg-arrow>svg{bottom:16px}.tippy-box[data-placement^=left]>.tippy-svg-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-svg-arrow:after,.tippy-box[data-placement^=left]>.tippy-svg-arrow>svg{transform:rotate(90deg);top:calc(50% - 3px);left:11px}.tippy-box[data-placement^=right]>.tippy-svg-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-svg-arrow:after,.tippy-box[data-placement^=right]>.tippy-svg-arrow>svg{transform:rotate(-90deg);top:calc(50% - 3px);right:11px}.tippy-svg-arrow{width:16px;height:16px;fill:#333;text-align:initial}.tippy-svg-arrow,.tippy-svg-arrow>svg{position:absolute} -/*# sourceMappingURL=chunk.143.db9b393ab5086aec385b.css-481e257b97fcf81c5a59ed89bec95f69.map*/ \ No newline at end of file +/*# sourceMappingURL=chunk.143.514eb80cfd7d0554773a.css-dab4810f7765b133c18bff2650e193b5.map*/ \ No newline at end of file diff --git a/agent/uiserver/dist/assets/chunk.143.db9b393ab5086aec385b.js b/agent/uiserver/dist/assets/chunk.143.514eb80cfd7d0554773a.js similarity index 97% rename from agent/uiserver/dist/assets/chunk.143.db9b393ab5086aec385b.js rename to agent/uiserver/dist/assets/chunk.143.514eb80cfd7d0554773a.js index 9e26ff5adc..401037dc38 100644 --- a/agent/uiserver/dist/assets/chunk.143.db9b393ab5086aec385b.js +++ b/agent/uiserver/dist/assets/chunk.143.514eb80cfd7d0554773a.js @@ -7,8 +7,8 @@ e.exports=require("@ember/modifier")},7219:e=>{"use strict" e.exports=require("@ember/object")},8773:e=>{"use strict" e.exports=require("@ember/runloop")},8574:e=>{"use strict" e.exports=require("@ember/service")},1866:e=>{"use strict" -e.exports=require("@ember/utils")},657:(e,r,t)=>{var n,o -e.exports=(n=_eai_d,o=_eai_r,window.emberAutoImportDynamic=function(e){return 1===arguments.length?o("_eai_dyn_"+e):o("_eai_dynt_"+e)(Array.prototype.slice.call(arguments,1))},window.emberAutoImportSync=function(e){return o("_eai_sync_"+e)(Array.prototype.slice.call(arguments,1))},n("@hashicorp/flight-icons/svg",[],(function(){return t(6604)})),n("@hashicorp/flight-icons/svg-sprite/svg-sprite-module",[],(function(){return t(2654)})),n("@lit/reactive-element",[],(function(){return t(8531)})),n("@xstate/fsm",[],(function(){return t(7440)})),n("a11y-dialog",[],(function(){return t(1413)})),n("base64-js",[],(function(){return t(8294)})),n("clipboard",[],(function(){return t(9079)})),n("d3-array",[],(function(){return t(5447)})),n("d3-scale",[],(function(){return t(5134)})),n("d3-scale-chromatic",[],(function(){return t(2331)})),n("d3-selection",[],(function(){return t(8740)})),n("d3-shape",[],(function(){return t(5043)})),n("dayjs",[],(function(){return t(2350)})),n("dayjs/plugin/calendar",[],(function(){return t(8888)})),n("dayjs/plugin/relativeTime",[],(function(){return t(2543)})),n("deepmerge",[],(function(){return t(3924)})),n("ember-focus-trap/modifiers/focus-trap.js",["@ember/modifier"],(function(){return t(3109)})),n("ember-keyboard/helpers/if-key.js",["@ember/component/helper","@ember/debug","@ember/utils"],(function(){return t(3481)})),n("ember-keyboard/helpers/on-key.js",["@ember/component/helper","@ember/debug","@ember/service"],(function(){return t(6415)})),n("ember-keyboard/modifiers/on-key.js",["@ember/application","@ember/modifier","@ember/destroyable","@ember/service","@ember/object","@ember/debug","@ember/utils"],(function(){return t(4146)})),n("ember-keyboard/services/keyboard.js",["@ember/service","@ember/application","@ember/object","@ember/runloop","@ember/debug","@ember/utils"],(function(){return t(9690)})),n("ember-modifier",["@ember/application","@ember/modifier","@ember/destroyable"],(function(){return t(2509)})),n("fast-memoize",[],(function(){return t(3276)})),n("flat",[],(function(){return t(2349)})),n("intl-messageformat",[],(function(){return t(5861)})),n("intl-messageformat-parser",[],(function(){return t(5011)})),n("mnemonist/multi-map",[],(function(){return t(5709)})),n("mnemonist/set",[],(function(){return t(2519)})),n("ngraph.graph",[],(function(){return t(6001)})),n("parse-duration",[],(function(){return t(89)})),n("pretty-ms",[],(function(){return t(9837)})),n("tippy.js",[],(function(){return t(9640)})),n("tippy.js/dist/svg-arrow.css",[],(function(){return t(2959)})),n("validated-changeset",[],(function(){return t(6885)})),n("wayfarer",[],(function(){return t(7116)})),n("_eai_dyn_dialog-polyfill",[],(function(){return t.e(121).then(t.bind(t,4121))})),void n("_eai_dyn_dialog-polyfill-css",[],(function(){return t.e(744).then(t.bind(t,7744))})))},1760:function(e,r){window._eai_r=require,window._eai_d=define}},o={} +e.exports=require("@ember/utils")},9357:(e,r,t)=>{var n,o +e.exports=(n=_eai_d,o=_eai_r,window.emberAutoImportDynamic=function(e){return 1===arguments.length?o("_eai_dyn_"+e):o("_eai_dynt_"+e)(Array.prototype.slice.call(arguments,1))},window.emberAutoImportSync=function(e){return o("_eai_sync_"+e)(Array.prototype.slice.call(arguments,1))},n("@hashicorp/flight-icons/svg",[],(function(){return t(6604)})),n("@hashicorp/flight-icons/svg-sprite/svg-sprite-module",[],(function(){return t(2654)})),n("@lit/reactive-element",[],(function(){return t(8531)})),n("@xstate/fsm",[],(function(){return t(7440)})),n("a11y-dialog",[],(function(){return t(1413)})),n("base64-js",[],(function(){return t(8294)})),n("clipboard",[],(function(){return t(9079)})),n("d3-array",[],(function(){return t(5447)})),n("d3-scale",[],(function(){return t(5134)})),n("d3-scale-chromatic",[],(function(){return t(2331)})),n("d3-selection",[],(function(){return t(8740)})),n("d3-shape",[],(function(){return t(5043)})),n("dayjs",[],(function(){return t(2350)})),n("dayjs/plugin/calendar",[],(function(){return t(8888)})),n("dayjs/plugin/relativeTime",[],(function(){return t(2543)})),n("deepmerge",[],(function(){return t(3924)})),n("ember-focus-trap/modifiers/focus-trap.js",["@ember/modifier"],(function(){return t(3109)})),n("ember-keyboard/helpers/if-key.js",["@ember/component/helper","@ember/debug","@ember/utils"],(function(){return t(3481)})),n("ember-keyboard/helpers/on-key.js",["@ember/component/helper","@ember/debug","@ember/service"],(function(){return t(6415)})),n("ember-keyboard/modifiers/on-key.js",["@ember/application","@ember/modifier","@ember/destroyable","@ember/service","@ember/object","@ember/debug","@ember/utils"],(function(){return t(4146)})),n("ember-keyboard/services/keyboard.js",["@ember/service","@ember/application","@ember/object","@ember/runloop","@ember/debug","@ember/utils"],(function(){return t(9690)})),n("ember-modifier",["@ember/application","@ember/modifier","@ember/destroyable"],(function(){return t(2509)})),n("fast-memoize",[],(function(){return t(3276)})),n("flat",[],(function(){return t(2349)})),n("intl-messageformat",[],(function(){return t(5861)})),n("intl-messageformat-parser",[],(function(){return t(5011)})),n("mnemonist/multi-map",[],(function(){return t(5709)})),n("mnemonist/set",[],(function(){return t(2519)})),n("ngraph.graph",[],(function(){return t(6001)})),n("parse-duration",[],(function(){return t(89)})),n("pretty-ms",[],(function(){return t(9837)})),n("tippy.js",[],(function(){return t(9640)})),n("tippy.js/dist/svg-arrow.css",[],(function(){return t(2959)})),n("validated-changeset",[],(function(){return t(6885)})),n("wayfarer",[],(function(){return t(7116)})),n("_eai_dyn_dialog-polyfill",[],(function(){return t.e(121).then(t.bind(t,4121))})),void n("_eai_dyn_dialog-polyfill-css",[],(function(){return t.e(744).then(t.bind(t,7744))})))},3404:function(e,r){window._eai_r=require,window._eai_d=define}},o={} function i(e){var r=o[e] if(void 0!==r)return r.exports var t=o[e]={exports:{}} @@ -47,6 +47,6 @@ var r=(r,t)=>{var n,o,[u,a,s]=t,c=0 if(u.some((r=>0!==e[r]))){for(n in a)i.o(a,n)&&(i.m[n]=a[n]) if(s)var l=s(i)}for(r&&r(t);ci(1760))) -var u=i.O(void 0,[924],(()=>i(657))) +t.forEach(r.bind(null,0)),t.push=r.bind(null,t.push.bind(t))})(),i.O(void 0,[924],(()=>i(3404))) +var u=i.O(void 0,[924],(()=>i(9357))) u=i.O(u),__ember_auto_import__=u})() diff --git a/agent/uiserver/dist/assets/chunk.178.ee7822d83932910fd2ef.js b/agent/uiserver/dist/assets/chunk.178.6f0fd5b292c40b1ce063.js similarity index 91% rename from agent/uiserver/dist/assets/chunk.178.ee7822d83932910fd2ef.js rename to agent/uiserver/dist/assets/chunk.178.6f0fd5b292c40b1ce063.js index 2c477d5653..fa8fae203c 100644 --- a/agent/uiserver/dist/assets/chunk.178.ee7822d83932910fd2ef.js +++ b/agent/uiserver/dist/assets/chunk.178.6f0fd5b292c40b1ce063.js @@ -1,4 +1,4 @@ -var __ember_auto_import__;(()=>{var r,e={9265:()=>{},3642:()=>{},1760:function(r,e){window._eai_r=require,window._eai_d=define},1471:(r,e,t)=>{var o,n +var __ember_auto_import__;(()=>{var r,e={9265:()=>{},3642:()=>{},3404:function(r,e){window._eai_r=require,window._eai_d=define},974:(r,e,t)=>{var o,n r.exports=(o=_eai_d,n=_eai_r,window.emberAutoImportDynamic=function(r){return 1===arguments.length?n("_eai_dyn_"+r):n("_eai_dynt_"+r)(Array.prototype.slice.call(arguments,1))},window.emberAutoImportSync=function(r){return n("_eai_sync_"+r)(Array.prototype.slice.call(arguments,1))},o("lodash.castarray",[],(function(){return t(9542)})),o("lodash.last",[],(function(){return t(9644)})),o("lodash.omit",[],(function(){return t(1609)})),o("qunit",[],(function(){return t(2053)})),void o("yadda",[],(function(){return t(2216)})))}},t={} function o(r){var n=t[r] if(void 0!==n)return n.exports @@ -16,6 +16,6 @@ var e=(e,t)=>{var n,i,[a,u,l]=t,_=0 if(a.some((e=>0!==r[e]))){for(n in u)o.o(u,n)&&(o.m[n]=u[n]) if(l)var c=l(o)}for(e&&e(t);_o(1760))) -var n=o.O(void 0,[778],(()=>o(1471))) +t.forEach(e.bind(null,0)),t.push=e.bind(null,t.push.bind(t))})(),o.O(void 0,[778],(()=>o(3404))) +var n=o.O(void 0,[778],(()=>o(974))) n=o.O(n),__ember_auto_import__=n})() diff --git a/agent/uiserver/dist/assets/consul-hcp/routes-50ef0656ce15ca397f130b4f71463b3d.js b/agent/uiserver/dist/assets/consul-hcp/routes-50ef0656ce15ca397f130b4f71463b3d.js deleted file mode 100644 index 703c187720..0000000000 --- a/agent/uiserver/dist/assets/consul-hcp/routes-50ef0656ce15ca397f130b4f71463b3d.js +++ /dev/null @@ -1 +0,0 @@ -((e,t=("undefined"!=typeof document?document.currentScript.dataset:module.exports))=>{t.routes=JSON.stringify(e)})({dc:{show:null}}) diff --git a/agent/uiserver/dist/assets/consul-hcp/services-0f03ec98fed9bf2a0b847a11efca654f.js b/agent/uiserver/dist/assets/consul-hcp/services-0f03ec98fed9bf2a0b847a11efca654f.js deleted file mode 100644 index dc99739254..0000000000 --- a/agent/uiserver/dist/assets/consul-hcp/services-0f03ec98fed9bf2a0b847a11efca654f.js +++ /dev/null @@ -1 +0,0 @@ -((e,o=("undefined"!=typeof document?document.currentScript.dataset:module.exports))=>{o.services=JSON.stringify(e)})({"component:consul/hcp/home":{class:"consul-ui/components/consul/hcp/home"}}) diff --git a/agent/uiserver/dist/assets/consul-ui-6998e44e8a8ea2039fda574ce1ac1be5.css b/agent/uiserver/dist/assets/consul-ui-6998e44e8a8ea2039fda574ce1ac1be5.css new file mode 100644 index 0000000000..ce065de1b5 --- /dev/null +++ b/agent/uiserver/dist/assets/consul-ui-6998e44e8a8ea2039fda574ce1ac1be5.css @@ -0,0 +1,2 @@ +@charset "UTF-8";/*! tailwindcss v3.2.7 | MIT License | https://tailwindcss.com + */.hds-table,table{border-spacing:0}input[type=checkbox],input[type=radio],progress,sub,sup{vertical-align:baseline}.hover\:scale-125:hover,.scale-100,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.ease-in-out,.transition{transition-timing-function:cubic-bezier(.4,0,.2,1)}*,::after,::before{border-width:0;border-style:solid;border-color:currentColor}html{line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-feature-settings:normal}a,hr{color:inherit}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto}[hidden]{display:none}*,::after,::backdrop,::before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scroll-snap-strictness:proximity;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.hds-side-nav,.sticky{position:sticky}.bottom-0{bottom:0}.isolate{isolation:isolate}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-6{margin-bottom:1.5rem}.mb-8{margin-bottom:2rem}.ml-4{margin-left:1rem}.mr-0{margin-right:0}.mr-0\.5{margin-right:.125rem}.mr-1{margin-right:.25rem}.mr-1\.5{margin-right:.375rem}.mr-2{margin-right:.5rem}.mr-2\.5{margin-right:.625rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.contents{display:contents}.hidden{display:none}.h-12{height:3rem}.h-16{height:4rem}.h-24{height:6rem}.h-4{height:1rem}.h-48{height:12rem}.h-8{height:2rem}.h-full{height:100%}.h-screen{height:100vh}.\!w-80{width:20rem!important}.w-24{width:6rem}.w-4{width:1rem}.w-8{width:2rem}.w-full{width:100%}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.flex-col,.hds-accordion{flex-direction:column}.scale-100{--tw-scale-x:1;--tw-scale-y:1}.cursor-pointer{cursor:pointer}.resize{resize:both}.flex-nowrap{flex-wrap:nowrap}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.space-x-12>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(3rem * var(--tw-space-x-reverse));margin-left:calc(3rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.overflow-x-auto{overflow-x:auto}.overflow-y-scroll{overflow-y:scroll}.hds-breadcrumb__text,.hds-card__container--overflow-hidden,.truncate{overflow:hidden}.truncate{text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.border{border-width:1px}.p-6{padding:1.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize,.type-source.popover-select li:not(.partition) button{text-transform:capitalize}.underline{text-decoration-line:underline}.opacity-0{opacity:0}.shadow{--tw-shadow:0 1px 3px 0 rgb(0 0 0 / 0.1),0 1px 2px -1px rgb(0 0 0 / 0.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.hds-elevation-inset,.hds-form-checkbox:not(:checked,:indeterminate){box-shadow:var(--token-elevation-inset-box-shadow)}.outline{outline-style:solid}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:150ms}.consul-surface-nav{background:var(--token-color-palette-neutral-700)}:root{--token-color-palette-blue-500:#1c345f;--token-color-palette-blue-400:#0046d1;--token-color-palette-blue-300:#0c56e9;--token-color-palette-blue-200:#1060ff;--token-color-palette-blue-100:#cce3fe;--token-color-palette-blue-50:#f2f8ff;--token-color-palette-purple-500:#42215b;--token-color-palette-purple-400:#7b00db;--token-color-palette-purple-300:#911ced;--token-color-palette-purple-200:#a737ff;--token-color-palette-purple-100:#ead2fe;--token-color-palette-purple-50:#f9f2ff;--token-color-palette-green-500:#054220;--token-color-palette-green-400:#006619;--token-color-palette-green-300:#00781e;--token-color-palette-green-200:#008a22;--token-color-palette-green-100:#cceeda;--token-color-palette-green-50:#f2fbf6;--token-color-palette-amber-500:#542800;--token-color-palette-amber-400:#803d00;--token-color-palette-amber-300:#9e4b00;--token-color-palette-amber-200:#bb5a00;--token-color-palette-amber-100:#fbeabf;--token-color-palette-amber-50:#fff9e8;--token-color-palette-red-500:#51130a;--token-color-palette-red-400:#940004;--token-color-palette-red-300:#c00005;--token-color-palette-red-200:#e52228;--token-color-palette-red-100:#fbd4d4;--token-color-palette-red-50:#fff5f5;--token-color-palette-neutral-700:#0c0c0e;--token-color-palette-neutral-600:#3b3d45;--token-color-palette-neutral-500:#656a76;--token-color-palette-neutral-400:#8c909c;--token-color-palette-neutral-300:#c2c5cb;--token-color-palette-neutral-200:#dedfe3;--token-color-palette-neutral-100:#f1f2f3;--token-color-palette-neutral-50:#fafafa;--token-color-palette-neutral-0:#ffffff;--token-color-palette-alpha-300:#3b3d4566;--token-color-palette-alpha-200:#656a7633;--token-color-palette-alpha-100:#656a761a;--token-color-border-primary:#656a7633;--token-color-border-faint:#656a761a;--token-color-border-strong:#3b3d4566;--token-color-border-action:#cce3fe;--token-color-border-highlight:#ead2fe;--token-color-border-success:#cceeda;--token-color-border-warning:#fbeabf;--token-color-border-critical:#fbd4d4;--token-color-focus-action-internal:#0c56e9;--token-color-focus-action-external:#5990ff;--token-color-focus-critical-internal:#c00005;--token-color-focus-critical-external:#dd7578;--token-color-foreground-strong:#0c0c0e;--token-color-foreground-primary:#3b3d45;--token-color-foreground-faint:#656a76;--token-color-foreground-high-contrast:#ffffff;--token-color-foreground-disabled:#8c909c;--token-color-foreground-action:#1060ff;--token-color-foreground-action-hover:#0c56e9;--token-color-foreground-action-active:#0046d1;--token-color-foreground-highlight:#a737ff;--token-color-foreground-highlight-on-surface:#911ced;--token-color-foreground-highlight-high-contrast:#42215b;--token-color-foreground-success:#008a22;--token-color-foreground-success-on-surface:#00781e;--token-color-foreground-success-high-contrast:#054220;--token-color-foreground-warning:#bb5a00;--token-color-foreground-warning-on-surface:#9e4b00;--token-color-foreground-warning-high-contrast:#542800;--token-color-foreground-critical:#e52228;--token-color-foreground-critical-on-surface:#c00005;--token-color-foreground-critical-high-contrast:#51130a;--token-color-page-primary:#ffffff;--token-color-page-faint:#fafafa;--token-color-surface-primary:#ffffff;--token-color-surface-faint:#fafafa;--token-color-surface-strong:#f1f2f3;--token-color-surface-interactive:#ffffff;--token-color-surface-interactive-hover:#f1f2f3;--token-color-surface-interactive-active:#dedfe3;--token-color-surface-interactive-disabled:#fafafa;--token-color-surface-action:#f2f8ff;--token-color-surface-highlight:#f9f2ff;--token-color-surface-success:#f2fbf6;--token-color-surface-warning:#fff9e8;--token-color-surface-critical:#fff5f5;--token-color-hashicorp-brand:#000000;--token-color-boundary-brand:#f24c53;--token-color-boundary-foreground:#cf2d32;--token-color-boundary-surface:#ffecec;--token-color-boundary-border:#fbd7d8;--token-color-boundary-gradient-primary-start:#f97076;--token-color-boundary-gradient-primary-stop:#db363b;--token-color-boundary-gradient-faint-start:#fffafa;--token-color-boundary-gradient-faint-stop:#ffecec;--token-color-consul-brand:#e03875;--token-color-consul-foreground:#d01c5b;--token-color-consul-surface:#ffe9f1;--token-color-consul-border:#ffcede;--token-color-consul-gradient-primary-start:#ff99be;--token-color-consul-gradient-primary-stop:#da306e;--token-color-consul-gradient-faint-start:#fff9fb;--token-color-consul-gradient-faint-stop:#ffe9f1;--token-color-hcp-brand:#000000;--token-color-nomad-brand:#06d092;--token-color-nomad-foreground:#008661;--token-color-nomad-surface:#d3fdeb;--token-color-nomad-border:#bff3dd;--token-color-nomad-gradient-primary-start:#bff3dd;--token-color-nomad-gradient-primary-stop:#60dea9;--token-color-nomad-gradient-faint-start:#f3fff9;--token-color-nomad-gradient-faint-stop:#d3fdeb;--token-color-packer-brand:#02a8ef;--token-color-packer-foreground:#007eb4;--token-color-packer-surface:#d4f2ff;--token-color-packer-border:#b4e4ff;--token-color-packer-gradient-primary-start:#b4e4ff;--token-color-packer-gradient-primary-stop:#63d0ff;--token-color-packer-gradient-faint-start:#f3fcff;--token-color-packer-gradient-faint-stop:#d4f2ff;--token-color-terraform-brand:#7b42bc;--token-color-terraform-foreground:#773cb4;--token-color-terraform-surface:#f4ecff;--token-color-terraform-border:#ebdbfc;--token-color-terraform-gradient-primary-start:#bb8deb;--token-color-terraform-gradient-primary-stop:#844fba;--token-color-terraform-gradient-faint-start:#fcfaff;--token-color-terraform-gradient-faint-stop:#f4ecff;--token-color-vagrant-brand:#1868f2;--token-color-vagrant-foreground:#1c61d8;--token-color-vagrant-surface:#d6ebff;--token-color-vagrant-border:#c7dbfc;--token-color-vagrant-gradient-primary-start:#c7dbfc;--token-color-vagrant-gradient-primary-stop:#7dadff;--token-color-vagrant-gradient-faint-start:#f4faff;--token-color-vagrant-gradient-faint-stop:#d6ebff;--token-color-vault-secrets-brand:#ffd814;--token-color-vault-secrets-brand-alt:#000000;--token-color-vault-secrets-foreground:#9a6f00;--token-color-vault-secrets-surface:#fff9cf;--token-color-vault-secrets-border:#feec7b;--token-color-vault-secrets-gradient-primary-start:#feec7b;--token-color-vault-secrets-gradient-primary-stop:#ffe543;--token-color-vault-secrets-gradient-faint-start:#fffdf2;--token-color-vault-secrets-gradient-faint-stop:#fff9cf;--token-color-vault-brand:#ffd814;--token-color-vault-brand-alt:#000000;--token-color-vault-foreground:#9a6f00;--token-color-vault-surface:#fff9cf;--token-color-vault-border:#feec7b;--token-color-vault-gradient-primary-start:#feec7b;--token-color-vault-gradient-primary-stop:#ffe543;--token-color-vault-gradient-faint-start:#fffdf2;--token-color-vault-gradient-faint-stop:#fff9cf;--token-color-waypoint-brand:#14c6cb;--token-color-waypoint-foreground:#008196;--token-color-waypoint-surface:#e0fcff;--token-color-waypoint-border:#cbf1f3;--token-color-waypoint-gradient-primary-start:#cbf1f3;--token-color-waypoint-gradient-primary-stop:#62d4dc;--token-color-waypoint-gradient-faint-start:#f6feff;--token-color-waypoint-gradient-faint-stop:#e0fcff;--token-elevation-inset-box-shadow:inset 0px 1px 2px 1px #656a761a;--token-elevation-low-box-shadow:0px 1px 1px 0px #656a760d,0px 2px 2px 0px #656a760d;--token-elevation-mid-box-shadow:0px 2px 3px 0px #656a761a,0px 8px 16px -10px #656a7633;--token-elevation-high-box-shadow:0px 2px 3px 0px #656a7626,0px 16px 16px -10px #656a7633;--token-elevation-higher-box-shadow:0px 2px 3px 0px #656a761a,0px 12px 28px 0px #656a7640;--token-elevation-overlay-box-shadow:0px 2px 3px 0px #3b3d4540,0px 12px 24px 0px #3b3d4559;--token-surface-inset-box-shadow:inset 0 0 0 1px #656a764d,inset 0px 1px 2px 1px #656a761a;--token-surface-base-box-shadow:0 0 0 1px #656a7633;--token-surface-low-box-shadow:0 0 0 1px #656a7626,0px 1px 1px 0px #656a760d,0px 2px 2px 0px #656a760d;--token-surface-mid-box-shadow:0 0 0 1px #656a7626,0px 2px 3px 0px #656a761a,0px 8px 16px -10px #656a7633;--token-surface-high-box-shadow:0 0 0 1px #656a7640,0px 2px 3px 0px #656a7626,0px 16px 16px -10px #656a7633;--token-surface-higher-box-shadow:0 0 0 1px #656a7633,0px 2px 3px 0px #656a761a,0px 12px 28px 0px #656a7640;--token-surface-overlay-box-shadow:0 0 0 1px #3b3d4540,0px 2px 3px 0px #3b3d4540,0px 12px 24px 0px #3b3d4559;--token-focus-ring-action-box-shadow:inset 0 0 0 1px #0c56e9,0 0 0 3px #5990ff;--token-focus-ring-critical-box-shadow:inset 0 0 0 1px #c00005,0 0 0 3px #dd7578;--token-form-label-color:#0c0c0e;--token-form-legend-color:#0c0c0e;--token-form-helper-text-color:#656a76;--token-form-indicator-optional-color:#656a76;--token-form-error-color:#c00005;--token-form-error-icon-size:14px;--token-form-checkbox-size:16px;--token-form-checkbox-border-radius:3px;--token-form-checkbox-border-width:1px;--token-form-checkbox-background-image-size:12px;--token-form-checkbox-background-image-data-url:url("data:image/svg+xml,%3csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M9.78033 3.21967C10.0732 3.51256 10.0732 3.98744 9.78033 4.28033L5.28033 8.78033C4.98744 9.07322 4.51256 9.07322 4.21967 8.78033L2.21967 6.78033C1.92678 6.48744 1.92678 6.01256 2.21967 5.71967C2.51256 5.42678 2.98744 5.42678 3.28033 5.71967L4.75 7.18934L8.71967 3.21967C9.01256 2.92678 9.48744 2.92678 9.78033 3.21967Z' fill='%23FFF'/%3e%3c/svg%3e");--token-form-checkbox-background-image-data-url-indeterminate:url("data:image/svg+xml,%3csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m2.03125,6a0.66146,0.75 0 0 1 0.66146,-0.75l6.61458,0a0.66146,0.75 0 0 1 0,1.5l-6.61458,0a0.66146,0.75 0 0 1 -0.66146,-0.75z' fill='%23FFF'/%3e%3c/svg%3e");--token-form-checkbox-background-image-data-url-disabled:url("data:image/svg+xml,%3csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M9.78033 3.21967C10.0732 3.51256 10.0732 3.98744 9.78033 4.28033L5.28033 8.78033C4.98744 9.07322 4.51256 9.07322 4.21967 8.78033L2.21967 6.78033C1.92678 6.48744 1.92678 6.01256 2.21967 5.71967C2.51256 5.42678 2.98744 5.42678 3.28033 5.71967L4.75 7.18934L8.71967 3.21967C9.01256 2.92678 9.48744 2.92678 9.78033 3.21967Z' fill='%238C909C'/%3e%3c/svg%3e");--token-form-checkbox-background-image-data-url-indeterminate-disabled:url("data:image/svg+xml,%3csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m2.03125,6a0.66146,0.75 0 0 1 0.66146,-0.75l6.61458,0a0.66146,0.75 0 0 1 0,1.5l-6.61458,0a0.66146,0.75 0 0 1 -0.66146,-0.75z' fill='%238C909C'/%3e%3c/svg%3e");--token-form-control-base-foreground-value-color:#0c0c0e;--token-form-control-base-foreground-placeholder-color:#656a76;--token-form-control-base-surface-color-default:#ffffff;--token-form-control-base-surface-color-hover:#f1f2f3;--token-form-control-base-border-color-default:#8c909c;--token-form-control-base-border-color-hover:#656a76;--token-form-control-checked-foreground-color:#ffffff;--token-form-control-checked-surface-color-default:#1060ff;--token-form-control-checked-surface-color-hover:#0c56e9;--token-form-control-checked-border-color-default:#0c56e9;--token-form-control-checked-border-color-hover:#0046d1;--token-form-control-invalid-border-color-default:#c00005;--token-form-control-invalid-border-color-hover:#940004;--token-form-control-readonly-foreground-color:#3b3d45;--token-form-control-readonly-surface-color:#f1f2f3;--token-form-control-readonly-border-color:#656a761a;--token-form-control-disabled-foreground-color:#8c909c;--token-form-control-disabled-surface-color:#fafafa;--token-form-control-disabled-border-color:#656a7633;--token-form-control-padding:7px;--token-form-control-border-radius:5px;--token-form-control-border-width:1px;--token-form-radio-size:16px;--token-form-radio-border-width:1px;--token-form-radio-background-image-size:12px;--token-form-radio-background-image-data-url:url("data:image/svg+xml,%3csvg width='12' height='12' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='6' cy='6' r='2.5' fill='%23ffffff'/%3e%3c/svg%3e");--token-form-radio-background-image-data-url-disabled:url("data:image/svg+xml,%3csvg width='12' height='12' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='6' cy='6' r='2.5' fill='%238C909C'/%3e%3c/svg%3e");--token-form-radiocard-group-gap:16px;--token-form-radiocard-border-width:1px;--token-form-radiocard-border-radius:6px;--token-form-radiocard-content-padding:24px;--token-form-radiocard-control-padding:8px;--token-form-radiocard-transition-duration:0.2s;--token-form-select-background-image-size:16px;--token-form-select-background-image-position-right-x:7px;--token-form-select-background-image-position-top-y:9px;--token-form-select-background-image-data-url:url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8.55 2.24a.75.75 0 0 0-1.1 0L4.2 5.74a.75.75 0 1 0 1.1 1.02L8 3.852l2.7 2.908a.75.75 0 1 0 1.1-1.02l-3.25-3.5Zm-1.1 11.52a.75.75 0 0 0 1.1 0l3.25-3.5a.75.75 0 1 0-1.1-1.02L8 12.148 5.3 9.24a.75.75 0 0 0-1.1 1.02l3.25 3.5Z' fill='%23656A76'/%3E%3C/svg%3E");--token-form-select-background-image-data-url-disabled:url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8.55 2.24a.75.75 0 0 0-1.1 0L4.2 5.74a.75.75 0 1 0 1.1 1.02L8 3.852l2.7 2.908a.75.75 0 1 0 1.1-1.02l-3.25-3.5Zm-1.1 11.52a.75.75 0 0 0 1.1 0l3.25-3.5a.75.75 0 1 0-1.1-1.02L8 12.148 5.3 9.24a.75.75 0 0 0-1.1 1.02l3.25 3.5Z' fill='%238C909C'/%3E%3C/svg%3E");--token-form-text-input-background-image-size:16px;--token-form-text-input-background-image-position-x:7px;--token-form-text-input-background-image-data-url-date:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M11.5.75a.75.75 0 00-1.5 0V1H6V.75a.75.75 0 00-1.5 0V1H3.25A2.25 2.25 0 001 3.25v9.5A2.25 2.25 0 003.25 15h9.5A2.25 2.25 0 0015 12.75v-9.5A2.25 2.25 0 0012.75 1H11.5V.75zm-7 2.5V2.5H3.25a.75.75 0 00-.75.75V5h11V3.25a.75.75 0 00-.75-.75H11.5v.75a.75.75 0 01-1.5 0V2.5H6v.75a.75.75 0 01-1.5 0zm9 3.25h-11v6.25c0 .414.336.75.75.75h9.5a.75.75 0 00.75-.75V6.5z' fill-rule='evenodd' clip-rule='evenodd' fill='%233B3D45'/%3e%3c/svg%3e");--token-form-text-input-background-image-data-url-time:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3e%3cg fill='%233B3D45'%3e%3cpath d='M8.5 3.75a.75.75 0 00-1.5 0V8c0 .284.16.544.415.67l2.5 1.25a.75.75 0 10.67-1.34L8.5 7.535V3.75z'/%3e%3cpath d='M8 0a8 8 0 100 16A8 8 0 008 0zM1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0z' fill-rule='evenodd' clip-rule='evenodd'/%3e%3c/g%3e%3c/svg%3e");--token-form-text-input-background-image-data-url-search:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3e%3cg fill='%23656A76'%3e%3cpath d='M7.25 2a5.25 5.25 0 103.144 9.455l2.326 2.325a.75.75 0 101.06-1.06l-2.325-2.326A5.25 5.25 0 007.25 2zM3.5 7.25a3.75 3.75 0 117.5 0 3.75 3.75 0 01-7.5 0z' fill-rule='evenodd' clip-rule='evenodd'/%3e%3c/g%3e%3c/svg%3e");--token-form-text-input-background-image-data-url-search-cancel:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.78 4.28a.75.75 0 00-1.06-1.06L8 6.94 4.28 3.22a.75.75 0 00-1.06 1.06L6.94 8l-3.72 3.72a.75.75 0 101.06 1.06L8 9.06l3.72 3.72a.75.75 0 101.06-1.06L9.06 8l3.72-3.72z'/%3e%3c/svg%3e");--token-form-text-input-background-image-data-url-search-loading:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' %3e%3cg fill='%23656A76' fill-rule='evenodd' clip-rule='evenodd'%3e%3canimateTransform attributeName='transform' type='rotate' dur='0.9s' from='0 8 8' to='360 8 8' repeatCount='indefinite'/%3e%3cpath d='M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8z' opacity='.2'/%3e%3cpath d='M7.25.75A.75.75 0 018 0a8 8 0 018 8 .75.75 0 01-1.5 0A6.5 6.5 0 008 1.5a.75.75 0 01-.75-.75z'/%3e%3c/g%3e%3c/svg%3e");--token-form-toggle-width:32px;--token-form-toggle-height:16px;--token-form-toggle-base-surface-color-default:#f1f2f3;--token-form-toggle-border-radius:3px;--token-form-toggle-border-width:1px;--token-form-toggle-background-image-size:12px;--token-form-toggle-background-image-position-x:2px;--token-form-toggle-background-image-data-url:url("data:image/svg+xml,%3csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M9.78033 3.21967C10.0732 3.51256 10.0732 3.98744 9.78033 4.28033L5.28033 8.78033C4.98744 9.07322 4.51256 9.07322 4.21967 8.78033L2.21967 6.78033C1.92678 6.48744 1.92678 6.01256 2.21967 5.71967C2.51256 5.42678 2.98744 5.42678 3.28033 5.71967L4.75 7.18934L8.71967 3.21967C9.01256 2.92678 9.48744 2.92678 9.78033 3.21967Z' fill='%23FFF'/%3e%3c/svg%3e");--token-form-toggle-background-image-data-url-disabled:url("data:image/svg+xml,%3csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M9.78033 3.21967C10.0732 3.51256 10.0732 3.98744 9.78033 4.28033L5.28033 8.78033C4.98744 9.07322 4.51256 9.07322 4.21967 8.78033L2.21967 6.78033C1.92678 6.48744 1.92678 6.01256 2.21967 5.71967C2.51256 5.42678 2.98744 5.42678 3.28033 5.71967L4.75 7.18934L8.71967 3.21967C9.01256 2.92678 9.48744 2.92678 9.78033 3.21967Z' fill='%238C909C'/%3e%3c/svg%3e");--token-form-toggle-transition-duration:0.2s;--token-form-toggle-transition-timing-function:cubic-bezier(0.68, -0.2, 0.265, 1.15);--token-form-toggle-thumb-size:16px;--token-pagination-nav-control-height:36px;--token-pagination-nav-control-padding-horizontal:12px;--token-pagination-nav-control-focus-inset:4px;--token-pagination-nav-control-icon-spacing:6px;--token-pagination-nav-indicator-height:2px;--token-pagination-nav-indicator-spacing:6px;--token-pagination-child-spacing-vertical:8px;--token-pagination-child-spacing-horizontal:20px;--token-side-nav-wrapper-border-width:1px;--token-side-nav-wrapper-border-color:#656a76;--token-side-nav-wrapper-padding-horizontal:16px;--token-side-nav-wrapper-padding-vertical:16px;--token-side-nav-wrapper-padding-horizontal-minimized:8px;--token-side-nav-wrapper-padding-vertical-minimized:22px;--token-side-nav-toggle-button-border-radius:5px;--token-side-nav-header-home-link-padding:4px;--token-side-nav-header-home-link-logo-size:48px;--token-side-nav-header-home-link-logo-size-minimized:32px;--token-side-nav-header-actions-spacing:8px;--token-side-nav-body-list-margin-vertical:24px;--token-side-nav-body-list-item-height:36px;--token-side-nav-body-list-item-padding-horizontal:8px;--token-side-nav-body-list-item-padding-vertical:4px;--token-side-nav-body-list-item-spacing-vertical:2px;--token-side-nav-body-list-item-content-spacing-horizontal:8px;--token-side-nav-body-list-item-border-radius:5px;--token-side-nav-color-foreground-primary:#dedfe3;--token-side-nav-color-foreground-strong:#fff;--token-side-nav-color-foreground-faint:#8c909c;--token-side-nav-color-surface-primary:#0c0c0e;--token-side-nav-color-surface-interactive-hover:#3b3d45;--token-side-nav-color-surface-interactive-active:#656a76;--token-tabs-tab-height:36px;--token-tabs-tab-padding-horizontal:12px;--token-tabs-tab-padding-vertical:0px;--token-tabs-tab-border-radius:5px;--token-tabs-tab-focus-inset:6px;--token-tabs-tab-gutter:6px;--token-tabs-indicator-height:3px;--token-tabs-indicator-transition-function:cubic-bezier(0.5, 1, 0.89, 1);--token-tabs-indicator-transition-duration:0.6s;--token-tabs-divider-height:1px;--token-tooltip-border-radius:5px;--token-tooltip-color-foreground-primary:var(--token-color-foreground-high-contrast);--token-tooltip-color-surface-primary:var(--token-color-palette-neutral-700);--token-tooltip-focus-offset:-2px;--token-tooltip-max-width:280px;--token-tooltip-padding-horizontal:12px;--token-tooltip-padding-vertical:8px;--token-tooltip-transition-function:cubic-bezier(0.54, 1.5, 0.38, 1.11);--token-typography-font-stack-display:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-font-stack-text:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-font-stack-code:ui-monospace,Menlo,Consolas,monospace;--token-typography-font-weight-regular:400;--token-typography-font-weight-medium:500;--token-typography-font-weight-semibold:600;--token-typography-font-weight-bold:700;--token-typography-display-500-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-display-500-font-size:1.875rem;--token-typography-display-500-line-height:1.2666;--token-typography-display-400-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-display-400-font-size:1.5rem;--token-typography-display-400-line-height:1.3333;--token-typography-display-300-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-display-300-font-size:1.125rem;--token-typography-display-300-line-height:1.3333;--token-typography-display-300-letter-spacing:-0.5px;--token-typography-display-200-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-display-200-font-size:1rem;--token-typography-display-200-line-height:1.5;--token-typography-display-200-letter-spacing:-0.5px;--token-typography-display-100-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-display-100-font-size:0.8125rem;--token-typography-display-100-line-height:1.3846;--token-typography-body-300-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-body-300-font-size:1rem;--token-typography-body-300-line-height:1.5;--token-typography-body-200-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-body-200-font-size:0.875rem;--token-typography-body-200-line-height:1.4286;--token-typography-body-100-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-body-100-font-size:0.8125rem;--token-typography-body-100-line-height:1.3846;--token-typography-code-100-font-family:ui-monospace,Menlo,Consolas,monospace;--token-typography-code-100-font-size:0.8125rem;--token-typography-code-100-line-height:1.23;--token-typography-code-200-font-family:ui-monospace,Menlo,Consolas,monospace;--token-typography-code-200-font-size:0.875rem;--token-typography-code-200-line-height:1.125;--token-typography-code-300-font-family:ui-monospace,Menlo,Consolas,monospace;--token-typography-code-300-font-size:1rem;--token-typography-code-300-line-height:1.25;--hds-app-desktop-breakpoint:1088px;--hds-app-sidenav-width-minimized:48px;--hds-app-sidenav-width-expanded:280px;--hds-app-sidenav-width-fixed:var(--hds-app-sidenav-width-expanded);--hds-app-sidenav-animation-duration:200ms;--hds-app-sidenav-animation-delay:var(--hds-app-sidenav-animation-duration);--hds-app-sidenav-animation-easing:cubic-bezier(0.65, 0, 0.35, 1);--decor-radius-000:0;--decor-radius-100:2px;--decor-radius-200:4px;--decor-radius-250:6px;--decor-radius-300:7px;--decor-radius-999:9999px;--decor-radius-full:100%;--decor-border-000:none;--decor-border-100:1px solid;--decor-border-200:2px solid;--decor-border-300:3px solid;--decor-border-400:4px solid;--icon-alert-triangle-16:url('data:image/svg+xml;charset=UTF-8,');--icon-alert-triangle-24:url('data:image/svg+xml;charset=UTF-8,');--icon-arrow-left-16:url('data:image/svg+xml;charset=UTF-8,');--icon-arrow-left-24:url('data:image/svg+xml;charset=UTF-8,');--icon-arrow-right-16:url('data:image/svg+xml;charset=UTF-8,');--icon-arrow-right-24:url('data:image/svg+xml;charset=UTF-8,');--icon-x-square-fill-16:url('data:image/svg+xml;charset=UTF-8,');--icon-x-square-fill-24:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-down-16:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-down-24:url('data:image/svg+xml;charset=UTF-8,');--icon-clipboard-copy-16:url('data:image/svg+xml;charset=UTF-8,');--icon-clipboard-copy-24:url('data:image/svg+xml;charset=UTF-8,');--icon-docs-16:url('data:image/svg+xml;charset=UTF-8,');--icon-docs-24:url('data:image/svg+xml;charset=UTF-8,');--icon-external-link-16:url('data:image/svg+xml;charset=UTF-8,');--icon-external-link-24:url('data:image/svg+xml;charset=UTF-8,');--icon-file-16:url('data:image/svg+xml;charset=UTF-8,');--icon-file-24:url('data:image/svg+xml;charset=UTF-8,');--icon-folder-16:url('data:image/svg+xml;charset=UTF-8,');--icon-folder-24:url('data:image/svg+xml;charset=UTF-8,');--icon-activity-16:url('data:image/svg+xml;charset=UTF-8,');--icon-activity-24:url('data:image/svg+xml;charset=UTF-8,');--icon-help-16:url('data:image/svg+xml;charset=UTF-8,');--icon-help-24:url('data:image/svg+xml;charset=UTF-8,');--icon-learn-16:url('data:image/svg+xml;charset=UTF-8,');--icon-learn-24:url('data:image/svg+xml;charset=UTF-8,');--icon-github-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-github-color-24:url('data:image/svg+xml;charset=UTF-8,');--icon-google-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-google-color-24:url('data:image/svg+xml;charset=UTF-8,');--icon-kubernetes-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-kubernetes-color-24:url('data:image/svg+xml;charset=UTF-8,');--icon-menu-16:url('data:image/svg+xml;charset=UTF-8,');--icon-menu-24:url('data:image/svg+xml;charset=UTF-8,');--icon-minus-square-16:url('data:image/svg+xml;charset=UTF-8,');--icon-minus-square-24:url('data:image/svg+xml;charset=UTF-8,');--icon-more-horizontal-16:url('data:image/svg+xml;charset=UTF-8,');--icon-more-horizontal-24:url('data:image/svg+xml;charset=UTF-8,');--icon-globe-16:url('data:image/svg+xml;charset=UTF-8,');--icon-globe-24:url('data:image/svg+xml;charset=UTF-8,');--icon-search-16:url('data:image/svg+xml;charset=UTF-8,');--icon-search-24:url('data:image/svg+xml;charset=UTF-8,');--icon-star-16:url('data:image/svg+xml;charset=UTF-8,');--icon-star-24:url('data:image/svg+xml;charset=UTF-8,');--icon-org-16:url('data:image/svg+xml;charset=UTF-8,');--icon-org-24:url('data:image/svg+xml;charset=UTF-8,');--icon-user-16:url('data:image/svg+xml;charset=UTF-8,');--icon-user-24:url('data:image/svg+xml;charset=UTF-8,');--icon-users-16:url('data:image/svg+xml;charset=UTF-8,');--icon-users-24:url('data:image/svg+xml;charset=UTF-8,');--icon-alert-circle-16:url('data:image/svg+xml;charset=UTF-8,');--icon-alert-circle-24:url('data:image/svg+xml;charset=UTF-8,');--icon-check-16:url('data:image/svg+xml;charset=UTF-8,');--icon-check-24:url('data:image/svg+xml;charset=UTF-8,');--icon-check-circle-16:url('data:image/svg+xml;charset=UTF-8,');--icon-check-circle-24:url('data:image/svg+xml;charset=UTF-8,');--icon-check-circle-fill-16:url('data:image/svg+xml;charset=UTF-8,');--icon-check-circle-fill-24:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-left-16:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-left-24:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-right-16:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-right-24:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-up-16:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-up-24:url('data:image/svg+xml;charset=UTF-8,');--icon-delay-16:url('data:image/svg+xml;charset=UTF-8,');--icon-delay-24:url('data:image/svg+xml;charset=UTF-8,');--icon-docs-link-16:url('data:image/svg+xml;charset=UTF-8,');--icon-docs-link-24:url('data:image/svg+xml;charset=UTF-8,');--icon-eye-16:url('data:image/svg+xml;charset=UTF-8,');--icon-eye-24:url('data:image/svg+xml;charset=UTF-8,');--icon-eye-off-16:url('data:image/svg+xml;charset=UTF-8,');--icon-eye-off-24:url('data:image/svg+xml;charset=UTF-8,');--icon-file-text-16:url('data:image/svg+xml;charset=UTF-8,');--icon-file-text-24:url('data:image/svg+xml;charset=UTF-8,');--icon-gateway-16:url('data:image/svg+xml;charset=UTF-8,');--icon-gateway-24:url('data:image/svg+xml;charset=UTF-8,');--icon-git-commit-16:url('data:image/svg+xml;charset=UTF-8,');--icon-git-commit-24:url('data:image/svg+xml;charset=UTF-8,');--icon-hexagon-16:url('data:image/svg+xml;charset=UTF-8,');--icon-hexagon-24:url('data:image/svg+xml;charset=UTF-8,');--icon-history-16:url('data:image/svg+xml;charset=UTF-8,');--icon-history-24:url('data:image/svg+xml;charset=UTF-8,');--icon-info-16:url('data:image/svg+xml;charset=UTF-8,');--icon-info-24:url('data:image/svg+xml;charset=UTF-8,');--icon-layers-16:url('data:image/svg+xml;charset=UTF-8,');--icon-layers-24:url('data:image/svg+xml;charset=UTF-8,');--icon-loading-16:url('data:image/svg+xml;charset=UTF-8,');--icon-loading-24:url('data:image/svg+xml;charset=UTF-8,');--icon-network-alt-16:url('data:image/svg+xml;charset=UTF-8,');--icon-network-alt-24:url('data:image/svg+xml;charset=UTF-8,');--icon-path-16:url('data:image/svg+xml;charset=UTF-8,');--icon-path-24:url('data:image/svg+xml;charset=UTF-8,');--icon-running-16:url('data:image/svg+xml;charset=UTF-8,');--icon-running-24:url('data:image/svg+xml;charset=UTF-8,');--icon-skip-16:url('data:image/svg+xml;charset=UTF-8,');--icon-skip-24:url('data:image/svg+xml;charset=UTF-8,');--icon-socket-16:url('data:image/svg+xml;charset=UTF-8,');--icon-socket-24:url('data:image/svg+xml;charset=UTF-8,');--icon-star-circle-16:url('data:image/svg+xml;charset=UTF-8,');--icon-star-circle-24:url('data:image/svg+xml;charset=UTF-8,');--icon-star-fill-16:url('data:image/svg+xml;charset=UTF-8,');--icon-star-fill-24:url('data:image/svg+xml;charset=UTF-8,');--icon-tag-16:url('data:image/svg+xml;charset=UTF-8,');--icon-tag-24:url('data:image/svg+xml;charset=UTF-8,');--icon-x-16:url('data:image/svg+xml;charset=UTF-8,');--icon-x-24:url('data:image/svg+xml;charset=UTF-8,');--icon-x-circle-16:url('data:image/svg+xml;charset=UTF-8,');--icon-x-circle-24:url('data:image/svg+xml;charset=UTF-8,');--icon-x-square-16:url('data:image/svg+xml;charset=UTF-8,');--icon-x-square-24:url('data:image/svg+xml;charset=UTF-8,');--icon-cloud-cross-16:url('data:image/svg+xml;charset=UTF-8,');--icon-loading-motion-16:url('data:image/svg+xml;charset=UTF-8,');--icon-auth0-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-auth0-color-24:url('data:image/svg+xml;charset=UTF-8,');--icon-logo-ember-circle-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-logo-glimmer-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-logo-jwt-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-microsoft-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-microsoft-color-24:url('data:image/svg+xml;charset=UTF-8,');--icon-logo-oidc-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-okta-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-okta-color-24:url('data:image/svg+xml;charset=UTF-8,');--icon-mesh-16:url('data:image/svg+xml;charset=UTF-8,');--icon-mesh-24:url('data:image/svg+xml;charset=UTF-8,');--icon-port-16:url('data:image/svg+xml;charset=UTF-8,');--icon-protocol-16:url('data:image/svg+xml;charset=UTF-8,');--icon-redirect-16:url('data:image/svg+xml;charset=UTF-8,');--icon-redirect-24:url('data:image/svg+xml;charset=UTF-8,');--icon-search-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-sort-desc-16:url('data:image/svg+xml;charset=UTF-8,');--icon-sort-desc-24:url('data:image/svg+xml;charset=UTF-8,');--icon-union-16:url('data:image/svg+xml;charset=UTF-8,');--chrome-width:280px;--chrome-height:64px;--typo-action:var(--token-color-foreground-action);--decor-error:var(--token-color-foreground-critical);--typo-contrast:var(--token-color-hashicorp-brand);--syntax-light-grey:#dde3e7;--syntax-light-gray:#a4a4a4;--syntax-light-grey-blue:#6c7b81;--syntax-dark-grey:#788290;--syntax-faded-gray:#eaeaea;--syntax-atlas:#127eff;--syntax-vagrant:#2f88f7;--syntax-consul:#69499a;--syntax-terraform:#822ff7;--syntax-serf:#dd4e58;--syntax-packer:#1ddba3;--syntax-gray:lighten(#000, 89%);--syntax-red:#ff3d3d;--syntax-green:#39b54a;--syntax-dark-gray:#535f73;--syntax-gutter-grey:#2a2f36;--syntax-yellow:var(--token-color-vault-brand);--horizontal-kv-list-separator-width:18px;--horizontal-kv-list-key-separator:":";--horizontal-kv-list-key-wrapper-start:"(";--horizontal-kv-list-key-wrapper-end:")";--csv-list-separator:",";--icon-loading:icon-loading-motion;--color-info:var(--token-color-foreground-action);--color-alert:var(--token-color-palette-amber-200)}.hds-border-primary{border:1px solid var(--token-color-border-primary)}.hds-border-faint{border:1px solid var(--token-color-border-faint)}.hds-border-strong{border:1px solid var(--token-color-border-strong)}.hds-border-action{border:1px solid var(--token-color-border-action)}.hds-border-highlight{border:1px solid var(--token-color-border-highlight)}.hds-border-success{border:1px solid var(--token-color-border-success)}.hds-border-warning{border:1px solid var(--token-color-border-warning)}.hds-border-critical{border:1px solid var(--token-color-border-critical)}.hds-foreground-strong{color:var(--token-color-foreground-strong)}.hds-foreground-primary{color:var(--token-color-foreground-primary)}.hds-foreground-faint{color:var(--token-color-foreground-faint)}.hds-foreground-high-contrast{color:var(--token-color-foreground-high-contrast)}.hds-foreground-disabled{color:var(--token-color-foreground-disabled)}.hds-foreground-action{color:var(--token-color-foreground-action)}.hds-foreground-action-hover{color:var(--token-color-foreground-action-hover)}.hds-foreground-action-active{color:var(--token-color-foreground-action-active)}.hds-foreground-highlight{color:var(--token-color-foreground-highlight)}.hds-foreground-highlight-on-surface{color:var(--token-color-foreground-highlight-on-surface)}.hds-foreground-highlight-high-contrast{color:var(--token-color-foreground-highlight-high-contrast)}.hds-foreground-success{color:var(--token-color-foreground-success)}.hds-foreground-success-on-surface{color:var(--token-color-foreground-success-on-surface)}.hds-foreground-success-high-contrast{color:var(--token-color-foreground-success-high-contrast)}.hds-foreground-warning{color:var(--token-color-foreground-warning)}.hds-foreground-warning-on-surface{color:var(--token-color-foreground-warning-on-surface)}.hds-foreground-warning-high-contrast{color:var(--token-color-foreground-warning-high-contrast)}.hds-foreground-critical{color:var(--token-color-foreground-critical)}.hds-foreground-critical-on-surface{color:var(--token-color-foreground-critical-on-surface)}.hds-foreground-critical-high-contrast{color:var(--token-color-foreground-critical-high-contrast)}.hds-page-primary{background-color:var(--token-color-page-primary)}.hds-page-faint{background-color:var(--token-color-page-faint)}.hds-surface-primary{background-color:var(--token-color-surface-primary)}.hds-surface-faint{background-color:var(--token-color-surface-faint)}.hds-badge--color-neutral.hds-badge--type-filled,.hds-surface-strong{background-color:var(--token-color-surface-strong)}.hds-surface-interactive{background-color:var(--token-color-surface-interactive)}.hds-surface-interactive-hover{background-color:var(--token-color-surface-interactive-hover)}.hds-surface-interactive-active{background-color:var(--token-color-surface-interactive-active)}.hds-surface-interactive-disabled{background-color:var(--token-color-surface-interactive-disabled)}.hds-surface-action{background-color:var(--token-color-surface-action)}.hds-surface-highlight{background-color:var(--token-color-surface-highlight)}.hds-surface-success{background-color:var(--token-color-surface-success)}.hds-surface-warning{background-color:var(--token-color-surface-warning)}.hds-surface-critical{background-color:var(--token-color-surface-critical)}.hds-elevation-low{box-shadow:var(--token-elevation-low-box-shadow)}.hds-elevation-mid{box-shadow:var(--token-elevation-mid-box-shadow)}.hds-elevation-high{box-shadow:var(--token-elevation-high-box-shadow)}.hds-elevation-higher{box-shadow:var(--token-elevation-higher-box-shadow)}.consul-server-list a:hover div,.hds-elevation-overlay,.modal-dialog [role=document]{box-shadow:var(--token-elevation-overlay-box-shadow)}.hds-surface-inset{box-shadow:var(--token-surface-inset-box-shadow)}.hds-surface-base{box-shadow:var(--token-surface-base-box-shadow)}.hds-surface-low{box-shadow:var(--token-surface-low-box-shadow)}.hds-accordion-item.hds-accordion-item--does-not-contain-interactive,.hds-surface-mid{box-shadow:var(--token-surface-mid-box-shadow)}.hds-surface-high{box-shadow:var(--token-surface-high-box-shadow)}.hds-surface-higher{box-shadow:var(--token-surface-higher-box-shadow)}.hds-surface-overlay{box-shadow:var(--token-surface-overlay-box-shadow)}.hds-focus-ring-action-box-shadow{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-focus-ring-critical-box-shadow{box-shadow:var(--token-focus-ring-critical-box-shadow)}.hds-font-family-sans-display{font-family:var(--token-typography-font-stack-display)}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive,.hds-badge-count,.hds-badge__text,.hds-breadcrumb__text,.hds-button,.hds-font-family-sans-text{font-family:var(--token-typography-font-stack-text)}.hds-font-family-mono-code{font-family:var(--token-typography-font-stack-code)}#metrics-container .sparkline-wrapper .tooltip,.app-view h1 span.kind-proxy,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password],.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text],.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea,.app-view>div form:not(.filter-bar) [role=radiogroup] label>em,.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.app-view>div form:not(.filter-bar) [role=radiogroup] label>strong,.auth-form em,.consul-auth-method-type,.consul-auth-method-view section,.consul-external-source,.consul-health-check-list .health-check-output dd em,.consul-health-check-list .health-check-output dl>dd,.consul-intention-fieldsets .permissions>button,.consul-intention-list td strong,.consul-intention-list td.destination em,.consul-intention-list td.permissions,.consul-intention-list td.source em,.consul-intention-permission-header-list>ul>li dd,.consul-intention-permission-list strong,.consul-intention-permission-list>ul>li dd,.consul-intention-search-bar li button span,.consul-kind,.consul-peer-search-bar li button span,.consul-server-card .health-status+dd,.consul-source,.consul-transparent-proxy,.disclosure-menu [aria-expanded]~*>div,.discovery-chain .resolvers>header>*,.discovery-chain .route-card header dt,.discovery-chain .route-card>header ul li,.discovery-chain .routes>header>*,.discovery-chain .splitters>header>*,.empty-state header :nth-child(2),.empty-state p,.empty-state>ul>li>*,.empty-state>ul>li>::before,.empty-state>ul>li>label>button,.empty-state>ul>li>label>button::before,.has-error>strong,.has-error>strong::before,.hds-font-weight-regular,.informed-action p,.leader,.menu-panel>div,.modal-dialog [role=document] .type-password [type=password],.modal-dialog [role=document] .type-password [type=text],.modal-dialog [role=document] .type-password textarea,.modal-dialog [role=document] .type-password>em,.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-password>strong,.modal-dialog [role=document] .type-select [type=password],.modal-dialog [role=document] .type-select [type=text],.modal-dialog [role=document] .type-select textarea,.modal-dialog [role=document] .type-select>em,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] .type-select>strong,.modal-dialog [role=document] .type-text [type=password],.modal-dialog [role=document] .type-text [type=text],.modal-dialog [role=document] .type-text textarea,.modal-dialog [role=document] .type-text>em,.modal-dialog [role=document] .type-text>span,.modal-dialog [role=document] .type-text>strong,.modal-dialog [role=document] [role=radiogroup] label [type=password],.modal-dialog [role=document] [role=radiogroup] label [type=text],.modal-dialog [role=document] [role=radiogroup] label textarea,.modal-dialog [role=document] [role=radiogroup] label>em,.modal-dialog [role=document] [role=radiogroup] label>span,.modal-dialog [role=document] [role=radiogroup] label>strong,.modal-dialog [role=document] form button+em,.modal-dialog [role=document] label a[rel*=help],.modal-dialog [role=document] p,.modal-dialog [role=document] table td,.modal-dialog [role=document] table td p,.modal-dialog [role=document] table th em,.more-popover-menu>[type=checkbox]+label+div>div,.oidc-select button.reset,.oidc-select label [type=password],.oidc-select label [type=text],.oidc-select label textarea,.oidc-select label>em,.oidc-select label>span,.oidc-select label>strong,.popover-menu>[type=checkbox]+label+div>div,.search-bar-status li:not(.remove-all),.tippy-box[data-theme~=tooltip] .tippy-content,.topology-metrics-source-type,.topology-metrics-status-error,.topology-metrics-status-loader,.type-toggle [type=password],.type-toggle [type=text],.type-toggle textarea,.type-toggle>em,.type-toggle>span,.type-toggle>strong,body,html[data-route^="dc.acls.index"] main td strong,main .type-password [type=password],main .type-password [type=text],main .type-password textarea,main .type-password>em,main .type-password>span,main .type-password>strong,main .type-select [type=password],main .type-select [type=text],main .type-select textarea,main .type-select>em,main .type-select>span,main .type-select>strong,main .type-text [type=password],main .type-text [type=text],main .type-text textarea,main .type-text>em,main .type-text>span,main .type-text>strong,main form button+em,main label a[rel*=help],main p,main table td,main table td p,main table th em,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl,section[data-route="dc.show.serverstatus"] .server-failure-tolerance dt,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em,section[data-route="dc.show.license"] .validity dl,span.label,span.policy-node-identity,span.policy-service-identity,table.has-actions tr>.actions>[type=checkbox]+label+div>div,table.with-details tr>.actions>[type=checkbox]+label+div>div{font-weight:400}.app-view h1 em,.consul-exposed-path-list>ul>li .copy-button button,.consul-exposed-path-list>ul>li>.header,.consul-lock-session-list ul>li:not(:first-child) .copy-button button,.consul-lock-session-list ul>li:not(:first-child)>.header,.consul-peer-search-bar .value-active span,.consul-peer-search-bar .value-deleting span,.consul-peer-search-bar .value-establishing span,.consul-peer-search-bar .value-failing span,.consul-peer-search-bar .value-pending span,.consul-peer-search-bar .value-terminated span,.consul-upstream-instance-list li .copy-button button,.consul-upstream-instance-list li>.header,.disclosure-menu [aria-expanded]~* [role=separator],.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.hds-font-weight-medium,.informed-action>ul>li>*,.list-collection>ul>li:not(:first-child) .copy-button button,.list-collection>ul>li:not(:first-child)>.header,.menu-panel [role=separator],.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.more-popover-menu>[type=checkbox]+label+div [role=separator],.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.peerings-badge .peerings-badge__text,.popover-menu>[type=checkbox]+label+div [role=separator],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.tab-nav,main header nav:first-child ol li>*,table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div [role=separator],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{font-weight:500}#downstream-container .topology-metrics-card p,#metrics-container .sparkline-wrapper .tooltip .sparkline-time,#metrics-container div:first-child,#upstream-container .topology-metrics-card p,.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.code-editor .toolbar-container .toolbar .title,.consul-auth-method-binding-list h2,.consul-auth-method-nspace-list thead td,.consul-auth-method-view section h2,.consul-auth-method-view section table thead td,.consul-bucket-list .service+dd,.consul-health-check-list .health-check-output dt,.consul-health-check-list .health-check-output header>*,.consul-intention-action-warn-modal button.dangerous,.consul-intention-list td.destination,.consul-intention-list td.source,.consul-intention-permission-form h2,.consul-intention-view h2,.consul-peer-form-generate li::after,.consul-server-card .name+dd,.copy-button button,.definition-table dt,.empty-state header :first-child,.hds-font-weight-semibold,.informed-action header,.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] .type-text>span,.modal-dialog [role=document] [role=radiogroup] label>span,.modal-dialog [role=document] form h2,.modal-dialog [role=document] table caption,.modal-dialog [role=document] table td:first-child,.modal-dialog [role=document] table td:first-child p,.modal-dialog [role=document] table th,.modal-dialog [role=document]>header>:not(button),.modal-dialog-body h2,.oidc-select label>span,.popover-select label>*,.radio-card header,.sparkline-key h3,.topology-notices button,.type-sort.popover-select label>*,.type-toggle label span,.type-toggle>span,.warning.modal-dialog header>:not(label),fieldset>header,html[data-route^="dc.services.instance.metadata"] .tab-section section h2,html[data-route^="dc.kv.edit"] h2,main .type-password>span,main .type-select>span,main .type-text>span,main form h2,main table caption,main table td:first-child,main table td:first-child p,main table th,section[data-route="dc.show.serverstatus"] .redundancy-zones h3,section[data-route="dc.show.serverstatus"] .server-failure-tolerance dd,section[data-route="dc.show.serverstatus"] h2,section[data-route="dc.show.serverstatus"] h3,section[data-route="dc.show.license"] aside header>:first-child,section[data-route="dc.show.license"] h2,span.label,strong{font-weight:600}.discovery-chain .route-card header:not(.short) dd,.discovery-chain .route-card section dt,.discovery-chain .splitter-card>header,.hds-font-weight-bold,h1{font-weight:700}.hds-typography-display-500,h1{font-family:var(--token-typography-display-500-font-family);font-size:var(--token-typography-display-500-font-size);line-height:var(--token-typography-display-500-line-height);margin:0;padding:0}.consul-auth-method-binding-list h2,.consul-auth-method-view section h2,.consul-intention-permission-form h2,.consul-intention-view h2,.empty-state header :first-child,.hds-typography-display-400,.modal-dialog [role=document] form h2,.modal-dialog [role=document]>header>:not(button),.modal-dialog-body h2,html[data-route^="dc.kv.edit"] h2,main form h2,section[data-route="dc.show.license"] h2{font-family:var(--token-typography-display-400-font-family);font-size:var(--token-typography-display-400-font-size);line-height:var(--token-typography-display-400-line-height);margin:0;padding:0}#downstream-container .topology-metrics-card p,#upstream-container .topology-metrics-card p,.consul-exposed-path-list>ul>li>.header,.consul-health-check-list .health-check-output header>*,.consul-lock-session-list ul>li:not(:first-child)>.header,.consul-upstream-instance-list li>.header,.hds-typography-display-300,.list-collection>ul>li:not(:first-child)>.header,.sparkline-key h3,.warning.modal-dialog header>:not(label),html[data-route^="dc.services.instance.metadata"] .tab-section section h2,section[data-route="dc.show.serverstatus"] .redundancy-zones h3,section[data-route="dc.show.serverstatus"] .server-failure-tolerance dd,section[data-route="dc.show.serverstatus"] h2,section[data-route="dc.show.serverstatus"] h3,section[data-route="dc.show.license"] aside header>:first-child{font-family:var(--token-typography-display-300-font-family);font-size:var(--token-typography-display-300-font-size);line-height:var(--token-typography-display-300-line-height);margin:0;padding:0}.consul-server-card .name+dd,.hds-side-nav .ember-a11y-refocus-skip-link,.hds-typography-display-200{font-size:var(--token-typography-display-200-font-size);line-height:var(--token-typography-display-200-line-height)}.consul-server-card .name+dd,.hds-typography-display-200{font-family:var(--token-typography-display-200-font-family);margin:0;padding:0}.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.consul-intention-list td.destination,.consul-intention-list td.source,.definition-table dt,.hds-typography-display-100,.informed-action header,.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] .type-text>span,.modal-dialog [role=document] [role=radiogroup] label>span,.modal-dialog [role=document] table caption,.modal-dialog [role=document] table th,.oidc-select label>span,.radio-card header,.type-toggle>span,fieldset>header,main .type-password>span,main .type-select>span,main .type-text>span,main table caption,main table th{font-family:var(--token-typography-display-100-font-family);font-size:var(--token-typography-display-100-font-size);line-height:var(--token-typography-display-100-line-height);margin:0;padding:0}#metrics-container div:first-child,.hds-typography-body-300,section[data-route="dc.show.license"] .validity dl{font-family:var(--token-typography-body-300-font-family);font-size:var(--token-typography-body-300-font-size);line-height:var(--token-typography-body-300-line-height);margin:0;padding:0}#metrics-container .sparkline-wrapper .tooltip,#metrics-container .sparkline-wrapper .tooltip .sparkline-time,.app-view h1 em,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password],.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text],.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea,.code-editor .toolbar-container .toolbar .title,.consul-auth-method-nspace-list thead td,.consul-auth-method-view section,.consul-auth-method-view section table thead td,.consul-external-source,.consul-health-check-list .health-check-output dl>dd,.consul-health-check-list .health-check-output dt,.consul-kind,.consul-peer-form-generate li::after,.consul-source,.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.empty-state>ul>li>::before,.empty-state>ul>li>label>button::before,.has-error>strong::before,.hds-typography-body-200,.informed-action>ul>li>*,.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.modal-dialog [role=document] .type-password [type=password],.modal-dialog [role=document] .type-password [type=text],.modal-dialog [role=document] .type-password textarea,.modal-dialog [role=document] .type-select [type=password],.modal-dialog [role=document] .type-select [type=text],.modal-dialog [role=document] .type-select textarea,.modal-dialog [role=document] .type-text [type=password],.modal-dialog [role=document] .type-text [type=text],.modal-dialog [role=document] .type-text textarea,.modal-dialog [role=document] [role=radiogroup] label [type=password],.modal-dialog [role=document] [role=radiogroup] label [type=text],.modal-dialog [role=document] [role=radiogroup] label textarea,.modal-dialog [role=document] table td,.modal-dialog [role=document] table td p,.modal-dialog [role=document] table td:first-child,.modal-dialog [role=document] table td:first-child p,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.oidc-select label [type=password],.oidc-select label [type=text],.oidc-select label textarea,.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.tab-nav,.topology-metrics-status-error,.topology-metrics-status-loader,.type-toggle [type=password],.type-toggle [type=text],.type-toggle label span,.type-toggle textarea,body,main .type-password [type=password],main .type-password [type=text],main .type-password textarea,main .type-select [type=password],main .type-select [type=text],main .type-select textarea,main .type-text [type=password],main .type-text [type=text],main .type-text textarea,main header nav:first-child ol li>*,main table td,main table td p,main table td:first-child,main table td:first-child p,strong,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{font-family:var(--token-typography-body-200-font-family);font-size:var(--token-typography-body-200-font-size);line-height:var(--token-typography-body-200-line-height);margin:0;padding:0}.app-view h1 span.kind-proxy,.app-view>div form:not(.filter-bar) [role=radiogroup] label>em,.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.auth-form em,.consul-exposed-path-list>ul>li .copy-button button,.consul-intention-action-warn-modal button.dangerous,.consul-intention-fieldsets .permissions>button,.consul-intention-list td.permissions,.consul-intention-permission-header-list>ul>li dd,.consul-intention-permission-list>ul>li dd,.consul-lock-session-list ul>li:not(:first-child) .copy-button button,.consul-server-card .health-status+dd,.consul-upstream-instance-list li .copy-button button,.copy-button button,.disclosure-menu [aria-expanded]~* [role=separator],.disclosure-menu [aria-expanded]~*>div,.discovery-chain .resolvers>header>*,.discovery-chain .routes>header>*,.discovery-chain .splitters>header>*,.empty-state header :nth-child(2),.empty-state p,.empty-state>ul>li>*,.empty-state>ul>li>label>button,.has-error>strong,.hds-typography-body-100,.informed-action p,.list-collection>ul>li:not(:first-child) .copy-button button,.menu-panel [role=separator],.menu-panel>div,.modal-dialog [role=document] .type-password>em,.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-select>em,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] .type-text>em,.modal-dialog [role=document] .type-text>span,.modal-dialog [role=document] [role=radiogroup] label>em,.modal-dialog [role=document] [role=radiogroup] label>span,.modal-dialog [role=document] form button+em,.modal-dialog [role=document] p,.more-popover-menu>[type=checkbox]+label+div [role=separator],.more-popover-menu>[type=checkbox]+label+div>div,.oidc-select button.reset,.oidc-select label>em,.oidc-select label>span,.peerings-badge .peerings-badge__text,.popover-menu>[type=checkbox]+label+div [role=separator],.popover-menu>[type=checkbox]+label+div>div,.popover-select label>*,.tippy-box[data-theme~=tooltip] .tippy-content,.topology-notices button,.type-sort.popover-select label>*,.type-toggle>em,.type-toggle>span,main .type-password>em,main .type-password>span,main .type-select>em,main .type-select>span,main .type-text>em,main .type-text>span,main form button+em,main p,section[data-route="dc.show.serverstatus"] .server-failure-tolerance dt,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em,span.label,table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator],table.has-actions tr>.actions>[type=checkbox]+label+div>div,table.with-details tr>.actions>[type=checkbox]+label+div [role=separator],table.with-details tr>.actions>[type=checkbox]+label+div>div{font-family:var(--token-typography-body-100-font-family);font-size:var(--token-typography-body-100-font-size);line-height:var(--token-typography-body-100-line-height);margin:0;padding:0}.hds-typography-code-100{font-family:var(--token-typography-code-100-font-family);font-size:var(--token-typography-code-100-font-size);line-height:var(--token-typography-code-100-line-height);margin:0;padding:0}.hds-typography-code-200{font-family:var(--token-typography-code-200-font-family);font-size:var(--token-typography-code-200-font-size);line-height:var(--token-typography-code-200-line-height);margin:0;padding:0}.hds-typography-code-300{font-family:var(--token-typography-code-300-font-family);font-size:var(--token-typography-code-300-font-size);line-height:var(--token-typography-code-300-line-height);margin:0;padding:0}.hds-accordion{display:flex;gap:12px}.hds-accordion-item{background:var(--token-color-surface-primary);border-radius:6px}.hds-accordion-item.hds-accordion-item--does-not-contain-interactive.mock-hover,.hds-accordion-item.hds-accordion-item--does-not-contain-interactive:hover{box-shadow:var(--token-surface-high-box-shadow)}.hds-accordion-item.hds-accordion-item--contains-interactive{box-shadow:var(--token-surface-base-box-shadow)}.hds-accordion-item__toggle{position:relative;display:flex;gap:12px;align-items:center;padding:16px 16px 16px 12px}.hds-accordion-item__button{padding:0}.hds-accordion-item__button:hover{cursor:pointer}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive{outline-style:solid;outline-color:transparent;isolation:isolate;position:static;margin:-1px 0;color:var(--token-color-foreground-primary);background:0 0;border:1px solid transparent}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive::before{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;border-radius:5px;content:""}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive.mock-focus::before,.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive:focus:not(:focus-visible)::before{box-shadow:none}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive:focus-visible::before,.hds-breadcrumb__link.mock-focus,.hds-breadcrumb__link:focus{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive.mock-focus.mock-active::before,.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive:focus:active::before{box-shadow:none}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive::after{position:absolute;display:block;border-radius:6px;content:"";inset:0}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive{position:relative;display:flex;gap:.375rem;align-items:center;justify-content:center;font-weight:var(--token-typography-font-weight-regular);text-decoration:none;border:1px solid transparent;border-radius:5px;outline-style:solid;outline-color:transparent;isolation:isolate;width:24px;height:24px;color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-strong);box-shadow:var(--token-elevation-low-box-shadow)}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive.mock-focus::before,.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive:focus::before{position:absolute;top:-4px;right:-4px;bottom:-4px;left:-4px;z-index:-1;border:3px solid transparent;border-radius:8px;content:""}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive.mock-hover,.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive:hover{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-primary);border-color:var(--token-color-border-strong);cursor:pointer}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive.mock-focus,.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive:focus{box-shadow:none;color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-focus-action-internal)}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive.mock-focus::before,.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive:focus::before{border-color:var(--token-color-focus-action-external)}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive.mock-active,.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive:active{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong);box-shadow:none}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive.mock-active::before,.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive:active::before{border-color:transparent}.hds-accordion-item__button.hds-accordion-item__button--is-open .flight-icon-chevron-down{transform:rotate(-180deg)}.hds-accordion-item__toggle-content{flex:1}.hds-accordion-item__content{padding:4px 16px 16px}.hds-alert{display:flex;align-items:flex-start}.hds-alert__icon{flex:none;width:20px;height:20px;margin-right:12px}.hds-alert__content{flex:1 1 auto}.hds-alert__text{display:flex;flex-direction:column;gap:4px;justify-content:center;color:var(--token-color-foreground-warning-on-surface)}.hds-alert__description{word-break:break-word}.hds-alert__description strong{font-weight:var(--token-typography-font-weight-semibold)}.hds-alert__description code,.hds-alert__description pre{display:inline;padding:1px 5px;font-size:.9em;font-family:var(--token-typography-code-100-font-family);line-height:1em;background-color:var(--token-color-surface-primary);border:1px solid var(--token-color-palette-neutral-200);border-radius:5px}.hds-alert__description a:not([class*=hds-]){color:var(--token-color-foreground-strong)}.hds-alert__description a:not([class*=hds-]):focus,.hds-alert__description a:not([class*=hds-]):focus-visible{text-decoration:none;outline:var(--token-color-focus-action-internal) solid 2px;outline-offset:1px}.hds-alert__description a:not([class*=hds-]):hover{color:var(--token-color-foreground-primary)}.hds-alert--color-neutral .hds-alert__icon,.hds-alert__description a:not([class*=hds-]):active{color:var(--token-color-foreground-faint)}.hds-alert__actions{display:flex;gap:16px;align-items:center}.hds-alert__actions>*{margin-top:16px}.hds-alert__dismiss{margin-top:2px;margin-left:16px}.hds-alert--type-compact .hds-alert__dismiss{margin-top:1px}.hds-alert--type-page{padding:16px 48px}.hds-alert--type-inline{padding:16px;border-style:solid;border-width:1px;border-radius:6px}.hds-alert--type-compact .hds-alert__icon{width:14px;height:14px;margin-top:2px;margin-right:8px}.hds-alert--type-compact .hds-alert__title{display:none}.hds-alert--type-compact .hds-alert__title+.hds-alert__description{margin-top:0}.hds-alert--color-neutral.hds-alert--type-page{background-color:var(--token-color-surface-faint);box-shadow:0 1px 0 0 var(--token-color-palette-alpha-300)}.hds-alert--color-neutral.hds-alert--type-inline{background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-strong)}.hds-alert--color-neutral .hds-alert__title{color:var(--token-color-foreground-primary)}.hds-alert--color-highlight.hds-alert--type-page{background-color:var(--token-color-surface-highlight);box-shadow:0 1px 0 0 var(--token-color-border-highlight)}.hds-alert--color-highlight.hds-alert--type-inline{background-color:var(--token-color-surface-highlight);border-color:var(--token-color-border-highlight)}.hds-alert--color-highlight .hds-alert__icon,.hds-alert--color-highlight .hds-alert__title{color:var(--token-color-foreground-highlight-on-surface)}.hds-alert--color-success.hds-alert--type-page{background-color:var(--token-color-surface-success);box-shadow:0 1px 0 0 var(--token-color-border-success)}.hds-alert--color-success.hds-alert--type-inline{background-color:var(--token-color-surface-success);border-color:var(--token-color-border-success)}.hds-alert--color-success .hds-alert__icon,.hds-alert--color-success .hds-alert__title{color:var(--token-color-foreground-success-on-surface)}.hds-alert--color-warning.hds-alert--type-page{background-color:var(--token-color-surface-warning);box-shadow:0 1px 0 0 var(--token-color-border-warning)}.hds-alert--color-warning.hds-alert--type-inline{background-color:var(--token-color-surface-warning);border-color:var(--token-color-border-warning)}.hds-alert--color-warning .hds-alert__icon,.hds-alert--color-warning .hds-alert__title{color:var(--token-color-foreground-warning-on-surface)}.hds-alert--color-critical.hds-alert--type-page{background-color:var(--token-color-surface-critical);box-shadow:0 1px 0 0 var(--token-color-border-critical)}.hds-alert--color-critical.hds-alert--type-inline{background-color:var(--token-color-surface-critical);border-color:var(--token-color-border-critical)}.hds-alert--color-critical .hds-alert__icon,.hds-alert--color-critical .hds-alert__title{color:var(--token-color-foreground-critical-on-surface)}.hds-app-footer{display:flex;flex-wrap:wrap;gap:24px;justify-content:flex-end;padding:24px;color:var(--app-footer-foreground-color);border-top:1px solid var(--app-footer-border-top-color)}.hds-app-footer__list:not(:has(li)){display:none}.hds-app-footer__legal-links,.hds-app-footer__list{display:flex;flex-wrap:wrap;gap:24px;align-items:center;justify-content:flex-end;width:-moz-fit-content;width:fit-content;min-width:0;margin:0;padding:0;list-style-type:none}.hds-app-footer__status-link.hds-link-inline--icon-leading>.hds-link-inline__icon{margin-right:6px}.hds-app-footer__status-link .flight-icon{fill:var(--hds-app-footer-status-icon-color,currentColor)}.hds-app-footer__status-link--operational .flight-icon{fill:var(--app-footer-status-link-icon-operational-color)}.hds-app-footer__status-link--degraded .flight-icon{fill:var(--app-footer-status-link-icon-degraded-color)}.hds-app-footer__status-link--maintenance .flight-icon{fill:var(--app-footer-status-link-icon-maintenance-color)}.hds-app-footer__status-link--critical .flight-icon{fill:var(--app-footer-status-link-icon-critical-color)}.hds-app-footer__link.hds-link-inline--color-secondary,.hds-app-footer__status-link{color:var(--app-footer-link-default-color);text-align:right}.hds-app-footer__link.hds-link-inline--color-secondary.mock-hover,.hds-app-footer__link.hds-link-inline--color-secondary:hover,.hds-app-footer__status-link.mock-hover,.hds-app-footer__status-link:hover{color:var(--app-footer-link-hover-color)}.hds-app-footer__link.hds-link-inline--color-secondary.mock-active,.hds-app-footer__link.hds-link-inline--color-secondary:active,.hds-app-footer__status-link.mock-active,.hds-app-footer__status-link:active{color:var(--app-footer-link-active-color)}.hds-app-footer__link.hds-link-inline--color-secondary.mock-focus,.hds-app-footer__link.hds-link-inline--color-secondary:focus,.hds-app-footer__link.hds-link-inline--color-secondary:focus-visible,.hds-app-footer__status-link.mock-focus,.hds-app-footer__status-link:focus,.hds-app-footer__status-link:focus-visible{color:var(--app-footer-link-focus-color);outline-color:var(--app-footer-link-focus-outline-color)}.hds-app-footer__list-item{display:flex;align-items:center}.hds-app-footer__copyright{display:flex;gap:6px;align-items:center;color:var(--app-footer-copyright-text-color)}.hds-app-footer__copyright .flight-icon{fill:var(--app-footer-copyright-icon-color)}.hds-app-footer--theme-light{--app-footer-foreground-color:var(--token-color-foreground-primary);--app-footer-border-top-color:var(--token-color-border-primary);--app-footer-link-default-color:var(--token-color-foreground-faint);--app-footer-link-hover-color:var(--token-color-palette-neutral-600);--app-footer-link-active-color:var(--token-color-palette-neutral-700);--app-footer-link-focus-color:var(--token-color-foreground-faint);--app-footer-link-focus-outline-color:var(--token-color-focus-action-internal);--app-footer-copyright-text-color:var(--token-color-foreground-primary);--app-footer-copyright-icon-color:var(--token-color-hashicorp-brand);--app-footer-status-link-icon-operational-color:var(--token-color-foreground-success);--app-footer-status-link-icon-degraded-color:var(--token-color-foreground-warning);--app-footer-status-link-icon-maintenance-color:var(--token-color-foreground-warning);--app-footer-status-link-icon-critical-color:var(--token-color-foreground-critical)}.hds-app-footer--theme-dark{--app-footer-foreground-color:#b2b6bd;--app-footer-border-top-color:#b2b6bd66;--app-footer-link-default-color:#b2b6bd;--app-footer-link-hover-color:#d5d7db;--app-footer-link-active-color:#efeff1;--app-footer-link-focus-color:#b2b6bd;--app-footer-link-focus-outline-color:#389aff;--app-footer-copyright-text-color:#b2b6bd;--app-footer-copyright-icon-color:#fff;--app-footer-status-link-icon-operational-color:#009241;--app-footer-status-link-icon-degraded-color:#e88c03;--app-footer-status-link-icon-maintenance-color:#e88c03;--app-footer-status-link-icon-critical-color:#ef3016}.hds-app-frame{display:grid;grid-template-areas:"header header" "sidebar main" "sidebar footer";grid-template-rows:auto 1fr auto;grid-template-columns:auto 1fr;min-height:100vh}.hds-app-frame__modals:empty,button.hds-button[href] .hds-button__text,button.hds-button[href]::before{display:none}.hds-app-frame__header{z-index:7;grid-area:header}.hds-app-frame__sidebar{z-index:6;grid-area:sidebar}.hds-app-frame__main{grid-area:main}.hds-app-frame__footer{grid-area:footer}.hds-app-frame__modals{position:fixed;top:0;left:0;z-index:100;width:100vw;height:100vh;pointer-events:none}.hds-application-state{width:19.5rem;max-width:100%;margin:0 auto}.hds-application-state__header{display:grid;grid-template-columns:min-content 1fr;align-items:start;color:var(--token-color-foreground-faint)}.hds-application-state__icon{margin-right:8px;padding-top:4px}.hds-application-state__error-code,.hds-application-state__title{grid-column-start:2}.hds-application-state__body{padding:12px 0;color:var(--token-color-foreground-faint)}.hds-application-state__footer{display:flex;gap:8px;justify-content:space-between}.hds-application-state__footer.hds-application-state__footer--has-divider{padding:12px 0;border-top:1px solid var(--token-color-border-strong)}.hds-avatar{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:32px;height:32px}.hds-avatar svg,.hds-dropdown--is-inline,.hds-form-toggle{display:inline-block}.hds-avatar img{width:inherit;height:inherit;border-radius:2px}.hds-badge{display:inline-flex;align-items:center;max-width:100%;vertical-align:middle;border:1px solid transparent;border-radius:5px}.hds-badge__icon{display:block;flex:0 0 auto}.hds-badge__text{flex:1 0 0;font-weight:var(--token-typography-font-weight-medium)}.hds-badge--size-small{gap:.25rem;min-height:1.25rem;padding:calc(.125rem - 1px) calc(.375rem - 1px)}.hds-badge--size-small .hds-badge__icon{width:.75rem;height:.75rem}.hds-badge--size-large .hds-badge__icon,.hds-badge--size-medium .hds-badge__icon{width:1rem;height:1rem}.hds-badge--size-small .hds-badge__text{font-size:.8125rem;line-height:1.2308}.hds-badge--size-medium{gap:.25rem;min-height:1.5rem;padding:calc(.25rem - 1px) calc(.5rem - 1px)}.hds-badge--size-medium .hds-badge__text{font-size:.8125rem;line-height:1.2308}.hds-badge--size-large{gap:.375rem;min-height:2rem;padding:calc(.25rem - 1px) calc(.5rem - 1px)}.hds-badge--size-large .hds-badge__text{font-size:1rem;line-height:1.5}.hds-badge--color-neutral.hds-badge--type-filled{color:var(--token-color-foreground-primary)}.hds-badge--color-neutral.hds-badge--type-inverted{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-faint)}.hds-badge--color-neutral.hds-badge--type-outlined{color:var(--token-color-foreground-primary);background-color:transparent;border-color:var(--token-color-foreground-faint)}.hds-badge--color-neutral-dark-mode.hds-badge--type-filled{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-faint)}.hds-badge--color-neutral-dark-mode.hds-badge--type-inverted{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint)}.hds-badge--color-neutral-dark-mode.hds-badge--type-outlined{color:var(--token-color-foreground-high-contrast);background-color:transparent;border-color:var(--token-color-palette-neutral-100)}.hds-badge--color-highlight.hds-badge--type-filled{color:var(--token-color-foreground-highlight-on-surface);background-color:var(--token-color-surface-highlight)}.hds-badge--color-highlight.hds-badge--type-inverted{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-highlight)}.hds-badge--color-highlight.hds-badge--type-outlined{color:var(--token-color-foreground-highlight);background-color:transparent;border-color:currentColor}.hds-badge--color-success.hds-badge--type-filled{color:var(--token-color-foreground-success-on-surface);background-color:var(--token-color-surface-success)}.hds-badge--color-success.hds-badge--type-inverted{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-success)}.hds-badge--color-success.hds-badge--type-outlined{color:var(--token-color-foreground-success);background-color:transparent;border-color:currentColor}.hds-badge--color-warning.hds-badge--type-filled{color:var(--token-color-foreground-warning-on-surface);background-color:var(--token-color-surface-warning)}.hds-badge--color-warning.hds-badge--type-inverted{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-warning)}.hds-badge--color-warning.hds-badge--type-outlined{color:var(--token-color-foreground-warning);background-color:transparent;border-color:currentColor}.hds-badge--color-critical.hds-badge--type-filled{color:var(--token-color-foreground-critical-on-surface);background-color:var(--token-color-surface-critical)}.hds-badge--color-critical.hds-badge--type-inverted{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-critical)}.hds-badge--color-critical.hds-badge--type-outlined{color:var(--token-color-foreground-critical);background-color:transparent;border-color:currentColor}.hds-badge-count{display:inline-flex;align-items:center;max-width:100%;font-weight:var(--token-typography-font-weight-medium);border:1px solid transparent}.hds-button,.hds-dropdown-toggle-button{font-weight:var(--token-typography-font-weight-regular)}.hds-badge-count--size-small{min-height:1.25rem;padding:calc(.125rem - 1px) calc(.5rem - 1px);font-size:.8125rem;line-height:1.2308;border-radius:.625rem}.hds-badge-count--size-medium{min-height:1.5rem;padding:calc(.25rem - 1px) calc(.75rem - 1px);font-size:.8125rem;line-height:1.2308;border-radius:.75rem}.hds-badge-count--size-large{min-height:2rem;padding:calc(.25rem - 1px) calc(.875rem - 1px);font-size:1rem;line-height:1.5;border-radius:1rem}.hds-breadcrumb__list,.hds-breadcrumb__sublist{margin:0;padding:0;list-style:none}.hds-badge-count--color-neutral.hds-badge-count--type-filled{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-strong)}.hds-badge-count--color-neutral.hds-badge-count--type-inverted{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-faint)}.hds-badge-count--color-neutral.hds-badge-count--type-outlined{color:var(--token-color-foreground-primary);background-color:transparent;border-color:var(--token-color-foreground-faint)}.hds-badge-count--color-neutral-dark-mode.hds-badge-count--type-filled{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-faint)}.hds-badge-count--color-neutral-dark-mode.hds-badge-count--type-inverted{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint)}.hds-badge-count--color-neutral-dark-mode.hds-badge-count--type-outlined{color:var(--token-color-foreground-high-contrast);background-color:transparent;border-color:var(--token-color-palette-neutral-100)}.hds-breadcrumb{position:relative}.hds-breadcrumb__list{display:flex}.hds-breadcrumb--items-can-wrap .hds-breadcrumb__list{flex-wrap:wrap}.hds-breadcrumb__item{position:relative;display:flex;flex-direction:row;align-items:center;min-width:0}.hds-breadcrumb__list>.hds-breadcrumb__item:not(:last-child)::after{padding:0 8px;color:var(--token-color-palette-neutral-300);content:"/"}.hds-breadcrumb__sublist>.hds-breadcrumb__item+.hds-breadcrumb__item{margin-top:4px}.hds-breadcrumb__current,.hds-breadcrumb__link{margin:0 -4px;padding:0 4px;display:flex;min-width:0}.hds-breadcrumb__item--is-truncation{flex:none}.hds-breadcrumb__link{flex-direction:row;align-items:center;color:var(--token-color-foreground-faint);border-radius:5px;text-decoration-color:transparent;outline-style:solid;outline-color:transparent}.hds-breadcrumb__link.mock-active>.hds-breadcrumb__text,.hds-breadcrumb__link.mock-hover>.hds-breadcrumb__text,.hds-breadcrumb__link:active>.hds-breadcrumb__text,.hds-breadcrumb__link:hover>.hds-breadcrumb__text{text-decoration-color:currentColor}.hds-breadcrumb__link.mock-hover,.hds-breadcrumb__link:hover{color:var(--token-color-palette-neutral-600)}.hds-breadcrumb__link:focus:not(:focus-visible){box-shadow:none}.hds-breadcrumb__link:focus-visible{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-breadcrumb__link.mock-focus.mock-active,.hds-breadcrumb__link:focus:active{box-shadow:none}.hds-breadcrumb__link.mock-active,.hds-breadcrumb__link:active{color:var(--token-color-foreground-secondary)}.hds-breadcrumb__current{flex-direction:row;align-items:center;color:var(--token-color-foreground-strong)}.hds-breadcrumb__icon{flex:none;width:13px;height:13px;margin-right:6px}.hds-breadcrumb__text{padding:calc((28px - 1rem)/ 2) 0;font-size:.8125rem;line-height:1rem;white-space:nowrap;text-decoration:underline;text-overflow:ellipsis;text-decoration-color:transparent}.hds-breadcrumb__sublist .hds-breadcrumb__text{white-space:normal}.hds-breadcrumb__truncation-toggle{display:flex;flex:none;align-items:center;justify-content:center;width:28px;height:28px;margin:0 -4px;padding:0;color:var(--token-color-foreground-faint);background-color:transparent;border:1px solid transparent;border-radius:5px;outline:transparent solid 0;cursor:pointer}.hds-button,.hds-copy-snippet{align-items:center;isolation:isolate}.hds-button,.hds-copy-snippet,.hds-dismiss-button,.hds-dropdown-toggle-button,.hds-dropdown-toggle-icon{outline-style:solid;outline-color:transparent}.hds-breadcrumb__truncation-toggle.mock-hover,.hds-breadcrumb__truncation-toggle:hover{color:var(--token-color-foreground-faint);border-color:var(--token-color-border-strong)}.hds-breadcrumb__truncation-toggle.mock-focus,.hds-breadcrumb__truncation-toggle:focus{box-shadow:var(--token-focus-ring-action-box-shadow);background-color:transparent;border:none}.hds-breadcrumb__truncation-toggle:focus:not(:focus-visible){box-shadow:none}.hds-breadcrumb__truncation-toggle:focus-visible,.hds-copy-snippet.mock-focus::before,.hds-copy-snippet:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-breadcrumb__truncation-toggle.mock-focus.mock-active,.hds-breadcrumb__truncation-toggle:focus:active{box-shadow:none}.hds-breadcrumb__truncation-toggle.mock-active,.hds-breadcrumb__truncation-toggle:active{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong)}.hds-breadcrumb__truncation-content{position:absolute;top:100%;left:-4px;z-index:300;width:-moz-max-content;width:max-content;max-width:200px;margin-top:4px;padding:6px 12px;background-color:var(--token-color-surface-primary);border-radius:6px;box-shadow:var(--token-surface-high-box-shadow)}.hds-button.hds-button--width-full,.hds-copy-snippet--is-truncated,.hds-copy-snippet--width-full,.hds-dropdown-toggle-button--width-full{max-width:100%;width:100%}.hds-button--size-small,.hds-dropdown-toggle-button--size-small{padding:.375rem .6875rem;min-height:1.75rem}.hds-button{position:relative;display:flex;gap:.375rem;justify-content:center;width:auto;text-decoration:none;border:1px solid transparent;border-radius:5px}a.hds-button{width:-moz-fit-content;width:fit-content}a.hds-button.mock-active,a.hds-button.mock-focus,a.hds-button.mock-hover,a.hds-button:active,a.hds-button:focus,a.hds-button:hover{text-decoration:underline}.hds-button.mock-disabled,.hds-button.mock-disabled:focus,.hds-button.mock-disabled:hover,.hds-button:disabled,.hds-button:disabled:focus,.hds-button:disabled:hover,.hds-button[disabled],.hds-button[disabled]:focus,.hds-button[disabled]:hover{color:var(--token-color-foreground-disabled);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary);box-shadow:none;cursor:not-allowed}.hds-button.mock-disabled::before,.hds-button.mock-disabled:focus::before,.hds-button.mock-disabled:hover::before,.hds-button:disabled::before,.hds-button:disabled:focus::before,.hds-button:disabled:hover::before,.hds-button[disabled]::before,.hds-button[disabled]:focus::before,.hds-button[disabled]:hover::before{border-color:transparent}.hds-button.hds-button--width-full .hds-button__text{flex:0 0 auto}.hds-button__text,.hds-copy-snippet__text,.hds-form-group--radio-cards .hds-form-radio-card--has-fluid-width{flex:1 0 0}.hds-button.mock-focus,.hds-button:focus{box-shadow:none}.hds-button.mock-focus::before,.hds-button:focus::before{position:absolute;top:-4px;right:-4px;bottom:-4px;left:-4px;z-index:-1;border:3px solid transparent;border-radius:8px;content:""}.hds-button__text{text-align:center}.hds-button--size-small .hds-button__icon{width:.75rem;height:.75rem}.hds-button--size-small .hds-button__text{font-size:.8125rem;line-height:.875rem}.hds-button--size-small.hds-button--is-icon-only{min-width:1.75rem;padding-right:.375rem;padding-left:.375rem}.hds-button--size-medium{min-height:2.25rem;padding:.5625rem .9375rem}.hds-button--size-medium .hds-button__icon{width:1rem;height:1rem}.hds-button--size-medium .hds-button__text{font-size:.875rem;line-height:1rem}.hds-button--size-medium.hds-button--is-icon-only{min-width:2.25rem;padding-right:.5625rem;padding-left:.5625rem}.hds-button--size-large{min-height:3rem;padding:.6875rem 1.1875rem}.hds-button--size-large .hds-button__icon{width:1.5rem;height:1.5rem}.hds-button--size-large .hds-button__text{font-size:1rem;line-height:1.5rem}.hds-button--size-large.hds-button--is-icon-only{min-width:3rem;padding-right:.6875rem;padding-left:.6875rem}.hds-button--color-primary{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-200);border-color:var(--token-color-palette-blue-300);box-shadow:var(--token-elevation-low-box-shadow)}.hds-button--color-primary.mock-hover,.hds-button--color-primary:hover{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-300);border-color:var(--token-color-palette-blue-400);cursor:pointer}.hds-button--color-primary.mock-focus,.hds-button--color-primary:focus{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-200);border-color:var(--token-color-focus-action-internal)}.hds-button--color-primary.mock-focus::before,.hds-button--color-primary:focus::before{top:-6px;right:-6px;bottom:-6px;left:-6px;border-color:var(--token-color-focus-action-external);border-radius:10px}.hds-button--color-primary.mock-active,.hds-button--color-primary:active{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-400);border-color:var(--token-color-palette-blue-400);box-shadow:none}.hds-button--color-primary.mock-active::before,.hds-button--color-primary:active::before{border-color:transparent}.hds-button--color-secondary{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-strong);box-shadow:var(--token-elevation-low-box-shadow)}.hds-button--color-secondary.mock-hover,.hds-button--color-secondary:hover{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-primary);border-color:var(--token-color-border-strong);cursor:pointer}.hds-button--color-secondary.mock-focus,.hds-button--color-secondary:focus{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-focus-action-internal)}.hds-button--color-secondary.mock-focus::before,.hds-button--color-secondary:focus::before{border-color:var(--token-color-focus-action-external)}.hds-button--color-secondary.mock-active,.hds-button--color-secondary:active{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong);box-shadow:none}.hds-button--color-secondary.mock-active::before,.hds-button--color-secondary:active::before{border-color:transparent}.hds-button--color-tertiary{color:var(--token-color-foreground-action);background-color:transparent;border-color:transparent}.hds-button--color-tertiary.mock-hover,.hds-button--color-tertiary:hover{color:var(--token-color-foreground-action-hover);background-color:var(--token-color-surface-primary);border-color:var(--token-color-border-strong);cursor:pointer}.hds-button--color-tertiary.mock-focus,.hds-button--color-tertiary:focus{color:var(--token-color-foreground-action);border-color:var(--token-color-focus-action-internal)}.hds-button--color-tertiary.mock-focus::before,.hds-button--color-tertiary:focus::before{border-color:var(--token-color-focus-action-external)}.hds-button--color-tertiary.mock-active,.hds-button--color-tertiary:active{color:var(--token-color-foreground-action-active);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong);box-shadow:none}.hds-button--color-tertiary.mock-active::before,.hds-button--color-tertiary:active::before{border-color:transparent}.hds-button--color-tertiary.mock-disabled,.hds-button--color-tertiary.mock-disabled:focus,.hds-button--color-tertiary.mock-disabled:hover,.hds-button--color-tertiary:disabled,.hds-button--color-tertiary:disabled:focus,.hds-button--color-tertiary:disabled:hover,.hds-button--color-tertiary[disabled],.hds-button--color-tertiary[disabled]:focus,.hds-button--color-tertiary[disabled]:hover{background-color:transparent;border-color:transparent}.hds-button--color-tertiary.mock-disabled::before,.hds-button--color-tertiary.mock-disabled:focus::before,.hds-button--color-tertiary.mock-disabled:hover::before,.hds-button--color-tertiary:disabled::before,.hds-button--color-tertiary:disabled:focus::before,.hds-button--color-tertiary:disabled:hover::before,.hds-button--color-tertiary[disabled]::before,.hds-button--color-tertiary[disabled]:focus::before,.hds-button--color-tertiary[disabled]:hover::before{border-color:transparent}.hds-button--color-critical{color:var(--token-color-foreground-critical-on-surface);background-color:var(--token-color-surface-critical);border-color:var(--token-color-foreground-critical-on-surface);box-shadow:var(--token-elevation-low-box-shadow)}.hds-button--color-critical.mock-hover,.hds-button--color-critical:hover{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-red-300);border-color:var(--token-color-palette-red-400);cursor:pointer}.hds-button--color-critical.mock-focus,.hds-button--color-critical:focus{color:var(--token-color-foreground-critical-on-surface);background-color:var(--token-color-surface-critical);border-color:var(--token-color-focus-critical-internal)}.hds-button--color-critical.mock-focus::before,.hds-button--color-critical:focus::before{border-color:var(--token-color-focus-critical-external)}.hds-button--color-critical.mock-active,.hds-button--color-critical:active{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-red-400);border-color:var(--token-color-palette-red-400);box-shadow:none}.hds-button--color-critical.mock-active::before,.hds-button--color-critical:active::before{border-color:transparent}button.hds-button[href]{color:#fff!important;background-color:red!important;border:none}button.hds-button[href]::after{content:' Attention: you’re passing a "href" attribute to the "Hds::Button" component, you should use an "@href" argument.'}.hds-button-set{display:flex;gap:16px}.hds-card__container{position:relative;background-color:#fff;border-radius:6px}.hds-card__container--level-surface-base{box-shadow:var(--token-surface-base-box-shadow)}.hds-card__container--level-surface-mid{box-shadow:var(--token-surface-mid-box-shadow)}.hds-card__container--level-surface-high{box-shadow:var(--token-surface-high-box-shadow)}.hds-card__container--hover-level-surface-base.mock-hover,.hds-card__container--hover-level-surface-base:hover{box-shadow:var(--token-surface-base-box-shadow)}.hds-card__container--hover-level-surface-mid.mock-hover,.hds-card__container--hover-level-surface-mid:hover{box-shadow:var(--token-surface-mid-box-shadow)}.hds-card__container--hover-level-surface-high.mock-hover,.hds-card__container--hover-level-surface-high:hover{box-shadow:var(--token-surface-high-box-shadow)}.hds-card__container--active-level-surface-base.mock-active,.hds-card__container--active-level-surface-base:active{box-shadow:var(--token-surface-base-box-shadow)}.hds-card__container--active-level-surface-mid.mock-active,.hds-card__container--active-level-surface-mid:active{box-shadow:var(--token-surface-mid-box-shadow)}.hds-card__container--active-level-surface-high.mock-active,.hds-card__container--active-level-surface-high:active{box-shadow:var(--token-surface-high-box-shadow)}.hds-card__container--level-elevation-base{box-shadow:var(--token-elevation-base-box-shadow)}.hds-card__container--level-elevation-mid{box-shadow:var(--token-elevation-mid-box-shadow)}.hds-card__container--level-elevation-high{box-shadow:var(--token-elevation-high-box-shadow)}.hds-card__container--hover-level-elevation-base.mock-hover,.hds-card__container--hover-level-elevation-base:hover{box-shadow:var(--token-elevation-base-box-shadow)}.hds-card__container--hover-level-elevation-mid.mock-hover,.hds-card__container--hover-level-elevation-mid:hover{box-shadow:var(--token-elevation-mid-box-shadow)}.hds-card__container--hover-level-elevation-high.mock-hover,.hds-card__container--hover-level-elevation-high:hover{box-shadow:var(--token-elevation-high-box-shadow)}.hds-card__container--active-level-elevation-base.mock-active,.hds-card__container--active-level-elevation-base:active{box-shadow:var(--token-elevation-base-box-shadow)}.hds-card__container--active-level-elevation-mid.mock-active,.hds-card__container--active-level-elevation-mid:active{box-shadow:var(--token-elevation-mid-box-shadow)}.hds-card__container--active-level-elevation-high.mock-active,.hds-card__container--active-level-elevation-high:active{box-shadow:var(--token-elevation-high-box-shadow)}.hds-card__container--background-neutral-primary{background-color:var(--token-color-surface-primary)}.hds-card__container--background-neutral-secondary{background-color:var(--token-color-surface-faint)}.hds-card__container--overflow-visible{overflow:visible}.hds-copy-button{cursor:pointer}.hds-copy-button .hds-button__icon{color:var(--token-color-foreground-action)}.hds-copy-button.hds-copy-button--status-success .hds-button__icon{color:var(--token-color-foreground-success)}.hds-copy-button.hds-copy-button--status-error .hds-button__icon{color:var(--token-color-foreground-critical)}.hds-copy-snippet{position:relative;display:flex;gap:8px;justify-content:space-between;padding:6px 4px;text-align:left;border:1px solid transparent;border-radius:5px;cursor:pointer}.hds-copy-snippet::before,.hds-dismiss-button::before{position:absolute;z-index:-1;content:""}.hds-copy-snippet::before{top:0;right:0;bottom:0;left:0;border-radius:5px}.hds-copy-snippet:focus:not(:focus-visible)::before{box-shadow:none}.hds-copy-snippet:focus-visible::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-copy-snippet.mock-focus.mock-active::before,.hds-copy-snippet:focus:active::before{box-shadow:none}.hds-copy-snippet--color-primary{color:var(--token-color-foreground-action);background-color:transparent}.hds-copy-snippet--color-primary.mock-hover,.hds-copy-snippet--color-primary:hover{color:var(--token-color-foreground-action-hover);background-color:var(--token-color-surface-interactive);border-color:var(--token-color-border-strong)}.hds-copy-snippet--color-primary.mock-active,.hds-copy-snippet--color-primary:active{color:var(--token-color-foreground-action-active);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong)}.hds-copy-snippet--color-primary:focus{background-color:transparent}.hds-copy-snippet--color-secondary{color:var(--token-color-foreground-primary);background-color:transparent}.hds-copy-snippet--color-secondary.mock-hover,.hds-copy-snippet--color-secondary:hover{background-color:var(--token-color-surface-interactive);border-color:var(--token-color-border-strong)}.hds-copy-snippet--color-secondary.mock-active,.hds-copy-snippet--color-secondary:active{background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong)}.hds-copy-snippet--status-error,.hds-copy-snippet--status-success{background-color:var(--token-color-surface-interactive)}.hds-copy-snippet--color-secondary .hds-copy-snippet__icon{color:var(--token-color-foreground-action)}.hds-copy-snippet--color-secondary .hds-copy-snippet__icon:hover{color:var(--token-color-foreground-action-hover)}.hds-copy-snippet--color-secondary .hds-copy-snippet__icon:active{color:var(--token-color-foreground-action-active)}.hds-copy-snippet--color-secondary .hds-copy-snippet__icon:focus{color:var(--token-color-foreground-action)}.hds-copy-snippet--status-success .hds-copy-snippet__icon{color:var(--token-color-foreground-success)}.hds-copy-snippet--status-error .hds-copy-snippet__icon{color:var(--token-color-foreground-critical)}.hds-copy-snippet__icon{flex:none}.hds-copy-snippet--is-truncated .hds-copy-snippet__text{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.hds-disclosure-primitive{position:relative}.hds-dismiss-button{flex:none;padding:0;color:var(--token-color-foreground-faint);background-color:transparent;border:none;cursor:pointer;position:relative;isolation:isolate}.hds-dismiss-button.mock-hover::before,.hds-dismiss-button:hover::before{background-color:rgba(222,223,227,.4)}.hds-dismiss-button::before{top:-4px;right:-4px;bottom:-4px;left:-4px;border-radius:5px}.hds-dismiss-button.mock-focus::before,.hds-dismiss-button:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-dismiss-button:focus:not(:focus-visible)::before{box-shadow:none}.hds-dismiss-button:focus-visible::before,.hds-dropdown-toggle-icon.mock-focus::before,.hds-dropdown-toggle-icon:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-dismiss-button.mock-focus.mock-active::before,.hds-dismiss-button:focus:active::before{box-shadow:none}.hds-dismiss-button.mock-active,.hds-dismiss-button:active{color:var(--token-color-foreground-secondary)}.hds-dismiss-button.mock-active::before,.hds-dismiss-button:active::before{background-color:rgba(222,223,227,.4);border:1px solid var(--token-color-border-strong)}.hds-dropdown--is-inline .hds-dropdown-toggle-button,.hds-dropdown--is-inline .hds-dropdown-toggle-icon{display:inline-flex}.hds-dropdown-toggle-icon{display:flex;gap:2px;align-items:center;justify-content:center;padding:1px;background-color:var(--token-color-surface-faint);border:1px solid var(--token-color-border-strong);border-radius:5px;position:relative;isolation:isolate}.hds-dropdown-toggle-button,.hds-link-standalone{gap:.375rem;font-family:var(--token-typography-font-stack-text);isolation:isolate}.hds-dropdown-toggle-icon.mock-hover,.hds-dropdown-toggle-icon:hover{background-color:var(--token-color-surface-interactive);cursor:pointer}.hds-dropdown-toggle-icon::before{position:absolute;top:-1px;right:-1px;bottom:-1px;left:-1px;z-index:-1;border-radius:5px;content:""}.hds-dropdown-toggle-icon:focus:not(:focus-visible)::before{box-shadow:none}.hds-dropdown-toggle-icon:focus-visible::before,.hds-link-standalone.mock-focus::before,.hds-link-standalone:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-dropdown-toggle-icon.mock-focus.mock-active::before,.hds-dropdown-toggle-icon:focus:active::before{box-shadow:none}.hds-dropdown-toggle-icon.mock-active,.hds-dropdown-toggle-icon:active{background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong)}.hds-dropdown-toggle-icon.mock-disabled,.hds-dropdown-toggle-icon:disabled{color:var(--token-color-foreground-disabled);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary);box-shadow:none;cursor:not-allowed}.hds-dropdown-toggle-icon.mock-disabled::before,.hds-dropdown-toggle-icon:disabled::before{border-color:transparent}.hds-dropdown-toggle-icon__wrapper{display:flex;align-items:center;justify-content:center;border-radius:3px}.hds-dropdown-toggle-icon__wrapper img{width:100%;height:100%;-o-object-fit:cover;object-fit:cover;border-radius:inherit}.hds-dropdown-toggle-icon--size-small .hds-dropdown-toggle-icon__wrapper{width:24px;height:24px}.hds-dropdown-toggle-icon--size-medium .hds-dropdown-toggle-icon__wrapper{width:32px;height:32px}.hds-dropdown-toggle-button{position:relative;display:flex;align-items:center;justify-content:center;width:auto;text-decoration:none;border:1px solid transparent;border-radius:5px}.hds-dropdown-toggle-button.mock-focus,.hds-dropdown-toggle-button:focus{box-shadow:none}.hds-dropdown-toggle-button.mock-focus::before,.hds-dropdown-toggle-button:focus::before{position:absolute;top:-4px;right:-4px;bottom:-4px;left:-4px;z-index:-1;border:3px solid transparent;border-radius:8px;content:""}.hds-dropdown-toggle-button.mock-disabled,.hds-dropdown-toggle-button.mock-disabled:focus,.hds-dropdown-toggle-button.mock-disabled:hover,.hds-dropdown-toggle-button:disabled,.hds-dropdown-toggle-button:disabled:focus,.hds-dropdown-toggle-button:disabled:hover{color:var(--token-color-foreground-disabled);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary);box-shadow:none;cursor:not-allowed}.hds-dropdown-toggle-button.mock-disabled::before,.hds-dropdown-toggle-button.mock-disabled:focus::before,.hds-dropdown-toggle-button.mock-disabled:hover::before,.hds-dropdown-toggle-button:disabled::before,.hds-dropdown-toggle-button:disabled:focus::before,.hds-dropdown-toggle-button:disabled:hover::before{border-color:transparent}.hds-dropdown-toggle-button.mock-disabled .hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button.mock-disabled .hds-dropdown-toggle-button__count,.hds-dropdown-toggle-button.mock-disabled:focus .hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button.mock-disabled:focus .hds-dropdown-toggle-button__count,.hds-dropdown-toggle-button.mock-disabled:hover .hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button.mock-disabled:hover .hds-dropdown-toggle-button__count,.hds-dropdown-toggle-button:disabled .hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button:disabled .hds-dropdown-toggle-button__count,.hds-dropdown-toggle-button:disabled:focus .hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button:disabled:focus .hds-dropdown-toggle-button__count,.hds-dropdown-toggle-button:disabled:hover .hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button:disabled:hover .hds-dropdown-toggle-button__count{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-strong)}.hds-dropdown-toggle-button--size-small .hds-dropdown-toggle-button__icon{width:.75rem;height:.75rem}.hds-dropdown-toggle-button--size-small .hds-dropdown-toggle-button__text{font-size:.8125rem;line-height:.875rem}.hds-dropdown-toggle-button--size-small.hds-dropdown-toggle-button--is-icon-only{min-width:1.75rem;padding-right:.375rem;padding-left:.375rem}.hds-dropdown-toggle-button--size-medium{min-height:2.25rem;padding:.5625rem .9375rem}.hds-dropdown-toggle-button--size-medium .hds-dropdown-toggle-button__icon{width:1rem;height:1rem}.hds-dropdown-toggle-button--size-medium .hds-dropdown-toggle-button__text{font-size:.875rem;line-height:1rem}.hds-dropdown-toggle-button--size-medium.hds-dropdown-toggle-button--is-icon-only{min-width:2.25rem;padding-right:.5625rem;padding-left:.5625rem}.hds-dropdown-toggle-button--size-large{min-height:3rem;padding:.6875rem 1.1875rem}.hds-dropdown-toggle-button--size-large .hds-dropdown-toggle-button__icon{width:1.5rem;height:1.5rem}.hds-dropdown-toggle-button--size-large .hds-dropdown-toggle-button__text{font-size:1rem;line-height:1.5rem}.hds-dropdown-toggle-button--size-large.hds-dropdown-toggle-button--is-icon-only{min-width:3rem;padding-right:.6875rem;padding-left:.6875rem}.hds-dropdown-toggle-button--size-small{padding-right:.375rem}.hds-dropdown-toggle-button--size-medium{padding-right:.5625rem}.hds-dropdown-toggle-button--color-primary{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-200);border-color:var(--token-color-palette-blue-300);box-shadow:var(--token-elevation-low-box-shadow)}.hds-dropdown-toggle-button--color-primary.mock-hover,.hds-dropdown-toggle-button--color-primary:hover{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-300);border-color:var(--token-color-palette-blue-400);cursor:pointer}.hds-dropdown-toggle-button--color-primary.mock-focus,.hds-dropdown-toggle-button--color-primary:focus{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-200);border-color:var(--token-color-focus-action-internal)}.hds-dropdown-toggle-button--color-primary.mock-focus::before,.hds-dropdown-toggle-button--color-primary:focus::before{top:-6px;right:-6px;bottom:-6px;left:-6px;border-color:var(--token-color-focus-action-external);border-radius:10px}.hds-dropdown-toggle-button--color-primary.mock-active,.hds-dropdown-toggle-button--color-primary:active{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-400);border-color:var(--token-color-palette-blue-400);box-shadow:none}.hds-dropdown-toggle-button--color-primary.mock-active::before,.hds-dropdown-toggle-button--color-primary:active::before{border-color:transparent}.hds-dropdown-toggle-button--color-secondary{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-strong);box-shadow:var(--token-elevation-low-box-shadow)}.hds-dropdown-toggle-button--color-secondary.mock-hover,.hds-dropdown-toggle-button--color-secondary:hover{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-primary);border-color:var(--token-color-border-strong);cursor:pointer}.hds-dropdown-toggle-button--color-secondary.mock-focus,.hds-dropdown-toggle-button--color-secondary:focus{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-focus-action-internal)}.hds-dropdown-toggle-button--color-secondary.mock-focus::before,.hds-dropdown-toggle-button--color-secondary:focus::before{border-color:var(--token-color-focus-action-external)}.hds-dropdown-toggle-button--color-secondary.mock-active,.hds-dropdown-toggle-button--color-secondary:active{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong);box-shadow:none}.hds-dropdown-toggle-button--color-secondary.mock-active::before,.hds-dropdown-toggle-button--color-secondary:active::before{border-color:transparent}.hds-dropdown-list-item--variant-separator::before,.hds-dropdown__header--with-divider{border-bottom:1px solid var(--token-color-border-primary)}.hds-dropdown-toggle-button--width-full{justify-content:space-between}.hds-dropdown-toggle-button__text{text-align:left}.hds-dropdown-toggle-button__icon{flex:none}.hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button__count{margin:-3px 0}.hds-dropdown-toggle-chevron{margin-left:auto;padding-left:2px}@media (prefers-reduced-motion:no-preference){.hds-accordion-item__button .flight-icon-chevron-down,.hds-dropdown-toggle-chevron .flight-icon-chevron-down{transition:transform .3s}}.hds-dropdown-toggle-button--is-open .hds-dropdown-toggle-chevron .flight-icon-chevron-down,.hds-dropdown-toggle-icon--is-open .hds-dropdown-toggle-chevron .flight-icon-chevron-down{transform:rotate(-180deg)}.hds-dropdown__content{display:flex;flex-direction:column;width:-moz-max-content;width:max-content;min-width:200px;max-width:400px;background-color:var(--token-color-surface-primary);border-radius:6px;box-shadow:var(--token-surface-high-box-shadow)}.hds-dropdown__content--fixed-width{min-width:initial;max-width:initial}.hds-dropdown__content--position-bottom-right{position:absolute;top:calc(100% + 4px);right:0;z-index:2}.hds-dropdown__content--position-bottom-left{position:absolute;top:calc(100% + 4px);left:0;z-index:2}.hds-dropdown__content--position-top-right{position:absolute;right:0;bottom:calc(100% + 4px);z-index:2}.hds-dropdown__content--position-top-left{position:absolute;bottom:calc(100% + 4px);left:0;z-index:2}.hds-dropdown__list{flex:1 1 auto;margin:0;padding:4px 0;overflow-y:auto;list-style:none;overscroll-behavior:contain}.hds-dropdown__footer,.hds-dropdown__header{position:relative;flex:none;padding:0 8px}.hds-dropdown__footer>.hds-link-standalone,.hds-dropdown__header>.hds-link-standalone{width:initial;margin:4px 0;padding:7px 8px}.hds-dropdown__footer>.hds-link-standalone::before,.hds-dropdown__header>.hds-link-standalone::before{top:0;bottom:0}.hds-dropdown__footer>.hds-button,.hds-dropdown__footer>.hds-form-text-input,.hds-dropdown__header>.hds-button,.hds-dropdown__header>.hds-form-text-input{margin:8px 0}.hds-dropdown__footer>.hds-button-set,.hds-dropdown__header>.hds-button-set{gap:8px;margin:8px 0}.hds-dropdown__footer--with-divider{border-top:1px solid var(--token-color-border-primary)}.hds-dropdown-list-item__copy-item-title{padding:2px 0 4px;color:var(--token-color-foreground-faint)}.hds-dropdown-list-item--variant-copy-item{width:100%;padding:10px 16px 12px}.hds-dropdown-list-item--variant-description{padding:2px 16px 4px;color:var(--token-color-foreground-faint)}.hds-dropdown-list-item--variant-generic{padding-right:16px;padding-left:16px}.hds-dropdown-list-item--variant-checkmark,.hds-dropdown-list-item--variant-interactive{position:relative;min-height:36px;isolation:isolate}.hds-dropdown-list-item--variant-checkmark button,.hds-dropdown-list-item--variant-interactive button{width:100%;background-color:transparent}.hds-dropdown-list-item--variant-checkmark button:hover,.hds-dropdown-list-item--variant-interactive button:hover{cursor:pointer}.hds-dropdown-list-item--variant-checkmark a,.hds-dropdown-list-item--variant-checkmark button,.hds-dropdown-list-item--variant-interactive a,.hds-dropdown-list-item--variant-interactive button{display:flex;align-items:flex-start;padding:7px 9px 7px 15px;text-decoration:none;border:1px solid transparent;outline-style:solid;outline-color:transparent}.hds-dropdown-list-item--variant-checkmark a::before,.hds-dropdown-list-item--variant-checkmark button::before,.hds-dropdown-list-item--variant-interactive a::before,.hds-dropdown-list-item--variant-interactive button::before{position:absolute;top:6px;bottom:6px;left:4px;z-index:-1;width:2px;border-radius:1px;content:""}.hds-dropdown-list-item--variant-checkmark a::after,.hds-dropdown-list-item--variant-checkmark button::after,.hds-dropdown-list-item--variant-interactive a::after,.hds-dropdown-list-item--variant-interactive button::after{position:absolute;top:0;right:4px;bottom:0;left:10px;z-index:-1;border-radius:5px;content:""}.hds-dropdown-list-item--variant-checkmark a.mock-hover,.hds-dropdown-list-item--variant-checkmark a:hover,.hds-dropdown-list-item--variant-checkmark button.mock-hover,.hds-dropdown-list-item--variant-checkmark button:hover,.hds-dropdown-list-item--variant-interactive a.mock-hover,.hds-dropdown-list-item--variant-interactive a:hover,.hds-dropdown-list-item--variant-interactive button.mock-hover,.hds-dropdown-list-item--variant-interactive button:hover{color:var(--current-color-hover)}.hds-dropdown-list-item--variant-checkmark a.mock-focus,.hds-dropdown-list-item--variant-checkmark a:focus,.hds-dropdown-list-item--variant-checkmark a:focus-visible,.hds-dropdown-list-item--variant-checkmark button.mock-focus,.hds-dropdown-list-item--variant-checkmark button:focus,.hds-dropdown-list-item--variant-checkmark button:focus-visible,.hds-dropdown-list-item--variant-interactive a.mock-focus,.hds-dropdown-list-item--variant-interactive a:focus,.hds-dropdown-list-item--variant-interactive a:focus-visible,.hds-dropdown-list-item--variant-interactive button.mock-focus,.hds-dropdown-list-item--variant-interactive button:focus,.hds-dropdown-list-item--variant-interactive button:focus-visible{color:var(--current-color-focus)}.hds-dropdown-list-item--variant-checkmark a.mock-hover::before,.hds-dropdown-list-item--variant-checkmark a:hover::before,.hds-dropdown-list-item--variant-checkmark button.mock-hover::before,.hds-dropdown-list-item--variant-checkmark button:hover::before,.hds-dropdown-list-item--variant-interactive a.mock-hover::before,.hds-dropdown-list-item--variant-interactive a:hover::before,.hds-dropdown-list-item--variant-interactive button.mock-hover::before,.hds-dropdown-list-item--variant-interactive button:hover::before{background-color:currentColor}.hds-dropdown-list-item--variant-checkmark a.mock-focus::after,.hds-dropdown-list-item--variant-checkmark a:focus::after,.hds-dropdown-list-item--variant-checkmark button.mock-focus::after,.hds-dropdown-list-item--variant-checkmark button:focus::after,.hds-dropdown-list-item--variant-interactive a.mock-focus::after,.hds-dropdown-list-item--variant-interactive a:focus::after,.hds-dropdown-list-item--variant-interactive button.mock-focus::after,.hds-dropdown-list-item--variant-interactive button:focus::after{left:4px;box-shadow:var(--current-focus-ring-box-shadow)}.hds-dropdown-list-item--variant-checkmark a:focus:not(:focus-visible)::after,.hds-dropdown-list-item--variant-checkmark button:focus:not(:focus-visible)::after,.hds-dropdown-list-item--variant-interactive a:focus:not(:focus-visible)::after,.hds-dropdown-list-item--variant-interactive button:focus:not(:focus-visible)::after{background-color:transparent;box-shadow:none}.hds-dropdown-list-item--variant-checkmark a:focus-visible::after,.hds-dropdown-list-item--variant-checkmark button:focus-visible::after,.hds-dropdown-list-item--variant-interactive a:focus-visible::after,.hds-dropdown-list-item--variant-interactive button:focus-visible::after{left:4px;box-shadow:var(--current-focus-ring-box-shadow)}.hds-dropdown-list-item--variant-checkmark a.mock-focus.mock-active::after,.hds-dropdown-list-item--variant-checkmark a:focus-visible:active::after,.hds-dropdown-list-item--variant-checkmark a:focus:active::after,.hds-dropdown-list-item--variant-checkmark button.mock-focus.mock-active::after,.hds-dropdown-list-item--variant-checkmark button:focus-visible:active::after,.hds-dropdown-list-item--variant-checkmark button:focus:active::after,.hds-dropdown-list-item--variant-interactive a.mock-focus.mock-active::after,.hds-dropdown-list-item--variant-interactive a:focus-visible:active::after,.hds-dropdown-list-item--variant-interactive a:focus:active::after,.hds-dropdown-list-item--variant-interactive button.mock-focus.mock-active::after,.hds-dropdown-list-item--variant-interactive button:focus-visible:active::after,.hds-dropdown-list-item--variant-interactive button:focus:active::after{left:10px;background-color:var(--current-background-color);box-shadow:none}.hds-dropdown-list-item--variant-checkmark a.mock-active,.hds-dropdown-list-item--variant-checkmark a:active,.hds-dropdown-list-item--variant-checkmark button.mock-active,.hds-dropdown-list-item--variant-checkmark button:active,.hds-dropdown-list-item--variant-interactive a.mock-active,.hds-dropdown-list-item--variant-interactive a:active,.hds-dropdown-list-item--variant-interactive button.mock-active,.hds-dropdown-list-item--variant-interactive button:active{color:var(--current-color-active)}.hds-dropdown-list-item--variant-checkmark a.mock-active::before,.hds-dropdown-list-item--variant-checkmark a:active::before,.hds-dropdown-list-item--variant-checkmark button.mock-active::before,.hds-dropdown-list-item--variant-checkmark button:active::before,.hds-dropdown-list-item--variant-interactive a.mock-active::before,.hds-dropdown-list-item--variant-interactive a:active::before,.hds-dropdown-list-item--variant-interactive button.mock-active::before,.hds-dropdown-list-item--variant-interactive button:active::before{background-color:currentColor}.hds-dropdown-list-item__interactive-icon{margin-top:2px;margin-right:8px}.hds-dropdown-list-item__interactive-text{flex:1;text-align:left}.hds-dropdown-list-item--color-action a,.hds-dropdown-list-item--color-action button{color:var(--token-color-foreground-primary);--current-color-hover:var(--token-color-foreground-action-hover);--current-color-focus:var(--token-color-foreground-action-active);--current-color-active:var(--token-color-foreground-action-active)}.hds-dropdown-list-item--color-action a::after,.hds-dropdown-list-item--color-action button::after{--current-focus-ring-box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-dropdown-list-item--color-critical a,.hds-dropdown-list-item--color-critical button{color:var(--token-color-foreground-critical);--current-color-hover:var(--token-color-palette-red-300);--current-color-focus:var(--token-color-palette-red-400);--current-color-active:var(--token-color-palette-red-400)}.hds-dropdown-list-item--color-critical a::after,.hds-dropdown-list-item--color-critical button::after{--current-background-color:var(--token-color-surface-critical);--current-focus-ring-box-shadow:var(--token-focus-ring-critical-box-shadow)}.hds-dropdown-list-item__interactive-loading-wrapper{display:flex;align-items:center;padding:8px 10px 8px 16px}.hds-dropdown-list-item__interactive-loading-wrapper .hds-dropdown-list-item__interactive-text{color:var(--token-color-foreground-faint)}.hds-dropdown-list-item__interactive-loading-wrapper .hds-dropdown-list-item__interactive-icon{color:var(--token-color-foreground-primary)}.hds-dropdown-list-item--variant-separator{position:relative;width:100%;height:4px}.hds-dropdown-list-item--variant-separator::before{position:absolute;right:6px;bottom:0;left:6px;content:""}.hds-dropdown-list-item--variant-title{padding:10px 16px 4px;color:var(--token-color-foreground-strong)}.hds-dropdown-list-item--variant-checkmark-selected .hds-dropdown-list-item__interactive{color:var(--token-color-foreground-action)}.hds-dropdown-list-item__checkmark{display:flex;width:16px;height:20px;margin-left:8px}.hds-dropdown-list-item__checkmark-icon{align-self:center}.hds-dropdown-list-item__interactive[disabled],.hds-dropdown-list-item__interactive[disabled]:hover{color:var(--token-color-foreground-disabled);cursor:not-allowed}.hds-dropdown-list-item--variant-checkbox,.hds-dropdown-list-item--variant-radio{display:flex;align-items:self-start;padding:8px 16px}.hds-dropdown-list-item--variant-checkbox .hds-dropdown-list-item__control,.hds-dropdown-list-item--variant-radio .hds-dropdown-list-item__control{flex-shrink:0;margin-top:2px;margin-right:8px}.hds-dropdown-list-item--variant-checkbox .hds-dropdown-list-item__control[disabled]~.hds-dropdown-list-item__count,.hds-dropdown-list-item--variant-checkbox .hds-dropdown-list-item__control[disabled]~.hds-dropdown-list-item__icon,.hds-dropdown-list-item--variant-checkbox .hds-dropdown-list-item__control[disabled]~.hds-dropdown-list-item__text-content,.hds-dropdown-list-item--variant-radio .hds-dropdown-list-item__control[disabled]~.hds-dropdown-list-item__count,.hds-dropdown-list-item--variant-radio .hds-dropdown-list-item__control[disabled]~.hds-dropdown-list-item__icon,.hds-dropdown-list-item--variant-radio .hds-dropdown-list-item__control[disabled]~.hds-dropdown-list-item__text-content{color:var(--token-color-foreground-disabled)}.hds-dropdown-list-item--variant-checkbox .hds-dropdown-list-item__label,.hds-dropdown-list-item--variant-radio .hds-dropdown-list-item__label{display:flex;flex-grow:1;align-items:flex-start;color:var(--token-color-foreground-primary)}.hds-dropdown-list-item--variant-checkbox .hds-dropdown-list-item__icon,.hds-dropdown-list-item--variant-radio .hds-dropdown-list-item__icon{margin-top:2px;margin-right:4px}.hds-dropdown-list-item__count{margin-left:auto;padding-left:8px;color:var(--token-color-foreground-faint);line-height:20px}.hds-dropdown-list-item--variant-checkbox .hds-badge,.hds-dropdown-list-item--variant-checkmark .hds-badge,.hds-dropdown-list-item--variant-radio .hds-badge{vertical-align:bottom}.hds-flyout{z-index:49;flex-direction:column;height:100vh;max-height:100vh;margin:0;padding:0;background:var(--token-color-surface-primary);border:none;box-shadow:0 2px 3px 0 rgba(59,61,69,.2509803922),0 12px 24px 0 rgba(59,61,69,.3490196078)}.hds-flyout__footer .hds-button-set .hds-button--color-tertiary,.hds-modal__footer .hds-button-set .hds-button--color-tertiary{margin-left:auto}.hds-flyout__body,.hds-flyout__footer{border-top:1px solid var(--token-color-border-primary)}.hds-flyout[open]{position:fixed;display:flex}.hds-flyout::backdrop{display:none}.hds-flyout__overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:49;background:var(--token-color-palette-neutral-700);opacity:.5}.hds-form-masked-input__toggle-button,.hds-form-text-input__visibility-toggle{right:calc(var(--token-form-control-padding) - var(--token-form-control-border-width))}.hds-flyout__header{display:flex;flex:none;gap:16px;align-items:flex-start;padding:16px 24px;color:var(--token-color-foreground-strong)}.hds-flyout__icon{flex:none;align-self:center}.hds-flyout__title{flex-grow:1}.hds-flyout__tagline{margin-bottom:4px}.hds-flyout__dismiss{align-self:center}.hds-flyout__description{padding:0 24px 16px}.hds-flyout__body{flex:1 1 auto;padding:24px;overflow-y:auto;overscroll-behavior:contain}.hds-flyout__footer{flex:none;padding:16px 24px;background:var(--token-color-surface-faint)}.hds-flyout--size-medium{width:min(480px,100vw - 40px);max-width:calc(100vw - 40px)}.hds-flyout--size-medium[open]{margin-left:calc(100% - min(480px,100vw - 40px))}.hds-flyout--size-large{width:min(720px,100vw - 40px);max-width:calc(100vw - 40px)}.hds-flyout--size-large[open]{margin-left:calc(100% - min(720px,100vw - 40px))}.hds-form-label{display:block;width:-moz-max-content;width:max-content;max-width:100%;color:var(--token-form-label-color)}.hds-form-label .hds-badge{vertical-align:initial}.hds-form-helper-text{display:block;color:var(--token-form-helper-text-color)}.hds-form-error{display:flex;gap:8px;align-items:flex-start;color:var(--token-form-error-color)}.hds-form-error__icon{flex:none;width:var(--token-form-error-icon-size);height:var(--token-form-error-icon-size);margin:2px 0}.hds-form-error__content{flex:1 1 auto}.hds-form-error__message{margin:0}.hds-form-field--layout-vertical{display:grid;justify-items:start;width:100%}.hds-form-field--layout-vertical .hds-form-field__label{width:-moz-fit-content;width:fit-content}.hds-form-field--layout-vertical .hds-form-field__helper-text:not(:first-child){margin-top:4px}.hds-form-field--layout-vertical .hds-form-field__helper-text+.hds-form-helper-text{margin-top:2px}.hds-form-field--layout-vertical .hds-form-field__control{display:flex;justify-self:stretch}.hds-form-field--layout-vertical .hds-form-field__control:not(:first-child){margin-top:8px}.hds-form-field--layout-vertical .hds-form-field__control:not(:last-child){margin-bottom:8px}.hds-form-field--layout-flag{display:grid;grid-auto-flow:row;grid-template-areas:"control label" "control helper-text" "control error";grid-template-rows:auto auto auto;grid-template-columns:auto 1fr;justify-items:start}.hds-form-field--layout-flag .hds-form-field__label{grid-area:label;width:-moz-fit-content;width:fit-content}.hds-form-field--layout-flag .hds-form-field__helper-text{grid-area:helper-text;margin-top:4px}.hds-form-field--layout-flag .hds-form-field__control{display:flex;grid-area:control}.hds-form-field--layout-flag .hds-form-field__control:not(:only-child){margin-top:2px;margin-right:8px}.hds-form-field--layout-flag .hds-form-field__error{grid-area:error;margin-top:4px}.hds-form-file-input{margin:-4px 0 -4px -4px;padding:3px 0 3px 3px;color:var(--token-color-foreground-primary)}.hds-form-file-input:focus,.hds-form-file-input:focus-visible{outline:0}.hds-form-file-input::file-selector-button{min-height:36px;margin-right:16px;padding:7px 16px 7px 37px;color:var(--token-color-foreground-primary);font:inherit;background-color:var(--token-color-surface-faint);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' viewBox='0 0 16 16'%3E%3Cg fill='%233b3d45'%3E%3Cpath d='M4.24 5.8a.75.75 0 001.06-.04l1.95-2.1v6.59a.75.75 0 001.5 0V3.66l1.95 2.1a.75.75 0 101.1-1.02l-3.25-3.5a.75.75 0 00-1.101.001L4.2 4.74a.75.75 0 00.04 1.06z'/%3E%3Cpath d='M1.75 9a.75.75 0 01.75.75v3c0 .414.336.75.75.75h9.5a.75.75 0 00.75-.75v-3a.75.75 0 011.5 0v3A2.25 2.25 0 0112.75 15h-9.5A2.25 2.25 0 011 12.75v-3A.75.75 0 011.75 9z'/%3E%3C/g%3E%3C/svg%3E");background-repeat:no-repeat;background-position:15px 50%;background-size:var(--token-form-text-input-background-image-size);border:1px solid var(--token-color-border-strong);border-radius:5px;box-shadow:var(--token-elevation-low-box-shadow);cursor:pointer}.hds-form-group__legend~.hds-form-group__control-fields-wrapper .hds-form-label,.hds-link-standalone{font-weight:var(--token-typography-font-weight-regular)}.hds-form-file-input.mock-hover::file-selector-button,.hds-form-file-input::file-selector-button:hover{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-primary);border-color:var(--token-color-border-strong)}.hds-form-file-input.mock-focus::file-selector-button,.hds-form-file-input:focus-within::file-selector-button{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-focus-action-internal);outline:var(--token-color-focus-action-external) solid 3px}.hds-form-file-input:not(:focus,.mock-focus)::file-selector-button{border-color:var(--token-color-border-strong);outline:0}.hds-form-file-input.mock-active::file-selector-button,.hds-form-file-input::file-selector-button:active{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong);box-shadow:none}.hds-form-file-input.mock-disabled,.hds-form-file-input.mock-disabled:focus,.hds-form-file-input.mock-disabled:hover,.hds-form-file-input:disabled,.hds-form-file-input:disabled:focus,.hds-form-file-input:disabled:hover,.hds-form-file-input[disabled],.hds-form-file-input[disabled]:focus,.hds-form-file-input[disabled]:hover{color:var(--token-color-foreground-disabled)}.hds-form-file-input.mock-disabled::file-selector-button,.hds-form-file-input.mock-disabled:focus::file-selector-button,.hds-form-file-input.mock-disabled:hover::file-selector-button,.hds-form-file-input:disabled::file-selector-button,.hds-form-file-input:disabled:focus::file-selector-button,.hds-form-file-input:disabled:hover::file-selector-button,.hds-form-file-input[disabled]::file-selector-button,.hds-form-file-input[disabled]:focus::file-selector-button,.hds-form-file-input[disabled]:hover::file-selector-button{color:var(--token-color-foreground-disabled);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary);box-shadow:none;cursor:not-allowed;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' viewBox='0 0 16 16'%3E%3Cg fill='%238c909c'%3E%3Cpath d='M4.24 5.8a.75.75 0 001.06-.04l1.95-2.1v6.59a.75.75 0 001.5 0V3.66l1.95 2.1a.75.75 0 101.1-1.02l-3.25-3.5a.75.75 0 00-1.101.001L4.2 4.74a.75.75 0 00.04 1.06z'/%3E%3Cpath d='M1.75 9a.75.75 0 01.75.75v3c0 .414.336.75.75.75h9.5a.75.75 0 00.75-.75v-3a.75.75 0 011.5 0v3A2.25 2.25 0 0112.75 15h-9.5A2.25 2.25 0 011 12.75v-3A.75.75 0 011.75 9z'/%3E%3C/g%3E%3C/svg%3E")}.hds-form-file-input.mock-disabled::file-selector-button::before,.hds-form-file-input.mock-disabled:focus::file-selector-button::before,.hds-form-file-input.mock-disabled:hover::file-selector-button::before,.hds-form-file-input:disabled::file-selector-button::before,.hds-form-file-input:disabled:focus::file-selector-button::before,.hds-form-file-input:disabled:hover::file-selector-button::before,.hds-form-file-input[disabled]::file-selector-button::before,.hds-form-file-input[disabled]:focus::file-selector-button::before,.hds-form-file-input[disabled]:hover::file-selector-button::before{border-color:transparent}.hds-form-legend{display:block;color:var(--token-form-legend-color)}.hds-form-legend .hds-badge{vertical-align:initial}.hds-form-group{display:block;margin:0;padding:0;border:none}.hds-form-checkbox,.hds-form-radio{background-position:center center;border-style:solid;-moz-appearance:none}.hds-form-group__legend{margin:0 0 4px;padding:0}.hds-form-group--layout-vertical .hds-form-group__control-fields-wrapper{display:flex;flex-direction:column}.hds-form-group--layout-vertical .hds-form-group__control-field+.hds-form-group__control-field{margin-top:12px}.hds-form-group--layout-horizontal .hds-form-group__control-fields-wrapper{display:flex;flex-wrap:wrap;margin-bottom:-4px}.hds-form-group--layout-horizontal .hds-form-group__control-field{margin-right:16px;margin-bottom:4px}.hds-form-group__helper-text{margin-bottom:8px}.hds-form-group__error{margin-top:8px}.hds-form-indicator--optional{color:var(--token-form-indicator-optional-color)}.hds-form-checkbox{width:var(--token-form-checkbox-size);height:var(--token-form-checkbox-size);margin:0;padding:0;background-size:var(--token-form-checkbox-background-image-size) var(--token-form-checkbox-background-image-size);border-width:var(--token-form-checkbox-border-width);border-radius:var(--token-form-checkbox-border-radius);cursor:pointer;-webkit-appearance:none;appearance:none}.hds-form-checkbox:not(:checked,:indeterminate){background-color:var(--token-form-control-base-surface-color-default);border-color:var(--token-form-control-base-border-color-default)}.hds-form-checkbox:checked,.hds-form-checkbox:indeterminate{background-color:var(--token-form-control-checked-surface-color-default);border-color:var(--token-form-control-checked-border-color-default)}.hds-form-checkbox:checked{background-image:var(--token-form-checkbox-background-image-data-url)}.hds-form-checkbox:indeterminate{background-image:var(--token-form-checkbox-background-image-data-url-indeterminate)}.hds-form-checkbox.mock-hover:not(:checked,:indeterminate),.hds-form-checkbox:hover:not(:checked,:indeterminate){background-color:var(--token-form-control-base-surface-color-hover);border-color:var(--token-form-control-base-border-color-hover)}.hds-form-checkbox.mock-hover:checked,.hds-form-checkbox.mock-hover:indeterminate,.hds-form-checkbox:hover:checked,.hds-form-checkbox:hover:indeterminate{background-color:var(--token-form-control-checked-border-color-default);border-color:var(--token-form-control-checked-border-color-hover)}.hds-form-checkbox:disabled:checked,.hds-form-checkbox:disabled:indeterminate,.hds-form-checkbox:disabled:not(:checked,:indeterminate){background-color:var(--token-form-control-disabled-surface-color);border-color:var(--token-form-control-disabled-border-color);box-shadow:none;cursor:not-allowed}.hds-form-checkbox.mock-focus,.hds-form-checkbox:focus{outline:var(--token-color-focus-action-external) solid 3px;outline-offset:1px}.hds-form-checkbox:disabled:checked{background-image:var(--token-form-checkbox-background-image-data-url-disabled)}.hds-form-checkbox:disabled:indeterminate{background-image:var(--token-form-checkbox-background-image-data-url-indeterminate-disabled);background-repeat:no-repeat}.hds-form-masked-input{position:relative;display:grid;grid-template-areas:"input copy-button";grid-template-columns:1fr auto;width:100%}.hds-form-masked-input .hds-form-masked-input__control{grid-area:input;padding-right:calc(var(--token-form-control-padding) + 24px)}.hds-form-masked-input--is-masked .hds-form-masked-input__control{-webkit-text-security:disc}.hds-form-masked-input--is-not-masked .hds-form-masked-input__control{-webkit-text-security:none}.hds-form-masked-input__toggle-button{position:absolute;top:calc(var(--token-form-control-padding) - var(--token-form-control-border-width));grid-area:input}.hds-form-masked-input__copy-button{grid-area:copy-button;align-self:flex-start;margin-left:8px}.hds-form-radio{width:var(--token-form-radio-size);height:var(--token-form-radio-size);margin:0;padding:0;background-size:var(--token-form-radio-background-image-size) var(--token-form-radio-background-image-size);border-width:var(--token-form-radio-border-width);border-radius:50%;cursor:pointer;-webkit-appearance:none;appearance:none}.hds-form-radio:not(:checked){background-color:var(--token-form-control-base-surface-color-default);border-color:var(--token-form-control-base-border-color-default);box-shadow:var(--token-elevation-inset-box-shadow)}.hds-form-radio:checked{background-color:var(--token-form-control-checked-surface-color-default);background-image:var(--token-form-radio-background-image-data-url);border-color:var(--token-form-control-checked-border-color-default)}.hds-form-radio.mock-hover:not(:checked),.hds-form-radio:hover:not(:checked){background-color:var(--token-form-control-base-surface-color-hover);border-color:var(--token-form-control-base-border-color-hover)}.hds-form-radio.mock-hover:checked,.hds-form-radio:hover:checked{background-color:var(--token-form-control-checked-border-color-default);border-color:var(--token-form-control-checked-border-color-hover)}.hds-form-radio:disabled:checked,.hds-form-radio:disabled:not(:checked){background-color:var(--token-form-control-disabled-surface-color);border-color:var(--token-form-control-disabled-border-color);box-shadow:none;cursor:not-allowed}.hds-form-radio.mock-focus,.hds-form-radio:focus{outline:var(--token-color-focus-action-external) solid 3px;outline-offset:1px}.hds-form-radio:disabled:checked{background-image:var(--token-form-radio-background-image-data-url-disabled)}.hds-form-group--radio-cards .hds-form-group__control-fields-wrapper{margin:calc(-1 * var(--token-form-radiocard-group-gap)/ 2)}.hds-form-group--radio-cards .hds-form-group__legend{margin-bottom:12px}.hds-form-group--radio-cards .hds-form-radio-card{margin:calc(var(--token-form-radiocard-group-gap)/ 2)}.hds-form-group--radio-cards .hds-form-radio-card--has-fixed-width{flex:1 0 100%}.hds-form-radio-card{display:flex;flex-direction:column;background-color:var(--token-color-surface-primary);border:var(--token-form-radiocard-border-width) solid var(--token-color-border-primary);border-radius:var(--token-form-radiocard-border-radius);box-shadow:var(--token-elevation-mid-box-shadow);cursor:pointer}.hds-form-radio-card .hds-form-radio-card__control{outline-color:transparent}.hds-form-radio-card.mock-hover,.hds-form-radio-card:hover{box-shadow:var(--token-elevation-high-box-shadow);transition:var(--token-form-radiocard-transition-duration)}.hds-form-radio-card.mock-focus,.hds-form-radio-card:focus-within{border-color:var(--token-color-focus-action-internal);box-shadow:0 0 0 3px var(--token-color-focus-action-external)}.hds-form-radio-card--checked,.hds-form-radio-card.mock-checked{border-color:var(--token-color-focus-action-internal)}.hds-form-radio-card--checked .hds-form-radio-card__control-wrapper,.hds-form-radio-card.mock-checked .hds-form-radio-card__control-wrapper{background-color:var(--token-color-surface-action);border-color:var(--token-color-border-action)}.hds-form-radio-card--disabled,.hds-form-radio-card--disabled .hds-form-radio-card__control-wrapper,.hds-form-radio-card.mock-disabled,.hds-form-radio-card.mock-disabled .hds-form-radio-card__control-wrapper{background-color:var(--token-color-surface-interactive-disabled);border-color:var(--token-color-border-primary)}.hds-form-radio-card--disabled,.hds-form-radio-card.mock-disabled{box-shadow:none;cursor:not-allowed}.hds-form-radio-card--align-left{text-align:left}.hds-form-radio-card--align-center{text-align:center}.hds-form-radio-card--align-center .flight-icon{margin:auto}.hds-form-radio-card--control-bottom .hds-form-radio-card__control-wrapper{border-top-width:var(--token-form-radiocard-border-width);border-top-style:solid;border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.hds-form-radio-card--control-left{flex-direction:row-reverse}.hds-form-radio-card--control-left .hds-form-radio-card__control-wrapper{display:flex;align-items:center;border-right-width:var(--token-form-radiocard-border-width);border-right-style:solid;border-top-left-radius:inherit;border-bottom-left-radius:inherit}.hds-form-radio-card__content{flex:1;padding:var(--token-form-radiocard-content-padding)}.hds-form-radio-card__content .hds-badge{margin-bottom:12px}.hds-form-radio-card__label{display:block;margin:8px 0;color:var(--token-form-label-color);overflow-wrap:break-word}.hds-form-radio-card__label:first-child{margin-top:0}.hds-form-radio-card__description{display:block;color:var(--token-color-foreground-primary)}.hds-form-radio-card__control-wrapper{padding:var(--token-form-radiocard-control-padding);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary)}.hds-form-select,.hds-form-text-input{border:var(--token-form-control-border-width) solid var(--token-form-control-base-border-color-default)}.hds-form-radio-card__control{display:block;margin:auto}.hds-form-select{max-width:100%;padding:var(--token-form-control-padding);padding-right:calc(var(--token-form-control-padding) + 24px);color:var(--token-form-control-base-foreground-value-color);background-color:var(--token-form-control-base-surface-color-default);background-image:var(--token-form-select-background-image-data-url);background-repeat:no-repeat;background-position:right var(--token-form-select-background-image-position-right-x) top var(--token-form-select-background-image-position-top-y);background-size:var(--token-form-select-background-image-size) var(--token-form-select-background-image-size);border-radius:var(--token-form-control-border-radius);box-shadow:var(--token-elevation-low-box-shadow);-webkit-appearance:none;-moz-appearance:none;appearance:none}.hds-form-select.mock-hover,.hds-form-select:hover{border-color:var(--token-form-control-base-border-color-hover)}.hds-form-select.mock-focus,.hds-form-select:focus{border-color:var(--token-color-focus-action-internal);outline:var(--token-color-focus-action-external) solid 3px;outline-offset:0}.hds-form-select:disabled{color:var(--token-form-control-disabled-foreground-color);background-color:var(--token-form-control-disabled-surface-color);background-image:var(--token-form-select-background-image-data-url-disabled);border-color:var(--token-form-control-disabled-border-color);box-shadow:none;cursor:not-allowed}.hds-form-select.hds-form-select--is-invalid{border-color:var(--token-form-control-invalid-border-color-default)}.hds-form-select.hds-form-select--is-invalid.mock-hover,.hds-form-select.hds-form-select--is-invalid:hover{border-color:var(--token-form-control-invalid-border-color-hover)}.hds-form-select.hds-form-select--is-invalid.mock-focus,.hds-form-select.hds-form-select--is-invalid:focus{border-color:var(--token-color-focus-critical-internal);outline-color:var(--token-color-focus-critical-external)}.hds-form-select[multiple],.hds-form-select[size]{background:0 0}.hds-form-select[multiple] option,.hds-form-select[size] option{margin:2px auto;border-radius:3px}.hds-form-select[multiple] option:hover,.hds-form-select[size] option:hover{color:var(--token-color-foreground-action)}.hds-form-select[multiple] option:disabled,.hds-form-select[size] option:disabled{color:var(--token-color-foreground-disabled)}.hds-form-select[multiple] option:checked,.hds-form-select[size] option:checked{color:var(--token-color-foreground-high-contrast);background:var(--token-color-palette-blue-200)}.hds-form-text-input,.hds-form-textarea{background-color:var(--token-form-control-base-surface-color-default);max-width:100%}.hds-form-select[multiple] optgroup,.hds-form-select[size] optgroup{color:var(--token-color-foreground-strong);font-weight:var(--token-typography-font-weight-semibold);font-style:normal}.hds-form-text-input{width:100%;padding:var(--token-form-control-padding);color:var(--token-form-control-base-foreground-value-color);border-radius:var(--token-form-control-border-radius);box-shadow:var(--token-elevation-inset-box-shadow)}.hds-form-text-input ::-moz-placeholder{color:var(--token-form-control-base-foreground-placeholder-color)}.hds-form-text-input ::placeholder{color:var(--token-form-control-base-foreground-placeholder-color)}.hds-form-text-input.mock-hover,.hds-form-text-input:hover{border-color:var(--token-form-control-base-border-color-hover)}.hds-form-text-input.mock-focus,.hds-form-text-input:focus{border-color:var(--token-color-focus-action-internal);outline:var(--token-color-focus-action-external) solid 3px;outline-offset:0}.hds-form-text-input:-moz-read-only{color:var(--token-form-control-readonly-foreground-color);background-color:var(--token-form-control-readonly-surface-color);border-color:var(--token-form-control-readonly-border-color);box-shadow:none}.hds-form-text-input:read-only{color:var(--token-form-control-readonly-foreground-color);background-color:var(--token-form-control-readonly-surface-color);border-color:var(--token-form-control-readonly-border-color);box-shadow:none}.hds-form-text-input:disabled{color:var(--token-form-control-disabled-foreground-color);background-color:var(--token-form-control-disabled-surface-color);border-color:var(--token-form-control-disabled-border-color);box-shadow:none;cursor:not-allowed}.hds-form-text-input.hds-form-text-input--is-invalid{border-color:var(--token-form-control-invalid-border-color-default)}.hds-form-text-input.hds-form-text-input--is-invalid.mock-hover,.hds-form-text-input.hds-form-text-input--is-invalid:hover{border-color:var(--token-form-control-invalid-border-color-hover)}.hds-form-text-input.hds-form-text-input--is-invalid.mock-focus,.hds-form-text-input.hds-form-text-input--is-invalid:focus{border-color:var(--token-color-focus-critical-internal);outline-color:var(--token-color-focus-critical-external)}.hds-form-text-input__wrapper{position:relative;width:100%}.hds-form-text-input--has-visibility-toggle{padding-right:calc(var(--token-form-control-padding) + 24px)}.hds-form-text-input__visibility-toggle{position:absolute;top:calc(var(--token-form-control-padding) - var(--token-form-control-border-width))}.hds-form-text-input[type=date],.hds-form-text-input[type=datetime-local],.hds-form-text-input[type=time]{width:initial}.hds-form-text-input[type=date]:disabled::-webkit-calendar-picker-indicator,.hds-form-text-input[type=datetime-local]:disabled::-webkit-calendar-picker-indicator,.hds-form-text-input[type=time]:disabled::-webkit-calendar-picker-indicator{visibility:visible;opacity:.5}.hds-form-text-input[type=date][readonly]::-webkit-calendar-picker-indicator,.hds-form-text-input[type=datetime-local][readonly]::-webkit-calendar-picker-indicator,.hds-form-text-input[type=time][readonly]::-webkit-calendar-picker-indicator{visibility:visible}.hds-form-text-input[type=date]::-webkit-calendar-picker-indicator,.hds-form-text-input[type=datetime-local]::-webkit-calendar-picker-indicator{background-image:var(--token-form-text-input-background-image-data-url-date);background-position:center center;background-size:var(--token-form-text-input-background-image-size)}.hds-form-text-input[type=time]::-webkit-calendar-picker-indicator{background-image:var(--token-form-text-input-background-image-data-url-time);background-position:center center;background-size:var(--token-form-text-input-background-image-size)}.hds-form-text-input[type=search]{padding-left:calc(var(--token-form-control-padding) + 24px);background-image:var(--token-form-text-input-background-image-data-url-search);background-repeat:no-repeat;background-position:var(--token-form-text-input-background-image-position-x) 50%;background-size:var(--token-form-text-input-background-image-size)}.hds-form-text-input[type=search]::-webkit-search-cancel-button{width:var(--token-form-text-input-background-image-size);height:var(--token-form-text-input-background-image-size);background-image:var(--token-form-text-input-background-image-data-url-search-cancel);background-position:center center;background-size:var(--token-form-text-input-background-image-size);-webkit-appearance:none}.hds-form-text-input[type=search].hds-form-text-input--is-loading{background-image:var(--token-form-text-input-background-image-data-url-search-loading)}.hds-form-textarea{width:100%;min-height:36px;padding:var(--token-form-control-padding);color:var(--token-form-control-base-foreground-value-color);border:var(--token-form-control-border-width) solid var(--token-form-control-base-border-color-default);border-radius:var(--token-form-control-border-radius);box-shadow:var(--token-elevation-inset-box-shadow);resize:vertical}.hds-form-textarea ::-moz-placeholder{color:var(--token-form-control-base-foreground-placeholder-color)}.hds-form-textarea ::placeholder{color:var(--token-form-control-base-foreground-placeholder-color)}.hds-form-textarea.mock-hover,.hds-form-textarea:hover{border-color:var(--token-form-control-base-border-color-hover)}.hds-form-textarea.mock-focus,.hds-form-textarea:focus{border-color:var(--token-color-focus-action-internal);outline:var(--token-color-focus-action-external) solid 3px;outline-offset:0}.hds-form-textarea:-moz-read-only{color:var(--token-form-control-readonly-foreground-color);background-color:var(--token-form-control-readonly-surface-color);border-color:var(--token-form-control-readonly-border-color);box-shadow:none}.hds-form-textarea:read-only{color:var(--token-form-control-readonly-foreground-color);background-color:var(--token-form-control-readonly-surface-color);border-color:var(--token-form-control-readonly-border-color);box-shadow:none}.hds-form-textarea:disabled{color:var(--token-form-control-disabled-foreground-color);background-color:var(--token-form-control-disabled-surface-color);border-color:var(--token-form-control-disabled-border-color);box-shadow:none;cursor:not-allowed}.hds-form-textarea.hds-form-textarea--is-invalid{border-color:var(--token-form-control-invalid-border-color-default)}.hds-form-textarea.hds-form-textarea--is-invalid.mock-hover,.hds-form-textarea.hds-form-textarea--is-invalid:hover{border-color:var(--token-form-control-invalid-border-color-hover)}.hds-form-textarea.hds-form-textarea--is-invalid.mock-focus,.hds-form-textarea.hds-form-textarea--is-invalid:focus{border-color:var(--token-color-focus-critical-internal);outline-color:var(--token-color-focus-critical-external)}.hds-form-toggle{position:relative;isolation:isolate}.hds-form-toggle__control{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;display:block;height:100%;margin:0;padding:0;color:transparent;background-color:transparent;border:none;outline:0;cursor:pointer;opacity:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.hds-form-toggle__control:disabled{cursor:not-allowed}.hds-form-toggle__facade{position:relative;display:block;width:var(--token-form-toggle-width);height:var(--token-form-toggle-height);background-image:var(--token-form-toggle-background-image-data-url);background-repeat:no-repeat;background-position:var(--token-form-toggle-background-image-position-x) 50%;background-size:var(--token-form-toggle-background-image-size) var(--token-form-toggle-background-image-size);border:var(--token-form-radio-border-width) solid var(--border-color);border-radius:calc(var(--token-form-toggle-height)/ 2)}.hds-form-toggle__facade::after{position:absolute;top:calc(var(--token-form-radio-border-width) * -1);left:calc(var(--token-form-radio-border-width) * -1);width:var(--token-form-toggle-thumb-size);height:var(--token-form-toggle-thumb-size);background-color:var(--token-form-control-base-surface-color-default);border:var(--token-form-radio-border-width) solid var(--border-color);border-radius:50%;transform:translate3d(0,0,0);content:""}@media (prefers-reduced-motion:no-preference){.hds-form-toggle__facade,.hds-form-toggle__facade::after{transition-timing-function:var(--token-form-toggle-transition-timing-function);transition-duration:var(--token-form-toggle-transition-duration);transition-property:all}}.hds-form-toggle__facade::before{position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;margin:auto;border-width:3px;border-radius:calc(var(--token-form-toggle-height)/ 2 + 3px + 1px);content:""}:not(:checked)+.hds-form-toggle__facade{--border-color:var(--token-form-control-base-border-color-default);background-color:var(--token-form-toggle-base-surface-color-default)}:checked+.hds-form-toggle__facade{--border-color:var(--token-form-control-checked-border-color-default);background-color:var(--token-form-control-checked-surface-color-default)}:checked+.hds-form-toggle__facade::after{transform:translate3d(calc(var(--token-form-toggle-width) - var(--token-form-toggle-thumb-size)),0,0)}.mock-hover:not(:checked)+.hds-form-toggle__facade,:hover:not(:checked)+.hds-form-toggle__facade{--border-color:var(--token-form-control-base-border-color-hover)}.mock-hover:checked+.hds-form-toggle__facade,:hover:checked+.hds-form-toggle__facade{--border-color:var(--token-form-control-checked-border-color-hover);background-color:var(--token-form-control-checked-border-color-default)}.mock-focus+.hds-form-toggle__facade::before,:focus+.hds-form-toggle__facade::before{border-color:var(--token-color-focus-action-external);border-style:solid}:disabled:checked+.hds-form-toggle__facade,:disabled:not(:checked)+.hds-form-toggle__facade{--border-color:var(--token-form-control-disabled-border-color);background-color:var(--token-form-control-disabled-surface-color);background-image:var(--token-form-toggle-background-image-data-url-disabled)}.hds-form-visibility-toggle{width:24px;height:24px;padding:2px;color:var(--token-color-foreground-primary);background-color:transparent;border-color:transparent;cursor:pointer}.hds-icon-tile--logo,.hds-icon-tile__extra{background-color:var(--token-color-surface-primary)}.hds-icon-tile{position:relative;display:flex;border:1px solid transparent;border-radius:4px;box-shadow:0 1px 1px rgba(101,106,118,.05)}.hds-icon-tile__icon,.hds-icon-tile__logo{display:flex;margin:auto}.hds-icon-tile__extra{position:absolute;right:-6px;bottom:-6px;display:flex;box-sizing:content-box;border:1px solid var(--token-color-border-primary);box-shadow:0 1px 1px rgba(101,106,118,.05)}.hds-icon-tile__extra-icon{display:flex;margin:auto;color:var(--token-color-foreground-strong)}.hds-icon-tile--size-small{width:1.75rem;height:1.75rem;border-radius:5px}.hds-icon-tile--size-small .hds-icon-tile__icon{width:1rem;height:1rem}.hds-icon-tile--size-small .hds-icon-tile__logo{width:1.125rem;height:1.125rem}.hds-icon-tile--size-small .hds-icon-tile__extra{width:1.125rem;height:1.125rem;border-radius:4px}.hds-icon-tile--size-small .hds-icon-tile__extra-icon{width:.75rem;height:.75rem}.hds-icon-tile--size-medium{width:2.5rem;height:2.5rem;border-radius:6px}.hds-icon-tile--size-medium .hds-icon-tile__icon{width:1.5rem;height:1.5rem}.hds-icon-tile--size-medium .hds-icon-tile__logo{width:1.75rem;height:1.75rem}.hds-icon-tile--size-medium .hds-icon-tile__extra{width:1.5rem;height:1.5rem;border-radius:5px}.hds-icon-tile--size-medium .hds-icon-tile__extra-icon{width:1rem;height:1rem}.hds-icon-tile--size-large{width:3rem;height:3rem;border-radius:6px}.hds-icon-tile--size-large .hds-icon-tile__icon{width:1.5rem;height:1.5rem}.hds-icon-tile--size-large .hds-icon-tile__logo{width:2rem;height:2rem}.hds-icon-tile--size-large .hds-icon-tile__extra{width:1.5rem;height:1.5rem;border-radius:5px}.hds-icon-tile--size-large .hds-icon-tile__extra-icon{width:1rem;height:1rem}.hds-icon-tile--logo{border-color:var(--token-color-border-primary)}.hds-icon-tile--icon.hds-icon-tile--color-neutral{color:var(--token-color-foreground-faint);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary)}.hds-icon-tile--icon.hds-icon-tile--color-boundary{color:var(--token-color-boundary-foreground);background:linear-gradient(135deg,var(--token-color-boundary-gradient-faint-start) 0,var(--token-color-boundary-gradient-faint-stop) 100%);border-color:var(--token-color-boundary-border)}.hds-icon-tile--icon.hds-icon-tile--color-consul{color:var(--token-color-consul-foreground);background:linear-gradient(135deg,var(--token-color-consul-gradient-faint-start) 0,var(--token-color-consul-gradient-faint-stop) 100%);border-color:var(--token-color-consul-border)}.hds-icon-tile--icon.hds-icon-tile--color-hcp{color:var(--token-color-palette-hcp-brand);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary)}.hds-icon-tile--icon.hds-icon-tile--color-nomad{color:var(--token-color-nomad-foreground);background:linear-gradient(135deg,var(--token-color-nomad-gradient-faint-start) 0,var(--token-color-nomad-gradient-faint-stop) 100%);border-color:var(--token-color-nomad-border)}.hds-icon-tile--icon.hds-icon-tile--color-packer{color:var(--token-color-packer-foreground);background:linear-gradient(135deg,var(--token-color-packer-gradient-faint-start) 0,var(--token-color-packer-gradient-faint-stop) 100%);border-color:var(--token-color-packer-border)}.hds-icon-tile--icon.hds-icon-tile--color-terraform{color:var(--token-color-terraform-foreground);background:linear-gradient(135deg,var(--token-color-terraform-gradient-faint-start) 0,var(--token-color-terraform-gradient-faint-stop) 100%);border-color:var(--token-color-terraform-border)}.hds-icon-tile--icon.hds-icon-tile--color-vagrant{color:var(--token-color-vagrant-foreground);background:linear-gradient(135deg,var(--token-color-vagrant-gradient-faint-start) 0,var(--token-color-vagrant-gradient-faint-stop) 100%);border-color:var(--token-color-vagrant-border)}.hds-icon-tile--icon.hds-icon-tile--color-vault{color:var(--token-color-vault-foreground);background:linear-gradient(135deg,var(--token-color-vault-gradient-faint-start) 0,var(--token-color-vault-gradient-faint-stop) 100%);border-color:var(--token-color-vault-border)}.hds-icon-tile--icon.hds-icon-tile--color-vault-secrets{color:var(--token-color-vault-secrets-foreground);background:linear-gradient(135deg,var(--token-color-vault-secrets-gradient-faint-start) 0,var(--token-color-vault-secrets-gradient-faint-stop) 100%);border-color:var(--token-color-vault-secrets-border)}.hds-icon-tile--icon.hds-icon-tile--color-waypoint{color:var(--token-color-waypoint-foreground);background:linear-gradient(135deg,var(--token-color-waypoint-gradient-faint-start) 0,var(--token-color-waypoint-gradient-faint-stop) 100%);border-color:var(--token-color-waypoint-border)}.hds-link-inline{border-radius:2px}.hds-link-inline.mock-focus,.hds-link-inline:focus,.hds-link-inline:focus-visible{text-decoration:none;outline:var(--token-color-focus-action-internal) solid 2px;outline-offset:1px}.hds-link-inline__icon{display:inline-block;width:1em;height:1em;vertical-align:text-bottom}.hds-link-inline--icon-leading>.hds-link-inline__icon{margin-right:.25em}.hds-link-inline--icon-trailing>.hds-link-inline__icon{margin-left:.25em}.hds-link-inline--color-primary{color:var(--token-color-foreground-action)}.hds-link-inline--color-primary.mock-hover,.hds-link-inline--color-primary:hover{color:var(--token-color-foreground-action-hover)}.hds-link-inline--color-primary.mock-active,.hds-link-inline--color-primary:active{color:var(--token-color-foreground-action-active)}.hds-link-inline--color-secondary{color:var(--token-color-foreground-strong)}.hds-link-inline--color-secondary.mock-hover,.hds-link-inline--color-secondary:hover{color:var(--token-color-foreground-primary)}.hds-link-inline--color-secondary.mock-active,.hds-link-inline--color-secondary:active{color:var(--token-color-foreground-faint)}.hds-link-standalone{display:flex;align-items:center;justify-content:center;width:-moz-fit-content;width:fit-content;padding-top:4px;padding-bottom:4px;background-color:transparent;border:1px solid transparent;text-decoration-color:transparent;position:relative;outline-style:solid;outline-color:transparent}.hds-link-standalone__text{flex:1 0 0;text-decoration:underline;text-decoration-color:transparent;transition:text-decoration-color .25s ease-in}#login-toggle+div footer button,.consul-intention-fieldsets .permissions>button,.empty-state>ul>li>*,.empty-state>ul>li>:active,.empty-state>ul>li>label>button,.empty-state>ul>li>label>button:active,.hds-pagination-nav__control,.hds-side-nav__list-item-link,.hds-tabs__tab,.modal-dialog [role=document] dd a,.modal-dialog [role=document] p a,.oidc-select button.reset,.search-bar-status .remove-all button,a,main dd a,main dd a:active,main p a,main p a:active{text-decoration:none}.hds-link-standalone--size-small .hds-link-standalone__icon{width:.75rem;height:.75rem}.hds-link-standalone--size-small .hds-link-standalone__text{font-size:.8125rem;line-height:1.231}.hds-link-standalone--size-medium .hds-link-standalone__icon{width:1rem;height:1rem}.hds-link-standalone--size-medium .hds-link-standalone__text{font-size:.875rem;line-height:1.143}.hds-link-standalone--size-large .hds-link-standalone__icon{width:1.5rem;height:1.5rem}.hds-link-standalone--size-large .hds-link-standalone__text{font-size:1rem;line-height:1.5}.hds-link-standalone--color-primary{color:var(--token-color-foreground-action)}.hds-link-standalone--color-primary.mock-hover,.hds-link-standalone--color-primary:hover{color:var(--token-color-foreground-action-hover)}.hds-link-standalone--color-primary.mock-hover .hds-link-standalone__text,.hds-link-standalone--color-primary:hover .hds-link-standalone__text{text-decoration-color:#4e81e8}.hds-link-standalone--color-primary.mock-active,.hds-link-standalone--color-primary:active{color:var(--token-color-foreground-action-active)}.hds-link-standalone--color-primary.mock-active .hds-link-standalone__text,.hds-link-standalone--color-primary:active .hds-link-standalone__text{text-decoration-color:#396ed6}.hds-link-standalone--color-primary.mock-active::before,.hds-link-standalone--color-primary:active::before{background-color:var(--token-color-surface-action)}.hds-link-standalone--color-secondary{color:var(--token-color-foreground-strong)}.hds-link-standalone--color-secondary.mock-hover .hds-link-standalone__text,.hds-link-standalone--color-secondary:hover .hds-link-standalone__text{text-decoration-color:#4d4d4f}.hds-link-standalone--color-secondary.mock-active,.hds-link-standalone--color-secondary:active{color:var(--token-color-foreground-primary)}.hds-link-standalone--color-secondary.mock-active .hds-link-standalone__text,.hds-link-standalone--color-secondary:active .hds-link-standalone__text{text-decoration-color:#6e7075}.hds-link-standalone--color-secondary.mock-active::before,.hds-link-standalone--color-secondary:active::before{background-color:var(--token-color-surface-interactive-active)}.hds-link-standalone::before{position:absolute;top:0;right:-5px;bottom:0;left:-5px;z-index:-1;border-radius:5px;content:""}.hds-link-standalone:focus:not(:focus-visible)::before{box-shadow:none}.hds-link-standalone:focus-visible::before,.hds-pagination-nav__control.mock-focus::before,.hds-pagination-nav__control:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-link-standalone.mock-focus.mock-active::before,.hds-link-standalone:focus:active::before{box-shadow:none}.hds-link-standalone.hds-link-standalone--icon-position-leading::before{right:-7px}.hds-link-standalone.hds-link-standalone--icon-position-trailing::before{left:-7px}.hds-menu-primitive{position:relative;width:-moz-fit-content;width:fit-content}.hds-modal{z-index:50;flex-direction:column;padding:0;background:var(--token-color-surface-primary);border:none;border-radius:8px;box-shadow:var(--token-surface-overlay-box-shadow)}.hds-modal[open]{position:fixed;display:flex}.hds-modal::backdrop{display:none}.hds-modal__overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:50;background:var(--token-color-palette-neutral-700);opacity:.5}.hds-modal__header{display:flex;flex:none;gap:16px;align-items:flex-start;padding:16px 24px;border-top-left-radius:inherit;border-top-right-radius:inherit}.hds-modal__icon{flex:none;align-self:center}.freetext-filter>label,.freetext-filter_input,.hds-modal__title{flex-grow:1}.hds-modal__tagline{margin-bottom:4px}.hds-modal__dismiss{align-self:center}.hds-modal__body{flex:1 1 auto;padding:24px;overflow-y:auto;overscroll-behavior:contain}.hds-modal__footer{flex:none;padding:16px 24px;background:var(--token-color-surface-faint);border-top:1px solid var(--token-color-border-primary);border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.hds-modal--size-small{width:min(400px,95vw)}.hds-modal--size-medium{width:min(600px,95vw)}.hds-modal--size-large{width:min(800px,95vw)}.hds-modal--color-neutral .hds-modal__header{color:var(--token-color-foreground-strong);background:var(--token-color-surface-faint);border-bottom:1px solid var(--token-color-border-primary)}.hds-modal--color-neutral .hds-modal__tagline{color:var(--token-color-foreground-faint)}.hds-modal--color-warning .hds-modal__header,.hds-modal--color-warning .hds-modal__tagline{color:var(--token-color-foreground-warning-on-surface)}.hds-modal--color-warning .hds-modal__header{background:var(--token-color-surface-warning);border-bottom:1px solid var(--token-color-border-warning)}.hds-modal--color-critical .hds-modal__header,.hds-modal--color-critical .hds-modal__tagline{color:var(--token-color-foreground-critical-on-surface)}.hds-modal--color-critical .hds-modal__header{background:var(--token-color-surface-critical);border-bottom:1px solid var(--token-color-border-critical)}.hds-page-header{position:relative;display:flex;flex-direction:column;gap:16px;container-type:inline-size}.hds-page-header__body{display:flex;flex-direction:column;gap:8px 16px}.hds-page-header__body .hds-icon-tile{flex-shrink:0}@container (min-width:400px){.hds-page-header__body{flex-direction:row}}.hds-page-header__main{display:flex;flex-direction:column;flex-grow:1;gap:16px;align-items:start;justify-content:start}@container (min-width:768px){.hds-page-header__main{flex-direction:row;justify-content:space-between;min-width:0}}.hds-page-header__content{display:flex;flex-direction:column;flex-grow:1;gap:8px;min-width:0;max-width:100%}.hds-page-header__title-wrapper{display:flex;flex-direction:row;flex-wrap:wrap;gap:8px 16px;align-items:center}.hds-page-header__title{max-width:100%;overflow-wrap:break-word}.hds-page-header__badges-wrapper{display:flex;flex-wrap:wrap;gap:8px}.hds-page-header__metadata{display:flex;flex-direction:column;gap:4px}.hds-page-header__description,.hds-page-header__subtitle{overflow-wrap:break-word}.hds-page-header__actions{display:flex;flex-direction:row;flex-shrink:0;flex-wrap:wrap;gap:16px;align-items:center}.hds-pagination{display:grid;grid-template-areas:"info nav selector";grid-template-rows:auto;grid-template-columns:1fr auto 1fr;align-items:center;margin:0 auto}@media screen and (max-width:1000px){.hds-pagination{display:flex;flex-wrap:wrap;justify-content:center}.hds-pagination .hds-pagination-info{margin-top:var(--token-pagination-child-spacing-vertical);margin-left:var(--token-pagination-child-spacing-horizontal)}}.hds-pagination .hds-pagination-info{grid-area:info;justify-self:flex-start;margin-right:var(--token-pagination-child-spacing-horizontal)}.hds-pagination .hds-pagination-nav{grid-area:nav}@media screen and (max-width:1000px){.hds-pagination .hds-pagination-nav{justify-content:center;order:-1;width:100%}.hds-pagination .hds-pagination-size-selector{margin-top:var(--token-pagination-child-spacing-vertical);margin-right:var(--token-pagination-child-spacing-horizontal)}}.hds-pagination .hds-pagination-size-selector{grid-area:selector;justify-self:flex-end;margin-left:var(--token-pagination-child-spacing-horizontal)}.hds-pagination-info{white-space:nowrap}.hds-pagination-nav{display:flex}.hds-pagination-nav__page-list{display:flex;margin:0;padding:0}.hds-pagination-nav__page-item{list-style-type:none}.hds-pagination-nav__control{display:flex;align-items:center;height:var(--token-pagination-nav-control-height);padding:0 calc(var(--token-pagination-nav-control-padding-horizontal) - 1px);color:var(--token-color-foreground-primary);background-color:transparent;border:1px solid transparent;position:relative;outline-style:solid;outline-color:transparent;isolation:isolate}.hds-pagination-nav__control::before{position:absolute;top:var(--token-pagination-nav-control-focus-inset);right:var(--token-pagination-nav-control-focus-inset);bottom:var(--token-pagination-nav-control-focus-inset);left:var(--token-pagination-nav-control-focus-inset);z-index:-1;border-radius:5px;content:""}.hds-pagination-nav__control:focus:not(:focus-visible)::before{box-shadow:none}.hds-pagination-nav__control:focus-visible::before,.hds-side-nav__home-link.mock-focus.mock-focus::before,.hds-side-nav__home-link.mock-focus:focus::before,.hds-side-nav__home-link:focus.mock-focus::before,.hds-side-nav__home-link:focus:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-pagination-nav__control.mock-focus.mock-active::before,.hds-pagination-nav__control:focus:active::before{box-shadow:none}.hds-pagination-nav__control.mock-hover,.hds-pagination-nav__control:hover{color:var(--token-color-foreground-action-hover)}.hds-pagination-nav__control.mock-active,.hds-pagination-nav__control:active{color:var(--token-color-foreground-action-active)}.hds-pagination-nav__arrow{gap:var(--token-pagination-nav-control-icon-spacing)}.hds-pagination-nav__arrow.mock-disabled,.hds-pagination-nav__arrow:disabled{color:var(--token-color-foreground-disabled);cursor:not-allowed}.hds-pagination-nav__arrow--direction-prev{flex-direction:row;justify-content:flex-start}.hds-pagination-nav__arrow--direction-next{flex-direction:row-reverse;justify-content:flex-end}.hds-pagination-nav__number--is-selected{position:relative;color:var(--token-color-foreground-action)}.hds-pagination-nav__number--is-selected:hover{color:var(--token-color-foreground-action-hover)}.hds-pagination-nav__number--is-selected:active{color:var(--token-color-foreground-action-active)}.hds-pagination-nav__number--is-selected::after{position:absolute;right:calc(var(--token-pagination-nav-indicator-spacing) - 1px);bottom:-1px;left:calc(var(--token-pagination-nav-indicator-spacing) - 1px);height:var(--token-pagination-nav-indicator-height);margin:0 auto;background-color:currentColor;border-radius:2px;content:""}.hds-pagination-nav__ellipsis{display:flex;align-items:center;height:var(--token-pagination-nav-control-height);padding:0 var(--token-pagination-nav-control-padding-horizontal);color:var(--token-color-foreground-faint)}.hds-pagination-size-selector{display:flex;align-items:center}.hds-pagination-size-selector>label{white-space:nowrap}.hds-pagination-size-selector>select{height:28px;margin-left:12px;padding:0 24px 0 8px;font-size:var(--token-typography-body-100-font-size);font-family:var(--token-typography-body-100-font-family);line-height:var(--token-typography-body-100-line-height);background-position:center right 5px}.hds-reveal{width:-moz-fit-content;width:fit-content}.hds-reveal__toggle-button{min-height:1.75rem;padding:.313rem .313rem .313rem .188rem}@media (prefers-reduced-motion:no-preference){.hds-reveal__toggle-button .flight-icon-chevron-down{transition:transform .3s}}.hds-reveal__toggle-button--is-open .flight-icon-chevron-down{transform:rotate(-180deg)}.hds-reveal__content{margin-top:4px}.hds-segmented-group{display:inline-flex}.hds-side-nav--is-desktop .hds-side-nav__overlay,.hds-side-nav__toggle-button.mock-focus::after,.hds-side-nav__toggle-button.mock-focus::before,.hds-side-nav__toggle-button:focus-visible::after,.hds-side-nav__toggle-button:focus-visible::before{display:none}.hds-segmented-group>.hds-button:first-child,.hds-segmented-group>.hds-dropdown:first-child,.hds-segmented-group>.hds-form-select:first-child,.hds-segmented-group>.hds-form-text-input:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.hds-segmented-group>.hds-button:first-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-button:first-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-button:first-child::before,.hds-segmented-group>.hds-dropdown:first-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-dropdown:first-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-dropdown:first-child::before,.hds-segmented-group>.hds-form-select:first-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-form-select:first-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-form-select:first-child::before,.hds-segmented-group>.hds-form-text-input:first-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-form-text-input:first-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-form-text-input:first-child::before{border-top-right-radius:inherit;border-bottom-right-radius:inherit}.hds-segmented-group>.hds-button:last-child,.hds-segmented-group>.hds-dropdown:last-child,.hds-segmented-group>.hds-form-select:last-child,.hds-segmented-group>.hds-form-text-input:last-child{margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.hds-segmented-group>.hds-button:last-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-button:last-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-button:last-child::before,.hds-segmented-group>.hds-dropdown:last-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-dropdown:last-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-dropdown:last-child::before,.hds-segmented-group>.hds-form-select:last-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-form-select:last-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-form-select:last-child::before,.hds-segmented-group>.hds-form-text-input:last-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-form-text-input:last-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-form-text-input:last-child::before{border-top-left-radius:inherit;border-bottom-left-radius:inherit}.hds-segmented-group>.hds-button:not(:first-child,:last-child),.hds-segmented-group>.hds-dropdown:not(:first-child,:last-child),.hds-segmented-group>.hds-form-select:not(:first-child,:last-child),.hds-segmented-group>.hds-form-text-input:not(:first-child,:last-child){margin-left:-1px;border-radius:0}.hds-segmented-group>.hds-button:not(:first-child,:last-child) .hds-dropdown-toggle-button,.hds-segmented-group>.hds-button:not(:first-child,:last-child) .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-button:not(:first-child,:last-child)::before,.hds-segmented-group>.hds-dropdown:not(:first-child,:last-child) .hds-dropdown-toggle-button,.hds-segmented-group>.hds-dropdown:not(:first-child,:last-child) .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-dropdown:not(:first-child,:last-child)::before,.hds-segmented-group>.hds-form-select:not(:first-child,:last-child) .hds-dropdown-toggle-button,.hds-segmented-group>.hds-form-select:not(:first-child,:last-child) .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-form-select:not(:first-child,:last-child)::before,.hds-segmented-group>.hds-form-text-input:not(:first-child,:last-child) .hds-dropdown-toggle-button,.hds-segmented-group>.hds-form-text-input:not(:first-child,:last-child) .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-form-text-input:not(:first-child,:last-child)::before{border-radius:inherit}.hds-segmented-group>.hds-button .hds-dropdown-toggle-button:focus,.hds-segmented-group>.hds-button.mock-focus,.hds-segmented-group>.hds-button:focus,.hds-segmented-group>.hds-dropdown .hds-dropdown-toggle-button:focus,.hds-segmented-group>.hds-dropdown.mock-focus,.hds-segmented-group>.hds-dropdown:focus,.hds-segmented-group>.hds-form-select .hds-dropdown-toggle-button:focus,.hds-segmented-group>.hds-form-select.mock-focus,.hds-segmented-group>.hds-form-select:focus,.hds-segmented-group>.hds-form-text-input .hds-dropdown-toggle-button:focus,.hds-segmented-group>.hds-form-text-input.mock-focus,.hds-segmented-group>.hds-form-text-input:focus{z-index:1}.hds-separator{border:none;border-top:1px solid var(--token-color-border-primary)}.hds-separator--spacing-24{margin:24px 0}.hds-separator--spacing-0{margin:0}.hds-side-nav{top:0;z-index:20;width:var(--hds-app-sidenav-width-fixed);height:100vh;min-height:100vh;isolation:isolate}.hds-side-nav.hds-side-nav--is-responsive{transition:width var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing)}.hds-side-nav.hds-side-nav--is-mobile{width:var(--hds-app-sidenav-width-minimized)}.hds-side-nav.hds-side-nav--is-desktop.hds-side-nav--is-not-minimized{width:var(--hds-app-sidenav-width-expanded)}.hds-side-nav--is-minimized .hds-side-nav__wrapper,.hds-side-nav.hds-side-nav--is-desktop.hds-side-nav--is-minimized{width:var(--hds-app-sidenav-width-minimized)}.hds-side-nav__overlay{position:fixed;z-index:-1;inset:0;background-color:var(--token-color-palette-neutral-700);opacity:.2;transition:opacity var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing) var(--hds-app-sidenav-animation-delay)}.hds-side-nav--is-minimized .hds-side-nav__overlay{opacity:0;pointer-events:none}.hds-side-nav__wrapper{display:flex;flex-direction:column;height:100%;color:var(--token-side-nav-color-foreground-primary);background:var(--token-side-nav-color-surface-primary);border-right:var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color)}.hds-side-nav--is-responsive .hds-side-nav__wrapper{transition:width var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing)}.hds-side-nav--is-not-minimized .hds-side-nav__wrapper{width:var(--hds-app-sidenav-width-expanded)}.hds-side-nav__wrapper-header{padding-top:var(--token-side-nav-wrapper-padding-vertical);padding-right:var(--token-side-nav-wrapper-padding-horizontal);padding-bottom:8px;padding-left:var(--token-side-nav-wrapper-padding-horizontal);transition:padding var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing)}.hds-side-nav--is-minimized .hds-side-nav__wrapper-header{padding-top:var(--token-side-nav-wrapper-padding-vertical-minimized);padding-right:var(--token-side-nav-wrapper-padding-horizontal-minimized);padding-left:var(--token-side-nav-wrapper-padding-horizontal-minimized)}.hds-side-nav__wrapper-body,.hds-side-nav__wrapper-footer{padding:var(--token-side-nav-wrapper-padding-vertical) var(--token-side-nav-wrapper-padding-horizontal)}.hds-side-nav__wrapper-body{flex:1;overflow-x:hidden;overflow-y:auto}.hds-side-nav--is-minimized .hds-side-nav-hide-when-minimized{visibility:hidden!important;opacity:0;pointer-events:none}.hds-side-nav--is-not-minimized .hds-side-nav-hide-when-minimized{visibility:visible;opacity:1;transition:opacity var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing) var(--hds-app-sidenav-animation-delay)}.hds-side-nav--is-animating .hds-side-nav-hide-when-minimized{pointer-events:none}.hds-side-nav-header{display:flex;align-items:center;justify-content:space-between}.hds-side-nav-header__logo-container{display:flex;flex:none;align-items:center;justify-content:center;width:var(--token-side-nav-header-home-link-logo-size);height:var(--token-side-nav-header-home-link-logo-size);transition:width var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing),height var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing)}.hds-side-nav--is-minimized .hds-side-nav-header__logo-container{width:var(--token-side-nav-header-home-link-logo-size-minimized);height:var(--token-side-nav-header-home-link-logo-size-minimized)}.hds-side-nav__home-link{color:var(--token-side-nav-color-foreground-strong);background-color:transparent;border:1px solid transparent;border-radius:var(--token-side-nav-body-list-item-border-radius);cursor:pointer;display:block;width:100%;height:100%;padding:calc(var(--token-side-nav-header-home-link-padding) - 1px)}.hds-side-nav__home-link.mock-focus,.hds-side-nav__home-link:focus{position:relative;outline-style:solid;outline-color:transparent;isolation:isolate}.hds-side-nav__home-link.mock-focus::before,.hds-side-nav__home-link:focus::before{position:absolute;top:-1px;right:-1px;bottom:-1px;left:-1px;z-index:-1;border-radius:5px;content:""}.hds-side-nav__home-link.mock-focus:focus:not(:focus-visible)::before,.hds-side-nav__home-link:focus:focus:not(:focus-visible)::before{box-shadow:none}.hds-side-nav__home-link.mock-focus:focus-visible::before,.hds-side-nav__home-link:focus:focus-visible::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__home-link.mock-focus.mock-focus.mock-active::before,.hds-side-nav__home-link.mock-focus:focus:active::before,.hds-side-nav__home-link:focus.mock-focus.mock-active::before,.hds-side-nav__home-link:focus:focus:active::before{box-shadow:none}.hds-side-nav__home-link.mock-hover,.hds-side-nav__home-link:hover{color:var(--token-side-nav-color-foreground-strong);background:var(--token-side-nav-color-surface-interactive-hover)}.hds-side-nav__home-link.mock-active,.hds-side-nav__home-link:active{color:var(--token-side-nav-color-foreground-strong);background:var(--token-side-nav-color-surface-interactive-active)}.hds-side-nav-header__actions-container{display:flex;gap:var(--token-side-nav-header-actions-spacing)}.hds-side-nav__dropdown .hds-dropdown-toggle-button,.hds-side-nav__dropdown .hds-dropdown-toggle-icon{color:var(--token-side-nav-color-foreground-strong);background-color:transparent;border:1px solid transparent;border-radius:var(--token-side-nav-body-list-item-border-radius);cursor:pointer;border-color:var(--token-color-palette-neutral-500)}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus{position:relative;outline-style:solid;outline-color:transparent;isolation:isolate}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus::before{position:absolute;top:-1px;right:-1px;bottom:-1px;left:-1px;z-index:-1;border-radius:5px;content:""}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus.mock-focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus:focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus.mock-focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus:focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus.mock-focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus:focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus.mock-focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus:focus:not(:focus-visible)::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus:focus:not(:focus-visible)::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus:focus:not(:focus-visible)::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus:focus:not(:focus-visible)::before{box-shadow:none}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus:focus-visible::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus:focus-visible::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus:focus-visible::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus:focus-visible::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus.mock-focus.mock-active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus:focus:active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus.mock-focus.mock-active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus:focus:active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus.mock-focus.mock-active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus:focus:active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus.mock-focus.mock-active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus:focus:active::before{box-shadow:none}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-hover,.hds-side-nav__dropdown .hds-dropdown-toggle-button:hover,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-hover,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:hover{color:var(--token-side-nav-color-foreground-strong);background:var(--token-side-nav-color-surface-interactive-hover)}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-active,.hds-side-nav__dropdown .hds-dropdown-toggle-button:active,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-active,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:active{color:var(--token-side-nav-color-foreground-strong);background:var(--token-side-nav-color-surface-interactive-active);border-color:var(--token-color-palette-neutral-400)}.hds-side-nav__icon-button{color:var(--token-side-nav-color-foreground-strong);background-color:transparent;border:1px solid transparent;border-radius:var(--token-side-nav-body-list-item-border-radius);cursor:pointer;border-color:var(--token-color-palette-neutral-500);display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:5px}.hds-side-nav__icon-button.mock-focus,.hds-side-nav__icon-button:focus{position:relative;outline-style:solid;outline-color:transparent;isolation:isolate}.hds-side-nav__icon-button.mock-focus::before,.hds-side-nav__icon-button:focus::before{position:absolute;top:-1px;right:-1px;bottom:-1px;left:-1px;z-index:-1;border-radius:5px;content:""}.hds-side-nav__icon-button.mock-focus.mock-focus::before,.hds-side-nav__icon-button.mock-focus:focus::before,.hds-side-nav__icon-button:focus.mock-focus::before,.hds-side-nav__icon-button:focus:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__icon-button.mock-focus:focus:not(:focus-visible)::before,.hds-side-nav__icon-button:focus:focus:not(:focus-visible)::before{box-shadow:none}.hds-side-nav__icon-button.mock-focus:focus-visible::before,.hds-side-nav__icon-button:focus:focus-visible::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__icon-button.mock-focus.mock-focus.mock-active::before,.hds-side-nav__icon-button.mock-focus:focus:active::before,.hds-side-nav__icon-button:focus.mock-focus.mock-active::before,.hds-side-nav__icon-button:focus:focus:active::before{box-shadow:none}.hds-side-nav__icon-button.mock-hover,.hds-side-nav__icon-button:hover{color:var(--token-side-nav-color-foreground-strong);background:var(--token-side-nav-color-surface-interactive-hover)}.hds-side-nav__icon-button.mock-active,.hds-side-nav__icon-button:active{color:var(--token-side-nav-color-foreground-strong);background:var(--token-side-nav-color-surface-interactive-active);border-color:var(--token-color-palette-neutral-400)}.hds-side-nav__content{margin:0 calc(var(--token-side-nav-wrapper-padding-horizontal) * -1)}.hds-side-nav__content-panels{display:grid;grid-template-columns:repeat(5,var(--hds-app-sidenav-width-expanded));width:100%}.hds-side-nav__content-panel{padding:0 var(--token-side-nav-wrapper-padding-horizontal)}.hds-side-nav__list-title{display:flex;align-items:center;min-height:var(--token-side-nav-body-list-item-height);margin-top:var(--token-side-nav-body-list-margin-vertical);padding:9px var(--token-side-nav-body-list-item-padding-horizontal);color:var(--token-side-nav-color-foreground-faint)}.hds-side-nav__list-wrapper:first-child .hds-side-nav__list-item:first-child>.hds-side-nav__list-title{margin-top:0}.hds-side-nav__list,.hds-side-nav__list-wrapper{margin:0;padding:0}.hds-side-nav__list-item{list-style-type:none}.hds-side-nav__list-item+.hds-side-nav__list-item{margin-top:var(--token-side-nav-body-list-item-spacing-vertical)}.hds-side-nav__list-item-link{display:flex;gap:var(--token-side-nav-body-list-item-content-spacing-horizontal);align-items:center;width:100%;min-height:var(--token-side-nav-body-list-item-height);padding:var(--token-side-nav-body-list-item-padding-vertical) var(--token-side-nav-body-list-item-padding-horizontal);color:var(--token-side-nav-color-foreground-primary);background:var(--token-side-nav-color-surface-primary);border-color:transparent;border-radius:var(--token-side-nav-body-list-item-border-radius)}.hds-side-nav__list-item-link.active .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link.active .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link.active .hds-side-nav__list-item-text,.hds-side-nav__list-item-link.mock-active .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link.mock-active .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link.mock-active .hds-side-nav__list-item-text,.hds-side-nav__list-item-link.mock-hover .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link.mock-hover .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link.mock-hover .hds-side-nav__list-item-text,.hds-side-nav__list-item-link:active .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link:active .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link:active .hds-side-nav__list-item-text,.hds-side-nav__list-item-link:hover .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link:hover .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link:hover .hds-side-nav__list-item-text,.hds-side-nav__list-item-link:hover:focus .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link:hover:focus .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link:hover:focus .hds-side-nav__list-item-text{color:var(--token-side-nav-color-foreground-strong)}.hds-side-nav__list-item-link.mock-focus,.hds-side-nav__list-item-link:focus{position:relative;outline-style:solid;outline-color:transparent;isolation:isolate}.hds-side-nav__list-item-link.mock-focus::before,.hds-side-nav__list-item-link:focus::before{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;border-radius:5px;content:""}.hds-side-nav__list-item-link.mock-focus.mock-focus::before,.hds-side-nav__list-item-link.mock-focus:focus::before,.hds-side-nav__list-item-link:focus.mock-focus::before,.hds-side-nav__list-item-link:focus:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__list-item-link.mock-focus:focus:not(:focus-visible)::before,.hds-side-nav__list-item-link:focus:focus:not(:focus-visible)::before{box-shadow:none}.hds-side-nav__list-item-link.mock-focus:focus-visible::before,.hds-side-nav__list-item-link:focus:focus-visible::before,.hds-table__th-sort button.mock-focus::before,.hds-table__th-sort button:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__list-item-link.mock-focus.mock-focus.mock-active::before,.hds-side-nav__list-item-link.mock-focus:focus:active::before,.hds-side-nav__list-item-link:focus.mock-focus.mock-active::before,.hds-side-nav__list-item-link:focus:focus:active::before{box-shadow:none}.hds-side-nav__list-item-link.mock-hover,.hds-side-nav__list-item-link:hover{background:var(--token-side-nav-color-surface-interactive-hover);border-color:transparent}.hds-side-nav__list-item-link.active,.hds-side-nav__list-item-link.mock-active,.hds-side-nav__list-item-link:active,.hds-side-nav__list-item-link:hover:focus{background:var(--token-side-nav-color-surface-interactive-active)}.hds-side-nav__list-item-link.active .hds-badge,.hds-side-nav__list-item-link.active .hds-badge-count,.hds-side-nav__list-item-link.mock-active .hds-badge,.hds-side-nav__list-item-link.mock-active .hds-badge-count,.hds-side-nav__list-item-link:active .hds-badge,.hds-side-nav__list-item-link:active .hds-badge-count,.hds-side-nav__list-item-link:hover:focus .hds-badge,.hds-side-nav__list-item-link:hover:focus .hds-badge-count{color:var(--token-color-foreground-primary);background:var(--token-color-surface-strong)}.hds-side-nav__list-item-link--back-link.mock-active .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link--back-link.mock-active .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link--back-link.mock-active .hds-side-nav__list-item-text,.hds-side-nav__list-item-link--back-link:active .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link--back-link:active .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link--back-link:active .hds-side-nav__list-item-text,.hds-side-nav__list-item-text{color:var(--token-side-nav-color-foreground-primary)}.hds-side-nav__list-item-link--back-link.mock-active,.hds-side-nav__list-item-link--back-link:active{background:var(--token-side-nav-color-surface-primary)}.hds-side-nav__list-item-text{text-align:left}.hds-side-nav__list-item-icon-leading{flex:none}.hds-side-nav__list-item-icon-trailing{flex:none;margin-left:auto}.hds-side-nav__toggle-button{position:absolute;top:22px;left:calc(var(--token-side-nav-wrapper-border-width) * -1);z-index:1;display:flex;flex-direction:row-reverse;align-items:center;width:26px;height:36px;padding:0 4px;color:var(--token-color-foreground-high-contrast);background:0 0;background-color:var(--token-side-nav-color-surface-primary);border:var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);border-left-color:transparent;border-top-right-radius:var(--token-side-nav-toggle-button-border-radius);border-bottom-right-radius:var(--token-side-nav-toggle-button-border-radius);transform:translateX(var(--hds-app-sidenav-width-expanded));cursor:pointer;transition:transform var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing),width var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing)}.hds-side-nav__toggle-button::after,.hds-side-nav__toggle-button::before{position:absolute;left:calc(var(--token-side-nav-wrapper-border-width) * -1);width:calc(var(--token-side-nav-toggle-button-border-radius) * 2);height:calc(var(--token-side-nav-toggle-button-border-radius) * 2);border-left:var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);box-sizing:border-box;content:""}.hds-side-nav__toggle-button::before{top:calc(var(--token-side-nav-toggle-button-border-radius) * -2);border-bottom:var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);border-bottom-left-radius:var(--token-side-nav-toggle-button-border-radius);box-shadow:0 var(--token-side-nav-toggle-button-border-radius) 0 var(--token-side-nav-color-surface-primary)}.hds-side-nav__toggle-button::after{bottom:calc(var(--token-side-nav-toggle-button-border-radius) * -2);border-top:var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);border-top-left-radius:var(--token-side-nav-toggle-button-border-radius);box-shadow:0 calc(var(--token-side-nav-toggle-button-border-radius) * -1) 0 var(--token-side-nav-color-surface-primary)}.hds-side-nav__toggle-button.mock-hover,.hds-side-nav__toggle-button:hover{width:30px;background-color:var(--token-side-nav-color-surface-interactive-hover)}.hds-side-nav__toggle-button.mock-hover::before,.hds-side-nav__toggle-button:hover::before{box-shadow:0 var(--token-side-nav-toggle-button-border-radius) 0 var(--token-side-nav-color-surface-interactive-hover)}.hds-side-nav__toggle-button.mock-hover::after,.hds-side-nav__toggle-button:hover::after{box-shadow:0 calc(var(--token-side-nav-toggle-button-border-radius) * -1) 0 var(--token-side-nav-color-surface-interactive-hover)}.hds-side-nav__toggle-button.mock-active,.hds-side-nav__toggle-button:active{background-color:var(--token-side-nav-color-surface-interactive-active)}.hds-side-nav__toggle-button.mock-active::before,.hds-side-nav__toggle-button:active::before{box-shadow:0 var(--token-side-nav-toggle-button-border-radius) 0 var(--token-side-nav-color-surface-interactive-active)}.hds-side-nav__toggle-button.mock-active::after,.hds-side-nav__toggle-button:active::after{box-shadow:0 calc(var(--token-side-nav-toggle-button-border-radius) * -1) 0 var(--token-side-nav-color-surface-interactive-active)}.hds-side-nav__toggle-button.mock-focus,.hds-side-nav__toggle-button:focus-visible{border-color:var(--token-color-focus-action-internal);outline:var(--token-color-focus-action-external) solid 3px}.hds-table__th-sort button,.hds-tabs__tab-button,.hds-tooltip-button{outline-color:transparent;outline-style:solid}.hds-side-nav--is-minimized .hds-side-nav__toggle-button{transform:translateX(var(--hds-app-sidenav-width-minimized))}.hds-side-nav .ember-a11y-refocus-skip-link{top:10px;left:10px;z-index:20;width:-moz-max-content;width:max-content;padding:2px 10px 4px;color:var(--token-color-foreground-action);font-family:var(--token-typography-display-200-font-family);background-color:var(--token-color-surface-faint);border-radius:3px;transform:translateY(-200%);transition:.6s ease-in-out}.hds-side-nav .ember-a11y-refocus-skip-link:focus{transform:translateY(0)}.hds-stepper-indicator-step{position:relative;width:24px;height:24px}.hds-stepper-indicator-step__svg-hexagon{width:100%;height:100%;filter:drop-shadow(0 1px 1px rgba(101, 106, 118, .05))}.hds-stepper-indicator-step__status{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center}.hds-stepper-indicator-step__icon{width:12px;height:12px}.hds-stepper-indicator-step__text{width:20px;overflow:hidden;white-space:nowrap;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none}.hds-stepper-indicator-step--status-incomplete .hds-stepper-indicator-step__status{color:var(--token-color-foreground-strong)}.hds-stepper-indicator-step--status-complete .hds-stepper-indicator-step__status,.hds-stepper-indicator-step--status-processing .hds-stepper-indicator-step__status,.hds-stepper-indicator-step--status-progress .hds-stepper-indicator-step__status{color:var(--token-color-foreground-high-contrast)}.hds-stepper-indicator-step--status-incomplete .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-surface-faint);stroke:var(--token-color-foreground-strong)}.hds-stepper-indicator-step--status-complete .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--status-processing .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--status-progress .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-foreground-strong);stroke:var(--token-color-foreground-strong)}.hds-stepper-indicator-step--is-interactive{cursor:pointer}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-incomplete .hds-stepper-indicator-step__status{color:var(--token-color-foreground-primary)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-processing .hds-stepper-indicator-step__status,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-progress .hds-stepper-indicator-step__status{color:var(--token-color-foreground-high-contrast)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-incomplete .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-surface-interactive);stroke:var(--token-color-border-strong)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-incomplete.mock-hover .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-incomplete:hover .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-surface-interactive-hover)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-incomplete.mock-active .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-incomplete:active .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-surface-interactive-active)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-progress .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-200);stroke:var(--token-color-palette-blue-300)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-progress.mock-hover .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-progress:hover .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-300);stroke:var(--token-color-palette-blue-400)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-progress.mock-active .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-progress:active .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-400);stroke:var(--token-color-palette-blue-400)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-processing .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-200);stroke:var(--token-color-palette-blue-300)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-processing.mock-hover .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-processing:hover .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-300);stroke:var(--token-color-palette-blue-400)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-processing.mock-active .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-processing:active .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-400);stroke:var(--token-color-palette-blue-400)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete .hds-stepper-indicator-step__status{color:var(--token-color-palette-blue-200)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-50);stroke:var(--token-color-palette-blue-300)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete.mock-active .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete.mock-hover .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete:active .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete:hover .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-100);stroke:var(--token-color-palette-blue-400)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete.mock-hover .hds-stepper-indicator-step__status,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete:hover .hds-stepper-indicator-step__status{color:var(--token-color-palette-blue-300)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete.mock-active .hds-stepper-indicator-step__status,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete:active .hds-stepper-indicator-step__status{color:var(--token-color-palette-blue-400)}.hds-stepper-indicator-task{position:relative;display:flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--token-color-foreground-strong)}.hds-stepper-indicator-task__icon{width:12px;height:12px}.hds-stepper-indicator-task--is-interactive{cursor:pointer}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-incomplete{color:var(--token-color-palette-neutral-300)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-incomplete.mock-hover,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-incomplete:hover{color:var(--token-color-palette-blue-300)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-incomplete.mock-active,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-incomplete:active{color:var(--token-color-palette-blue-400)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-progress{color:var(--token-color-palette-blue-200)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-progress.mock-hover,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-progress:hover{color:var(--token-color-palette-blue-300)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-progress.mock-active,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-progress:active{color:var(--token-color-palette-blue-400)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-processing{color:var(--token-color-palette-blue-200)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-processing.mock-hover,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-processing:hover{color:var(--token-color-palette-blue-300)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-processing.mock-active,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-processing:active{color:var(--token-color-palette-blue-400)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-complete{color:var(--token-color-palette-green-200)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-complete.mock-hover,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-complete:hover{color:var(--token-color-palette-green-300)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-complete.mock-active,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-complete:active{color:var(--token-color-palette-green-400)}.hds-table{width:100%;border:1px solid var(--token-color-border-primary);border-radius:6px}.hds-table--layout-fixed{table-layout:fixed}.hds-table__thead .hds-table__tr{color:var(--token-color-foreground-strong);background-color:var(--token-color-surface-strong)}.hds-table__thead .hds-table__tr:first-of-type th:first-child{border-top-left-radius:5px}.hds-table__thead .hds-table__tr:first-of-type th:last-child{border-top-right-radius:5px}.hds-table__thead .hds-table__th,.hds-table__thead .hds-table__th-sort{min-height:48px}.hds-table__th,.hds-table__th-sort{text-align:left;border-top:none;border-right:none;border-bottom:1px solid var(--token-color-border-primary);border-left:none}.hds-table__th{padding:14px 16px 13px}.hds-table__th-sort{padding:0}.hds-table__th-sort button{width:100%;height:100%;min-height:48px;margin:0;padding:14px 16px 13px;text-align:inherit;background-color:transparent;border:1px solid transparent;border-radius:inherit;position:relative;isolation:isolate}.hds-table__th-sort button .hds-table__th-sort--button-content{display:flex;align-items:center}.hds-table__th-sort button .hds-table__th-sort--button-content .flight-icon{flex:none;margin-left:8px;color:var(--token-color-foreground-action)}.hds-table__th-sort button.mock-hover,.hds-table__th-sort button:hover{color:var(--token-color-foreground-strong);background-color:var(--token-color-palette-neutral-200);cursor:pointer}.hds-table__th-sort button::before{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;border-radius:inherit;content:""}.hds-table__th-sort button:focus:not(:focus-visible)::before{box-shadow:none}.hds-table__th-sort button:focus-visible::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-table__th-sort button.mock-focus.mock-active::before,.hds-table__th-sort button:focus:active::before{box-shadow:none}.hds-table__th-sort button.mock-active,.hds-table__th-sort button:active{color:var(--token-color-foreground-strong);background-color:var(--token-color-palette-neutral-300)}.hds-table__tbody .hds-table__tr,.hds-tabs__tab,.hds-tag__dismiss-icon{color:var(--token-color-foreground-primary)}.hds-table--striped .hds-table__tbody .hds-table__tr:nth-child(even){background-color:var(--token-color-surface-faint)}.hds-table--density-short .hds-table__tbody td,.hds-table--density-short .hds-table__tbody th{padding:6px 16px 5px}.hds-table--density-medium .hds-table__tbody td,.hds-table--density-medium .hds-table__tbody th{padding:14px 16px 13px}.hds-table--density-tall .hds-table__tbody td,.hds-table--density-tall .hds-table__tbody th{padding:22px 16px 21px}.hds-table--valign-top .hds-table__tbody td,.hds-table--valign-top .hds-table__tbody th{vertical-align:top}.hds-table--valign-middle .hds-table__tbody td,.hds-table--valign-middle .hds-table__tbody th,.hds-tag{vertical-align:middle}.hds-table__td--text-right,.hds-table__th--text-right,.hds-table__th-sort--text-right{text-align:right}.hds-table__th-sort--text-right .hds-table__th-sort--button-content{justify-content:flex-end}.hds-table__td--text-center,.hds-table__th--text-center,.hds-table__th-sort--text-center{text-align:center}.hds-table__tbody .hds-table__tr{background-color:var(--token-color-surface-primary)}.hds-table__tbody .hds-table__tr td,.hds-table__tbody .hds-table__tr th{border-top:none;border-right:none;border-bottom:1px solid var(--token-color-border-primary);border-left:none}.hds-table__tbody .hds-table__tr:last-of-type td,.hds-table__tbody .hds-table__tr:last-of-type th{border-bottom:none}.hds-table__tbody .hds-table__tr:last-of-type td:first-child,.hds-table__tbody .hds-table__tr:last-of-type th:first-child{border-bottom-left-radius:5px}.hds-table__tbody .hds-table__tr:last-of-type td:last-child{border-bottom-right-radius:5px}.hds-tabs__tablist-wrapper{position:relative}.hds-tabs__tablist-wrapper::before{position:absolute;right:0;bottom:calc((var(--token-tabs-indicator-height) - var(--token-tabs-divider-height))/ 2);left:0;display:block;border-top:var(--token-tabs-divider-height) solid var(--token-color-border-primary);content:""}.hds-tabs__tablist{position:relative;display:flex;margin:0;padding:0;overflow-x:auto;-webkit-overflow-scrolling:touch}.hds-tabs__tab{position:relative;display:flex;align-items:center;height:var(--token-tabs-tab-height);margin:0;padding:var(--token-tabs-tab-padding-vertical) var(--token-tabs-tab-padding-horizontal);white-space:nowrap;list-style:none}.hds-tabs__tab.hds-tabs__tab--is-selected,.hds-tabs__tab.mock-hover,.hds-tabs__tab:hover{color:var(--token-color-foreground-action)}.hds-tabs__tab.hds-tabs__tab--is-selected:hover{color:var(--token-color-foreground-action-hover)}.hds-tabs__tab.hds-tabs__tab--is-selected:hover~.hds-tabs__tab-indicator{background:var(--token-color-foreground-action-hover)}.hds-tabs__tab-button{isolation:isolate;position:static;display:flex;gap:var(--token-tabs-tab-gutter);align-items:center;padding:0;color:inherit;background-color:transparent;border:none;border-radius:var(--token-tabs-tab-border-radius);cursor:pointer}.hds-tabs__tab-button::before{position:absolute;top:var(--token-tabs-tab-focus-inset);right:var(--token-tabs-tab-focus-inset);bottom:var(--token-tabs-tab-focus-inset);left:var(--token-tabs-tab-focus-inset);z-index:-1;border-radius:5px;content:""}.hds-tabs__tab-button.mock-focus::before,.hds-tabs__tab-button:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-tabs__tab-button:focus:not(:focus-visible)::before{box-shadow:none}.hds-tabs__tab-button:focus-visible::before,.hds-tag__dismiss.mock-focus.mock-focus,.hds-tag__dismiss.mock-focus:focus,.hds-tag__dismiss:focus.mock-focus,.hds-tag__dismiss:focus:focus,.hds-tag__link.mock-focus.mock-focus,.hds-tag__link.mock-focus:focus,.hds-tag__link:focus.mock-focus,.hds-tag__link:focus:focus{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-tabs__tab-button.mock-focus.mock-active::before,.hds-tabs__tab-button:focus:active::before{box-shadow:none}.hds-tabs__tab-button::after{position:absolute;content:"";inset:0}.hds-tabs__tab-indicator{position:absolute;right:0;bottom:0;left:var(--indicator-left-pos);z-index:10;display:block;width:var(--indicator-width);height:var(--token-tabs-indicator-height);background-color:var(--token-color-foreground-action);border-radius:var(--token-tabs-indicator-height)}.hds-tabs__panel[hidden],.readonly-codemirror .CodeMirror-cursors{display:none}.hds-tag,.hds-tag__dismiss,.hds-tag__link{background-color:var(--token-color-surface-interactive)}@media screen and (prefers-reduced-motion:no-preference){.hds-tabs__tab-indicator{transition-timing-function:var(--token-tabs-indicator-transition-function);transition-duration:var(--token-tabs-indicator-transition-duration);transition-property:left,width}}.tippy-box,.tippy-box[data-theme~=hds]{transition-property:transform,visibility,opacity}.hds-tag,:where(.hds-tooltip-button--is-inline){display:inline-flex}.hds-tag{align-items:stretch;line-height:1rem;border:1px solid var(--token-color-border-strong);border-radius:50px}.hds-tag__dismiss{flex:0 0 auto;margin:0;padding:6px 4px 6px 8px;border:none;border-radius:inherit;border-top-right-radius:0;border-bottom-right-radius:0}.hds-tag__dismiss-icon{width:12px;height:12px}.hds-tag__link,.hds-tag__text{flex:1 0 0;padding:3px 10px 5px;border-radius:inherit}.hds-tag__dismiss~.hds-tag__link,.hds-tag__dismiss~.hds-tag__text{padding:3px 8px 5px 6px;border-top-left-radius:0;border-bottom-left-radius:0}.hds-tag__dismiss,.hds-tag__link{cursor:pointer}.hds-tag__dismiss.mock-hover,.hds-tag__dismiss:hover,.hds-tag__link.mock-hover,.hds-tag__link:hover{background-color:var(--token-color-surface-interactive-hover)}.hds-tag__dismiss.mock-active,.hds-tag__dismiss:active,.hds-tag__link.mock-active,.hds-tag__link:active{background-color:var(--token-color-surface-interactive-active)}.hds-tag__dismiss.mock-focus,.hds-tag__dismiss:focus,.hds-tag__link.mock-focus,.hds-tag__link:focus{outline-style:solid;outline-color:transparent;z-index:1}.hds-tag__dismiss.mock-focus:focus:not(:focus-visible),.hds-tag__dismiss:focus:focus:not(:focus-visible),.hds-tag__link.mock-focus:focus:not(:focus-visible),.hds-tag__link:focus:focus:not(:focus-visible){box-shadow:none}.hds-tag__dismiss.mock-focus:focus-visible,.hds-tag__dismiss:focus:focus-visible,.hds-tag__link.mock-focus:focus-visible,.hds-tag__link:focus:focus-visible{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-tag__dismiss.mock-focus.mock-focus.mock-active,.hds-tag__dismiss.mock-focus:focus:active,.hds-tag__dismiss:focus.mock-focus.mock-active,.hds-tag__dismiss:focus:focus:active,.hds-tag__link.mock-focus.mock-focus.mock-active,.hds-tag__link.mock-focus:focus:active,.hds-tag__link:focus.mock-focus.mock-active,.hds-tag__link:focus:focus:active{box-shadow:none}.hds-tag--color-primary .hds-tag__link{color:var(--token-color-foreground-action)}.hds-tag--color-primary .hds-tag__link.mock-hover,.hds-tag--color-primary .hds-tag__link:hover{color:var(--token-color-foreground-action-hover)}.hds-tag--color-primary .hds-tag__link.mock-active,.hds-tag--color-primary .hds-tag__link:active{color:var(--token-color-foreground-action-active)}.hds-tag--color-secondary .hds-tag__link{color:var(--token-color-foreground-strong)}.hds-text--align-left{text-align:left}.hds-text--align-center{text-align:center}.hds-text--align-right{text-align:right}.hds-toast{width:-moz-fit-content;width:fit-content;min-width:min(360px,80vw);max-width:min(500px,80vw);box-shadow:var(--token-elevation-higher-box-shadow)}.hds-tooltip-button{position:relative;isolation:isolate}.hds-tooltip-button::before{position:absolute;top:var(--token-tooltip-focus-offset);right:var(--token-tooltip-focus-offset);bottom:var(--token-tooltip-focus-offset);left:var(--token-tooltip-focus-offset);z-index:-1;border-radius:5px;content:""}.hds-tooltip-button.mock-focus::before,.hds-tooltip-button:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-tooltip-button:focus:not(:focus-visible)::before{box-shadow:none}.hds-tooltip-button:focus-visible::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-tooltip-button.mock-focus.mock-active::before,.hds-tooltip-button:focus:active::before{box-shadow:none}:where(.hds-tooltip-button){margin:0;padding:0;color:inherit;font:inherit;text-align:inherit;background-color:inherit;border:none}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li,.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.menu-panel>ul>[role=treeitem],.menu-panel>ul>li,.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.modal-dialog [role=document] table caption,.modal-dialog [role=document] table thead th,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li,.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],main table caption,main table thead th,table td,table th,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],td,th{text-align:left}:where(.hds-tooltip-button--is-block){display:flex}article,aside,figure,footer,header,hgroup,hr,section{display:block}.tippy-box[data-theme~=hds]{padding:var(--token-tooltip-padding-vertical) var(--token-tooltip-padding-horizontal);color:var(--token-tooltip-color-foreground-primary);font-weight:var(--token-typography-font-weight-regular);font-size:var(--token-typography-body-200-font-size);font-family:var(--token-typography-body-200-font-family);line-height:var(--token-typography-body-200-line-height);overflow-wrap:break-word;background-color:var(--token-tooltip-color-surface-primary);border-radius:var(--token-tooltip-border-radius);box-shadow:var(--token-elevation-higher-box-shadow)}.tippy-box[data-theme~=hds][data-animation=fade][data-state=hidden]{opacity:0}.tippy-box[data-theme~=hds][data-inertia][data-state=visible]{transition-timing-function:var(--token-tooltip-transition-function)}.tippy-box[data-theme~=hds] .tippy-content{position:relative;z-index:1;max-width:var(--token-tooltip-max-width);white-space:normal}.tippy-box[data-theme~=hds] .tippy-svg-arrow{fill:var(--token-tooltip-color-surface-primary)}.sr-only{position:absolute!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;overflow:hidden!important;white-space:nowrap!important;border:0!important;clip:rect(1px,1px,1px,1px)!important;-webkit-clip-path:inset(50%)!important;clip-path:inset(50%)!important}fieldset,hr{border:none}blockquote,body,dd,dl,dt,fieldset,figure,hr,html,iframe,legend,li,ol,p,textarea,ul{margin:0;padding:0}ul{list-style:none}table td,table th{padding:0}audio,embed,img,object,video{height:auto;max-width:100%}.consul-intention-action-warn-modal button.dangerous,.copy-button button,.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.informed-action>ul>li>*,.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-select label>*,.topology-notices button,.type-sort.popover-select label>*,label span,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}a{color:var(--token-color-foreground-action)}span,strong,td,th{color:inherit}body{color:var(--token-color-foreground-strong)}html{background-color:var(--token-color-surface-primary);font-size:16px;text-rendering:optimizeLegibility;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;overflow-x:hidden;overflow-y:scroll;box-sizing:border-box;min-width:300px}hr{background-color:var(--token-color-surface-interactive-active);height:1px;margin:1.5rem 0}body,input,select,textarea{font-family:var(--token-typography-font-stack-text)}strong{font-style:inherit}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}pre{-webkit-overflow-scrolling:touch;overflow-x:auto;white-space:pre;word-wrap:normal}*,::after,::before{box-sizing:inherit;animation-play-state:paused;animation-fill-mode:forwards}fieldset{width:100%}a,input[type=checkbox],input[type=radio]{cursor:pointer}td,th{vertical-align:top}button,input,select,textarea{margin:0}iframe{border:0}.consul-bucket-list .service,.consul-bucket-list:not([class]) dt:not([class]),.consul-exposed-path-list>ul>li>.detail dl:not([class]) dt:not([class]),.consul-instance-checks:not([class]) dt:not([class]),.consul-lock-session-list dl:not([class]) dt:not([class]),.consul-server-card dt:not(.name),.consul-upstream-instance-list dl.local-bind-address dt,.consul-upstream-instance-list dl.local-bind-socket-path dt,.consul-upstream-instance-list dl:not([class]) dt:not([class]),.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dt:not([class]),.route-title,.tag-list:not([class]) dt:not([class]),section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dt:not([class]),section[data-route="dc.show.license"] .validity dl .expired+dd,section[data-route="dc.show.license"] .validity dl:not([class]) dt:not([class]),td.tags:not([class]) dt:not([class]){position:absolute;overflow:hidden;clip:rect(0 0 0 0);width:1px;height:1px;margin:-1px;padding:0;border:0}.consul-upstream-instance-list dl.local-bind-socket-mode dt,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dt{position:static!important;clip:unset!important;overflow:visible!important;width:auto!important;height:auto!important;margin:0!important;padding:0!important}.animatable.tab-nav ul::after,.consul-auth-method-type,.consul-external-source,.consul-intention-action-warn-modal button.dangerous,.consul-intention-action-warn-modal button.dangerous:hover:active,.consul-intention-action-warn-modal button.dangerous:hover:not(:disabled):not(:active),.consul-intention-list td.intent- strong,.consul-intention-permission-form button.type-submit,.consul-intention-permission-form button.type-submit:disabled,.consul-intention-permission-form button.type-submit:focus:not(:disabled),.consul-intention-permission-form button.type-submit:hover:not(:disabled),.consul-intention-search-bar .value- span,.consul-kind,.consul-source,.consul-transparent-proxy,.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:first-child,.discovery-chain .route-card>header ul li,.informed-action>ul>.dangerous>*,.informed-action>ul>.dangerous>:focus,.informed-action>ul>.dangerous>:hover,.leader,.menu-panel>ul>li.dangerous>:first-child,.modal-dialog [role=document]>footer,.modal-dialog [role=document]>header,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:first-child,.tab-nav .selected>*,.topology-metrics-source-type,html[data-route^="dc.acls.index"] main td strong,span.policy-node-identity,span.policy-service-identity,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:first-child{border-style:solid}.ember-power-select-trigger,.ember-power-select-trigger--active,.ember-power-select-trigger:focus{border-top:1px solid #aaa;border-bottom:1px solid #aaa;border-right:1px solid #aaa;border-left:1px solid #aaa}.animatable.tab-nav ul::after,.app .notifications .app-notification,.tab-nav li>*{transition-duration:.15s;transition-timing-function:ease-out}html body>.brand-loader{transition-timing-function:cubic-bezier(.1,.1,.25,.9);transition-duration:.1s}html[data-state]:not(.ember-loading) body>.brand-loader{animation-timing-function:cubic-bezier(.1,.1,.25,.9);animation-duration:.1s;animation-name:remove-from-flow;animation-fill-mode:forwards}@keyframes remove-from-flow{100%{visibility:hidden;overflow:hidden;clip:rect(0 0 0 0)}}@keyframes typo-truncate{100%{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}.CodeMirror-lint-tooltip,dd code,dd pre code{font-family:var(--token-typography-font-stack-code);font-size:var(--token-typography-code-100-font-size);line-height:var(--token-typography-code-100-line-height);font-weight:var(--token-typography-font-weight-regular)}.consul-health-check-list .health-check-output pre code,code,pre,pre code{font-family:var(--token-typography-font-stack-code);font-size:var(--token-typography-code-200-font-size);line-height:var(--token-typography-code-200-line-height);font-weight:var(--token-typography-font-weight-regular)}.informed-action header>*{font-size:inherit;font-weight:inherit;line-height:inherit;font-style:inherit}::after,::before{--tw-content:'';display:inline-block;vertical-align:text-top;background-repeat:no-repeat;background-position:center;mask-repeat:no-repeat;-webkit-mask-repeat:no-repeat;mask-position:center;-webkit-mask-position:center}::before{animation-name:var(--icon-name-start,var(--icon-name)),var(--icon-size-start,var(--icon-size,icon-000));background-color:var(--icon-color-start,var(--icon-color))}::after{animation-name:var(--icon-name-end,var(--icon-name)),var(--icon-size-end,var(--icon-size,icon-000));background-color:var(--icon-color-end,var(--icon-color))}[style*="--icon-color-start"]::before{color:var(--icon-color-start)}[style*="--icon-color-end"]::after{color:var(--icon-color-end)}[style*="--icon-name-start"]::before,[style*="--icon-name-end"]::after{content:""}@keyframes icon-000{100%{width:1.2em;height:1.2em}}@keyframes icon-100{100%{width:.625rem;height:.625rem}}@keyframes icon-200{100%{width:.75rem;height:.75rem}}@keyframes icon-300{100%{width:1rem;height:1rem}}@keyframes icon-400{100%{width:1.125rem;height:1.125rem}}@keyframes icon-500{100%{width:1.25rem;height:1.25rem}}@keyframes icon-600{100%{width:1.375rem;height:1.375rem}}@keyframes icon-700{100%{width:1.5rem;height:1.5rem}}@keyframes icon-800{100%{width:1.625rem;height:1.625rem}}@keyframes icon-900{100%{width:1.75rem;height:1.75rem}}@keyframes icon-999{100%{width:100%;height:100%}}.consul-intention-permission-header-list dt::before,.consul-intention-permission-list dt::before,.discovery-chain .resolver-card dt,.discovery-chain .route-card section header>::before{font-weight:var(--token-typography-font-weight-regular);background-color:var(--token-color-surface-strong);visibility:visible;padding:0 4px}#downstream-container .topology-metrics-card .details .group span::before,#downstream-container .topology-metrics-card div .critical::before,#downstream-container .topology-metrics-card div .empty::before,#downstream-container .topology-metrics-card div .health dt::before,#downstream-container .topology-metrics-card div .nspace dt::before,#downstream-container .topology-metrics-card div .partition dt::before,#downstream-container .topology-metrics-card div .passing::before,#downstream-container .topology-metrics-card div .warning::before,#downstream-container>div:first-child span::before,#login-toggle+div footer button::after,#metrics-container .link .config-link::before,#metrics-container .link .metrics-link::before,#metrics-container:hover .sparkline-key-link::before,#upstream-container .topology-metrics-card .details .group span::before,#upstream-container .topology-metrics-card div .critical::before,#upstream-container .topology-metrics-card div .empty::before,#upstream-container .topology-metrics-card div .health dt::before,#upstream-container .topology-metrics-card div .nspace dt::before,#upstream-container .topology-metrics-card div .partition dt::before,#upstream-container .topology-metrics-card div .passing::before,#upstream-container .topology-metrics-card div .warning::before,.animatable.tab-nav ul::after,.consul-auth-method-binding-list dl dt.type+dd span::before,.consul-auth-method-list ul .locality::before,.consul-auth-method-view dl dt.type+dd span::before,.consul-auth-method-view section dl dt.type+dd span::before,.consul-bucket-list .nspace::before,.consul-bucket-list .partition::before,.consul-bucket-list .peer::before,.consul-exposed-path-list>ul>li>.detail .policy-management::before,.consul-exposed-path-list>ul>li>.detail .policy::before,.consul-exposed-path-list>ul>li>.detail .role::before,.consul-exposed-path-list>ul>li>.detail dl.address dt::before,.consul-exposed-path-list>ul>li>.detail dl.behavior dt::before,.consul-exposed-path-list>ul>li>.detail dl.checks dt::before,.consul-exposed-path-list>ul>li>.detail dl.critical dt::before,.consul-exposed-path-list>ul>li>.detail dl.datacenter dt::before,.consul-exposed-path-list>ul>li>.detail dl.empty dt::before,.consul-exposed-path-list>ul>li>.detail dl.lock-delay dt::before,.consul-exposed-path-list>ul>li>.detail dl.mesh dt::before,.consul-exposed-path-list>ul>li>.detail dl.node dt::before,.consul-exposed-path-list>ul>li>.detail dl.nspace dt::before,.consul-exposed-path-list>ul>li>.detail dl.passing dt::before,.consul-exposed-path-list>ul>li>.detail dl.path dt::before,.consul-exposed-path-list>ul>li>.detail dl.port dt::before,.consul-exposed-path-list>ul>li>.detail dl.protocol dt::before,.consul-exposed-path-list>ul>li>.detail dl.socket dt::before,.consul-exposed-path-list>ul>li>.detail dl.ttl dt::before,.consul-exposed-path-list>ul>li>.detail dl.unknown dt::before,.consul-exposed-path-list>ul>li>.detail dl.warning dt::before,.consul-exposed-path-list>ul>li>.header .critical dd::before,.consul-exposed-path-list>ul>li>.header .empty dd::before,.consul-exposed-path-list>ul>li>.header .passing dd::before,.consul-exposed-path-list>ul>li>.header .policy-management dd::before,.consul-exposed-path-list>ul>li>.header .unknown dd::before,.consul-exposed-path-list>ul>li>.header .warning dd::before,.consul-exposed-path-list>ul>li>.header [rel=me] dd::before,.consul-external-source.jwt::before,.consul-external-source.oidc::before,.consul-health-check-list .health-check-output dd em.jwt::before,.consul-health-check-list .health-check-output dd em.oidc::before,.consul-health-check-list .health-check-output::before,.consul-instance-checks dt::before,.consul-intention-fieldsets .value->:last-child::before,.consul-intention-fieldsets .value-allow>:last-child::before,.consul-intention-fieldsets .value-deny>:last-child::before,.consul-intention-list em span::before,.consul-intention-list td strong.jwt::before,.consul-intention-list td strong.oidc::before,.consul-intention-list td.intent- strong::before,.consul-intention-list td.intent-allow strong::before,.consul-intention-list td.intent-deny strong::before,.consul-intention-permission-list .intent-allow::before,.consul-intention-permission-list .intent-deny::before,.consul-intention-permission-list strong.jwt::before,.consul-intention-permission-list strong.oidc::before,.consul-intention-search-bar .value- span::before,.consul-intention-search-bar .value-allow span::before,.consul-intention-search-bar .value-deny span::before,.consul-intention-search-bar li button span.jwt::before,.consul-intention-search-bar li button span.oidc::before,.consul-kind::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .policy-management::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .policy::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .role::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.address dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.behavior dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.checks dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.critical dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.datacenter dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.empty dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.lock-delay dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.mesh dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.node dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.nspace dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.passing dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.path dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.port dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.protocol dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.socket dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.ttl dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.unknown dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.warning dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .critical dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header .empty dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header .passing dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header .policy-management dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header .unknown dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header .warning dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header [rel=me] dd::before,.consul-peer-search-bar li button span.jwt::before,.consul-peer-search-bar li button span.oidc::before,.consul-server-card .health-status+dd.jwt::before,.consul-server-card .health-status+dd.oidc::before,.consul-upstream-instance-list dl.datacenter dt::before,.consul-upstream-instance-list dl.nspace dt::before,.consul-upstream-instance-list dl.partition dt::before,.consul-upstream-instance-list li>.detail .policy-management::before,.consul-upstream-instance-list li>.detail .policy::before,.consul-upstream-instance-list li>.detail .role::before,.consul-upstream-instance-list li>.detail dl.address dt::before,.consul-upstream-instance-list li>.detail dl.behavior dt::before,.consul-upstream-instance-list li>.detail dl.checks dt::before,.consul-upstream-instance-list li>.detail dl.critical dt::before,.consul-upstream-instance-list li>.detail dl.datacenter dt::before,.consul-upstream-instance-list li>.detail dl.empty dt::before,.consul-upstream-instance-list li>.detail dl.lock-delay dt::before,.consul-upstream-instance-list li>.detail dl.mesh dt::before,.consul-upstream-instance-list li>.detail dl.node dt::before,.consul-upstream-instance-list li>.detail dl.nspace dt::before,.consul-upstream-instance-list li>.detail dl.passing dt::before,.consul-upstream-instance-list li>.detail dl.path dt::before,.consul-upstream-instance-list li>.detail dl.port dt::before,.consul-upstream-instance-list li>.detail dl.protocol dt::before,.consul-upstream-instance-list li>.detail dl.socket dt::before,.consul-upstream-instance-list li>.detail dl.ttl dt::before,.consul-upstream-instance-list li>.detail dl.unknown dt::before,.consul-upstream-instance-list li>.detail dl.warning dt::before,.consul-upstream-instance-list li>.header .critical dd::before,.consul-upstream-instance-list li>.header .empty dd::before,.consul-upstream-instance-list li>.header .passing dd::before,.consul-upstream-instance-list li>.header .policy-management dd::before,.consul-upstream-instance-list li>.header .unknown dd::before,.consul-upstream-instance-list li>.header .warning dd::before,.consul-upstream-instance-list li>.header [rel=me] dd::before,.consul-upstream-list dl.partition dt::before,.copy-button button::before,.dangerous.informed-action header::before,.disclosure-menu [aria-expanded]~*>ul>li.is-active>::after,.disclosure-menu [aria-expanded]~*>ul>li[aria-checked]>::after,.disclosure-menu [aria-expanded]~*>ul>li[aria-current]>::after,.disclosure-menu [aria-expanded]~*>ul>li[aria-selected]>::after,.discovery-chain .resolvers>header span::after,.discovery-chain .route-card::before,.discovery-chain .route-card>header ul li.jwt::before,.discovery-chain .route-card>header ul li.oidc::before,.discovery-chain .routes>header span::after,.discovery-chain .splitter-card::before,.discovery-chain .splitters>header span::after,.empty-state li[class*=-link]>::after,.has-error>strong::before,.info.informed-action header::before,.jwt.consul-auth-method-type::before,.jwt.consul-external-source::before,.jwt.consul-kind::before,.jwt.consul-source::before,.jwt.consul-transparent-proxy::before,.jwt.leader::before,.jwt.topology-metrics-source-type::before,.leader::before,.list-collection>button::after,.list-collection>ul>li:not(:first-child)>.detail .policy-management::before,.list-collection>ul>li:not(:first-child)>.detail .policy::before,.list-collection>ul>li:not(:first-child)>.detail .role::before,.list-collection>ul>li:not(:first-child)>.detail dl.address dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.behavior dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.checks dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.critical dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.datacenter dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.empty dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.lock-delay dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.mesh dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.node dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.nspace dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.passing dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.path dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.port dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.protocol dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.socket dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.ttl dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.unknown dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.warning dt::before,.list-collection>ul>li:not(:first-child)>.header .critical dd::before,.list-collection>ul>li:not(:first-child)>.header .empty dd::before,.list-collection>ul>li:not(:first-child)>.header .passing dd::before,.list-collection>ul>li:not(:first-child)>.header .policy-management dd::before,.list-collection>ul>li:not(:first-child)>.header .unknown dd::before,.list-collection>ul>li:not(:first-child)>.header .warning dd::before,.list-collection>ul>li:not(:first-child)>.header [rel=me] dd::before,.menu-panel>ul>li.is-active>::after,.menu-panel>ul>li[aria-checked]>::after,.menu-panel>ul>li[aria-current]>::after,.menu-panel>ul>li[aria-selected]>::after,.modal-dialog [role=document] a[rel*=help]::after,.modal-dialog [role=document] table td.folder::before,.modal-dialog [role=document] table th span::after,.more-popover-menu>[type=checkbox]+label+div>ul>li.is-active>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li[aria-checked]>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li[aria-current]>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li[aria-selected]>::after,.more-popover-menu>[type=checkbox]+label>::after,.oidc-select .auth0-oidc-provider::before,.oidc-select .google-oidc-provider::before,.oidc-select .microsoft-oidc-provider::before,.oidc-select .okta-oidc-provider::before,.oidc.consul-auth-method-type::before,.oidc.consul-external-source::before,.oidc.consul-kind::before,.oidc.consul-source::before,.oidc.consul-transparent-proxy::before,.oidc.leader::before,.oidc.topology-metrics-source-type::before,.popover-menu>[type=checkbox]+label+div>ul>li.is-active>::after,.popover-menu>[type=checkbox]+label+div>ul>li[aria-checked]>::after,.popover-menu>[type=checkbox]+label+div>ul>li[aria-current]>::after,.popover-menu>[type=checkbox]+label+div>ul>li[aria-selected]>::after,.popover-menu>[type=checkbox]+label>::after,.popover-select .jwt button::before,.popover-select .oidc button::before,.popover-select .value-critical button::before,.popover-select .value-empty button::before,.popover-select .value-passing button::before,.popover-select .value-unknown button::before,.popover-select .value-warning button::before,.search-bar-status li.jwt:not(.remove-all)::before,.search-bar-status li.oidc:not(.remove-all)::before,.search-bar-status li:not(.remove-all) button::before,.sparkline-key h3::before,.tag-list dt::before,.tooltip-panel dd>div::before,.topology-metrics-popover.deny .tippy-arrow::after,.topology-metrics-popover.deny>button::before,.topology-metrics-popover.l7 .tippy-arrow::after,.topology-metrics-popover.l7>button::before,.topology-metrics-popover.not-defined .tippy-arrow::after,.topology-metrics-popover.not-defined>button::before,.topology-metrics-status-error span::before,.topology-metrics-status-loader span::before,.topology-notices button::before,.type-sort.popover-select label>::before,.type-source.popover-select li.partition button::before,.warning.informed-action header::before,.warning.modal-dialog header::before,[class*=status-].empty-state header::before,a[rel*=external]::after,html[data-route^="dc.acls.index"] main td strong.jwt::before,html[data-route^="dc.acls.index"] main td strong.oidc::before,main a[rel*=help]::after,main header nav:first-child ol li:first-child a::before,main table td.folder::before,main table th span::after,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl.jwt::before,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl.oidc::before,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em.jwt::before,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em.oidc::before,span.jwt.policy-node-identity::before,span.jwt.policy-service-identity::before,span.oidc.policy-node-identity::before,span.oidc.policy-service-identity::before,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.is-active>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li[aria-checked]>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li[aria-current]>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li[aria-selected]>::after,table.has-actions tr>.actions>[type=checkbox]+label>::after,table.with-details td:only-child>div>label::before,table.with-details td>label::before,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.is-active>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li[aria-checked]>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li[aria-current]>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li[aria-selected]>::after,table.with-details tr>.actions>[type=checkbox]+label>::after,td.tags dt::before{content:""}@keyframes icon-alert-circle-outline{100%{-webkit-mask-image:var(--icon-alert-circle-16);mask-image:var(--icon-alert-circle-16);background-color:var(--icon-color,var(--color-alert-circle-outline-500,currentColor))}}[class*=status-].empty-state header::before{--icon-name:icon-alert-circle-outline;content:""}@keyframes icon-alert-triangle{100%{-webkit-mask-image:var(--icon-alert-triangle-16);mask-image:var(--icon-alert-triangle-16);background-color:var(--icon-color,var(--color-alert-triangle-500,currentColor))}}#downstream-container .topology-metrics-card div .warning::before,#upstream-container .topology-metrics-card div .warning::before,.consul-exposed-path-list>ul>li>.detail dl.warning dt::before,.consul-exposed-path-list>ul>li>.header .warning dd::before,.consul-health-check-list .warning.health-check-output::before,.consul-instance-checks.warning dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.warning dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .warning dd::before,.consul-upstream-instance-list li>.detail dl.warning dt::before,.consul-upstream-instance-list li>.header .warning dd::before,.dangerous.informed-action header::before,.list-collection>ul>li:not(:first-child)>.detail dl.warning dt::before,.list-collection>ul>li:not(:first-child)>.header .warning dd::before,.popover-select .value-warning button::before,.topology-metrics-popover.not-defined .tippy-arrow::after,.topology-metrics-popover.not-defined>button::before,.warning.informed-action header::before,.warning.modal-dialog header::before{--icon-name:icon-alert-triangle;content:""}@keyframes icon-arrow-left{100%{-webkit-mask-image:var(--icon-arrow-left-16);mask-image:var(--icon-arrow-left-16);background-color:var(--icon-color,var(--color-arrow-left-500,currentColor))}}@keyframes icon-arrow-right{100%{-webkit-mask-image:var(--icon-arrow-right-16);mask-image:var(--icon-arrow-right-16);background-color:var(--icon-color,var(--color-arrow-right-500,currentColor))}}@keyframes icon-cancel-plain{100%{-webkit-mask-image:var(--icon-x-16);mask-image:var(--icon-x-16);background-color:var(--icon-color,var(--color-cancel-plain-500,currentColor))}}.search-bar-status li:not(.remove-all) button::before{--icon-name:icon-cancel-plain;content:""}@keyframes icon-cancel-square-fill{100%{-webkit-mask-image:var(--icon-x-square-fill-16);mask-image:var(--icon-x-square-fill-16);background-color:var(--icon-color,var(--color-cancel-square-fill-500,currentColor))}}#downstream-container .topology-metrics-card div .critical::before,#upstream-container .topology-metrics-card div .critical::before,.consul-exposed-path-list>ul>li>.detail dl.critical dt::before,.consul-exposed-path-list>ul>li>.header .critical dd::before,.consul-health-check-list .critical.health-check-output::before,.consul-instance-checks.critical dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.critical dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .critical dd::before,.consul-upstream-instance-list li>.detail dl.critical dt::before,.consul-upstream-instance-list li>.header .critical dd::before,.has-error>strong::before,.list-collection>ul>li:not(:first-child)>.detail dl.critical dt::before,.list-collection>ul>li:not(:first-child)>.header .critical dd::before,.popover-select .value-critical button::before,.topology-metrics-popover.deny .tippy-arrow::after,.topology-metrics-popover.deny>button::before{--icon-name:icon-cancel-square-fill;content:""}@keyframes icon-check-plain{100%{-webkit-mask-image:var(--icon-check-16);mask-image:var(--icon-check-16);background-color:var(--icon-color,var(--color-check-plain-500,currentColor))}}.disclosure-menu [aria-expanded]~*>ul>li.is-active>::after,.disclosure-menu [aria-expanded]~*>ul>li[aria-checked]>::after,.disclosure-menu [aria-expanded]~*>ul>li[aria-current]>::after,.disclosure-menu [aria-expanded]~*>ul>li[aria-selected]>::after,.menu-panel>ul>li.is-active>::after,.menu-panel>ul>li[aria-checked]>::after,.menu-panel>ul>li[aria-current]>::after,.menu-panel>ul>li[aria-selected]>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li.is-active>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li[aria-checked]>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li[aria-current]>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li[aria-selected]>::after,.popover-menu>[type=checkbox]+label+div>ul>li.is-active>::after,.popover-menu>[type=checkbox]+label+div>ul>li[aria-checked]>::after,.popover-menu>[type=checkbox]+label+div>ul>li[aria-current]>::after,.popover-menu>[type=checkbox]+label+div>ul>li[aria-selected]>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.is-active>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li[aria-checked]>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li[aria-current]>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li[aria-selected]>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.is-active>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li[aria-checked]>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li[aria-current]>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li[aria-selected]>::after{--icon-name:icon-check-plain;content:""}@keyframes icon-chevron-down{100%{-webkit-mask-image:var(--icon-chevron-down-16);mask-image:var(--icon-chevron-down-16);background-color:var(--icon-color,var(--color-chevron-down-500,currentColor))}}.list-collection>button.closed::after,.more-popover-menu>[type=checkbox]+label>::after,.popover-menu>[type=checkbox]+label>::after,.topology-notices button::before,table.has-actions tr>.actions>[type=checkbox]+label>::after,table.with-details td:only-child>div>label::before,table.with-details td>label::before,table.with-details tr>.actions>[type=checkbox]+label>::after{--icon-name:icon-chevron-down;content:""}@keyframes icon-copy-action{100%{-webkit-mask-image:var(--icon-clipboard-copy-16);mask-image:var(--icon-clipboard-copy-16);background-color:var(--icon-color,var(--color-copy-action-500,currentColor))}}.copy-button button::before{--icon-name:icon-copy-action;content:"";--icon-color:var(--token-color-foreground-faint)}@keyframes icon-deny-alt{100%{-webkit-mask-image:var(--icon-skip-16);mask-image:var(--icon-skip-16);background-color:var(--icon-color,var(--color-deny-alt-500,currentColor))}}@keyframes icon-deny-default{100%{-webkit-mask-image:var(--icon-skip-16);mask-image:var(--icon-skip-16);background-color:var(--icon-color,var(--color-deny-default-500,currentColor))}}@keyframes icon-disabled{100%{-webkit-mask-image:var(--icon-skip-16);mask-image:var(--icon-skip-16);background-color:var(--icon-color,var(--color-disabled-500,currentColor))}}.status-403.empty-state header::before{--icon-name:icon-disabled;content:""}@keyframes icon-docs{100%{-webkit-mask-image:var(--icon-docs-16);mask-image:var(--icon-docs-16);background-color:var(--icon-color,var(--color-docs-500,currentColor))}}#metrics-container .link .config-link::before,.empty-state .docs-link>::after{--icon-name:icon-docs;content:""}@keyframes icon-exit{100%{-webkit-mask-image:var(--icon-external-link-16);mask-image:var(--icon-external-link-16);background-color:var(--icon-color,var(--color-exit-500,currentColor))}}#metrics-container .link .metrics-link::before,a[rel*=external]::after{--icon-name:icon-exit;content:""}@keyframes icon-file-fill{100%{-webkit-mask-image:var(--icon-file-16);mask-image:var(--icon-file-16);background-color:var(--icon-color,var(--color-file-fill-500,currentColor))}}@keyframes icon-folder-outline{100%{-webkit-mask-image:var(--icon-folder-16);mask-image:var(--icon-folder-16);background-color:var(--icon-color,var(--color-folder-outline-500,currentColor))}}#downstream-container .topology-metrics-card div .nspace dt::before,#upstream-container .topology-metrics-card div .nspace dt::before,.consul-bucket-list .nspace::before,.consul-exposed-path-list>ul>li>.detail dl.nspace dt::before,.consul-intention-list span[class|=nspace]::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.nspace dt::before,.consul-upstream-instance-list dl.nspace dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.nspace dt::before,.modal-dialog [role=document] table td.folder::before,main table td.folder::before{--icon-name:icon-folder-outline;content:""}@keyframes icon-health{100%{-webkit-mask-image:var(--icon-activity-16);mask-image:var(--icon-activity-16);background-color:var(--icon-color,var(--color-health-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.checks dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.checks dt::before,.consul-upstream-instance-list li>.detail dl.checks dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.checks dt::before{--icon-name:icon-health;content:""}@keyframes icon-help-circle-outline{100%{-webkit-mask-image:var(--icon-help-16);mask-image:var(--icon-help-16);background-color:var(--icon-color,var(--color-help-circle-outline-500,currentColor))}}#downstream-container .topology-metrics-card div .health dt::before,#upstream-container .topology-metrics-card div .health dt::before,.consul-exposed-path-list>ul>li>.detail dl.unknown dt::before,.consul-exposed-path-list>ul>li>.header .unknown dd::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.unknown dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .unknown dd::before,.consul-upstream-instance-list li>.detail dl.unknown dt::before,.consul-upstream-instance-list li>.header .unknown dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.unknown dt::before,.list-collection>ul>li:not(:first-child)>.header .unknown dd::before,.popover-select .value-unknown button::before,.status-404.empty-state header::before{--icon-name:icon-help-circle-outline;content:""}@keyframes icon-info-circle-fill{100%{-webkit-mask-image:var(--icon-info-16);mask-image:var(--icon-info-16);background-color:var(--icon-color,var(--color-info-circle-fill-500,currentColor))}}#metrics-container:hover .sparkline-key-link::before,.info.informed-action header::before,.sparkline-key h3::before{--icon-name:icon-info-circle-fill;content:""}@keyframes icon-info-circle-outline{100%{-webkit-mask-image:var(--icon-info-16);mask-image:var(--icon-info-16);background-color:var(--icon-color,var(--color-info-circle-outline-500,currentColor))}}#downstream-container>div:first-child span::before,.consul-auth-method-binding-list dl dt.type+dd span::before,.consul-auth-method-view dl dt.type+dd span::before,.consul-exposed-path-list>ul>li>.detail dl.behavior dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.behavior dt::before,.consul-upstream-instance-list li>.detail dl.behavior dt::before,.discovery-chain .resolvers>header span::after,.discovery-chain .routes>header span::after,.discovery-chain .splitters>header span::after,.list-collection>ul>li:not(:first-child)>.detail dl.behavior dt::before,.modal-dialog [role=document] a[rel*=help]::after,.modal-dialog [role=document] table th span::after,.topology-metrics-status-error span::before,.topology-metrics-status-loader span::before,main a[rel*=help]::after,main table th span::after{--icon-name:icon-info-circle-outline;content:""}@keyframes icon-learn{100%{-webkit-mask-image:var(--icon-learn-16);mask-image:var(--icon-learn-16);background-color:var(--icon-color,var(--color-learn-500,currentColor))}}.empty-state .learn-link>::after{--icon-name:icon-learn;content:""}@keyframes icon-logo-github-monochrome{100%{-webkit-mask-image:var(--icon-github-color-16);mask-image:var(--icon-github-color-16);background-color:var(--icon-color,var(--color-logo-github-monochrome-500,currentColor))}}@keyframes icon-logo-google-color{100%{background-image:var(--icon-google-color-16)}}.oidc-select .google-oidc-provider::before{--icon-name:icon-logo-google-color;content:""}@keyframes icon-logo-kubernetes-color{100%{background-image:var(--icon-kubernetes-color-16)}}@keyframes icon-menu{100%{-webkit-mask-image:var(--icon-menu-16);mask-image:var(--icon-menu-16);background-color:var(--icon-color,var(--color-menu-500,currentColor))}}@keyframes icon-minus-square-fill{100%{-webkit-mask-image:var(--icon-minus-square-16);mask-image:var(--icon-minus-square-16);background-color:var(--icon-color,var(--color-minus-square-fill-500,currentColor))}}#downstream-container .topology-metrics-card div .empty::before,#upstream-container .topology-metrics-card div .empty::before,.consul-exposed-path-list>ul>li>.detail dl.empty dt::before,.consul-exposed-path-list>ul>li>.header .empty dd::before,.consul-instance-checks.empty dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.empty dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .empty dd::before,.consul-upstream-instance-list li>.detail dl.empty dt::before,.consul-upstream-instance-list li>.header .empty dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.empty dt::before,.list-collection>ul>li:not(:first-child)>.header .empty dd::before,.popover-select .value-empty button::before{--icon-name:icon-minus-square-fill;content:""}@keyframes icon-more-horizontal{100%{-webkit-mask-image:var(--icon-more-horizontal-16);mask-image:var(--icon-more-horizontal-16);background-color:var(--icon-color,var(--color-more-horizontal-500,currentColor))}}@keyframes icon-public-default{100%{-webkit-mask-image:var(--icon-globe-16);mask-image:var(--icon-globe-16);background-color:var(--icon-color,var(--color-public-default-500,currentColor))}}.consul-auth-method-list ul .locality::before,.consul-exposed-path-list>ul>li>.detail dl.address dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.address dt::before,.consul-upstream-instance-list li>.detail dl.address dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.address dt::before{--icon-name:icon-public-default;content:""}@keyframes icon-search{100%{-webkit-mask-image:var(--icon-search-16);mask-image:var(--icon-search-16);background-color:var(--icon-color,var(--color-search-500,currentColor))}}@keyframes icon-star-outline{100%{-webkit-mask-image:var(--icon-star-16);mask-image:var(--icon-star-16);background-color:var(--icon-color,var(--color-star-outline-500,currentColor))}}.leader::before{--icon-name:icon-star-outline;content:""}@keyframes icon-user-organization{100%{-webkit-mask-image:var(--icon-org-16);mask-image:var(--icon-org-16);background-color:var(--icon-color,var(--color-user-organization-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.datacenter dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.datacenter dt::before,.consul-upstream-instance-list dl.datacenter dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.datacenter dt::before{--icon-name:icon-user-organization;content:""}@keyframes icon-user-plain{100%{-webkit-mask-image:var(--icon-user-16);mask-image:var(--icon-user-16);background-color:var(--icon-color,var(--color-user-plain-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail .role::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .role::before,.consul-upstream-instance-list li>.detail .role::before,.list-collection>ul>li:not(:first-child)>.detail .role::before{--icon-name:icon-user-plain;content:""}@keyframes icon-user-team{100%{-webkit-mask-image:var(--icon-users-16);mask-image:var(--icon-users-16);background-color:var(--icon-color,var(--color-user-team-500,currentColor))}}#downstream-container .topology-metrics-card div .partition dt::before,#upstream-container .topology-metrics-card div .partition dt::before,.consul-bucket-list .partition::before,.consul-intention-list span[class|=partition]::before,.consul-upstream-instance-list dl.partition dt::before,.consul-upstream-list dl.partition dt::before,.type-source.popover-select li.partition button::before{--icon-name:icon-user-team;content:""}@keyframes icon-alert-circle{100%{-webkit-mask-image:var(--icon-alert-circle-16);mask-image:var(--icon-alert-circle-16);background-color:var(--icon-color,var(--color-alert-circle-500,currentColor))}}@keyframes icon-check{100%{-webkit-mask-image:var(--icon-check-16);mask-image:var(--icon-check-16);background-color:var(--icon-color,var(--color-check-500,currentColor))}}@keyframes icon-check-circle{100%{-webkit-mask-image:var(--icon-check-circle-16);mask-image:var(--icon-check-circle-16);background-color:var(--icon-color,var(--color-check-circle-500,currentColor))}}@keyframes icon-check-circle-fill{100%{-webkit-mask-image:var(--icon-check-circle-fill-16);mask-image:var(--icon-check-circle-fill-16);background-color:var(--icon-color,var(--color-check-circle-fill-500,currentColor))}}#downstream-container .topology-metrics-card div .passing::before,#upstream-container .topology-metrics-card div .passing::before,.consul-exposed-path-list>ul>li>.detail dl.passing dt::before,.consul-exposed-path-list>ul>li>.header .passing dd::before,.consul-exposed-path-list>ul>li>.header [rel=me] dd::before,.consul-health-check-list .passing.health-check-output::before,.consul-instance-checks.passing dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.passing dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .passing dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header [rel=me] dd::before,.consul-upstream-instance-list li>.detail dl.passing dt::before,.consul-upstream-instance-list li>.header .passing dd::before,.consul-upstream-instance-list li>.header [rel=me] dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.passing dt::before,.list-collection>ul>li:not(:first-child)>.header .passing dd::before,.list-collection>ul>li:not(:first-child)>.header [rel=me] dd::before,.popover-select .value-passing button::before{--icon-name:icon-check-circle-fill;content:""}@keyframes icon-chevron-left{100%{-webkit-mask-image:var(--icon-chevron-left-16);mask-image:var(--icon-chevron-left-16);background-color:var(--icon-color,var(--color-chevron-left-500,currentColor))}}.empty-state .back-link>::after,main header nav:first-child ol li:first-child a::before{--icon-name:icon-chevron-left;content:""}@keyframes icon-chevron-right{100%{-webkit-mask-image:var(--icon-chevron-right-16);mask-image:var(--icon-chevron-right-16);background-color:var(--icon-color,var(--color-chevron-right-500,currentColor))}}#login-toggle+div footer button::after{--icon-name:icon-chevron-right;content:""}@keyframes icon-chevron-up{100%{-webkit-mask-image:var(--icon-chevron-up-16);mask-image:var(--icon-chevron-up-16);background-color:var(--icon-color,var(--color-chevron-up-500,currentColor))}}.list-collection>button::after,.more-popover-menu>[type=checkbox]:checked+label>::after,.popover-menu>[type=checkbox]:checked+label>::after,.topology-notices button[aria-expanded=true]::before,table.has-actions tr>.actions>[type=checkbox]:checked+label>::after,table.with-details tr>.actions>[type=checkbox]:checked+label>::after{--icon-name:icon-chevron-up;content:""}@keyframes icon-delay{100%{-webkit-mask-image:var(--icon-delay-16);mask-image:var(--icon-delay-16);background-color:var(--icon-color,var(--color-delay-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.lock-delay dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.lock-delay dt::before,.consul-upstream-instance-list li>.detail dl.lock-delay dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.lock-delay dt::before{--icon-name:icon-delay;content:""}@keyframes icon-docs-link{100%{-webkit-mask-image:var(--icon-docs-link-16);mask-image:var(--icon-docs-link-16);background-color:var(--icon-color,var(--color-docs-link-500,currentColor))}}@keyframes icon-eye{100%{-webkit-mask-image:var(--icon-eye-16);mask-image:var(--icon-eye-16);background-color:var(--icon-color,var(--color-eye-500,currentColor))}}@keyframes icon-eye-off{100%{-webkit-mask-image:var(--icon-eye-off-16);mask-image:var(--icon-eye-off-16);background-color:var(--icon-color,var(--color-eye-off-500,currentColor))}}@keyframes icon-file-text{100%{-webkit-mask-image:var(--icon-file-text-16);mask-image:var(--icon-file-text-16);background-color:var(--icon-color,var(--color-file-text-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail .policy::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .policy::before,.consul-upstream-instance-list li>.detail .policy::before,.list-collection>ul>li:not(:first-child)>.detail .policy::before{--icon-name:icon-file-text;content:""}@keyframes icon-gateway{100%{-webkit-mask-image:var(--icon-gateway-16);mask-image:var(--icon-gateway-16);background-color:var(--icon-color,var(--color-gateway-500,currentColor))}}.consul-kind::before{--icon-name:icon-gateway;content:""}@keyframes icon-git-commit{100%{-webkit-mask-image:var(--icon-git-commit-16);mask-image:var(--icon-git-commit-16);background-color:var(--icon-color,var(--color-git-commit-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.node dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.node dt::before,.consul-upstream-instance-list li>.detail dl.node dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.node dt::before{--icon-name:icon-git-commit;content:""}@keyframes icon-hexagon{100%{-webkit-mask-image:var(--icon-hexagon-16);mask-image:var(--icon-hexagon-16);background-color:var(--icon-color,var(--color-hexagon-500,currentColor))}}@keyframes icon-history{100%{-webkit-mask-image:var(--icon-history-16);mask-image:var(--icon-history-16);background-color:var(--icon-color,var(--color-history-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.ttl dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.ttl dt::before,.consul-upstream-instance-list li>.detail dl.ttl dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.ttl dt::before{--icon-name:icon-history;content:""}@keyframes icon-info{100%{-webkit-mask-image:var(--icon-info-16);mask-image:var(--icon-info-16);background-color:var(--icon-color,var(--color-info-500,currentColor))}}@keyframes icon-layers{100%{-webkit-mask-image:var(--icon-layers-16);mask-image:var(--icon-layers-16);background-color:var(--icon-color,var(--color-layers-500,currentColor))}}.topology-metrics-popover.l7 .tippy-arrow::after,.topology-metrics-popover.l7>button::before{--icon-name:icon-layers;content:"";--icon-color:var(--token-color-palette-neutral-300)}@keyframes icon-loading{100%{-webkit-mask-image:var(--icon-loading-16);mask-image:var(--icon-loading-16);background-color:var(--icon-color,var(--color-loading-500,currentColor))}}@keyframes icon-network-alt{100%{-webkit-mask-image:var(--icon-network-alt-16);mask-image:var(--icon-network-alt-16);background-color:var(--icon-color,var(--color-network-alt-500,currentColor))}}.consul-bucket-list .peer::before{--icon-name:icon-network-alt;content:""}@keyframes icon-path{100%{-webkit-mask-image:var(--icon-path-16);mask-image:var(--icon-path-16);background-color:var(--icon-color,var(--color-path-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.path dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.path dt::before,.consul-upstream-instance-list li>.detail dl.path dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.path dt::before{--icon-name:icon-path;content:""}@keyframes icon-running{100%{-webkit-mask-image:var(--icon-running-16);mask-image:var(--icon-running-16);background-color:var(--icon-color,var(--color-running-500,currentColor))}}@keyframes icon-skip{100%{-webkit-mask-image:var(--icon-skip-16);mask-image:var(--icon-skip-16);background-color:var(--icon-color,var(--color-skip-500,currentColor))}}@keyframes icon-socket{100%{-webkit-mask-image:var(--icon-socket-16);mask-image:var(--icon-socket-16);background-color:var(--icon-color,var(--color-socket-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.socket dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.socket dt::before,.consul-upstream-instance-list li>.detail dl.socket dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.socket dt::before{--icon-name:icon-socket;content:""}@keyframes icon-star-circle{100%{-webkit-mask-image:var(--icon-star-circle-16);mask-image:var(--icon-star-circle-16);background-color:var(--icon-color,var(--color-star-circle-500,currentColor))}}@keyframes icon-star-fill{100%{-webkit-mask-image:var(--icon-star-fill-16);mask-image:var(--icon-star-fill-16);background-color:var(--icon-color,var(--color-star-fill-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail .policy-management::before,.consul-exposed-path-list>ul>li>.header .policy-management dd::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .policy-management::before,.consul-lock-session-list ul>li:not(:first-child)>.header .policy-management dd::before,.consul-upstream-instance-list li>.detail .policy-management::before,.consul-upstream-instance-list li>.header .policy-management dd::before,.list-collection>ul>li:not(:first-child)>.detail .policy-management::before,.list-collection>ul>li:not(:first-child)>.header .policy-management dd::before{--icon-name:icon-star-fill;content:"";--icon-color:var(--token-color-consul-brand)}@keyframes icon-tag{100%{-webkit-mask-image:var(--icon-tag-16);mask-image:var(--icon-tag-16);background-color:var(--icon-color,var(--color-tag-500,currentColor))}}.tag-list dt::before,td.tags dt::before{--icon-name:icon-tag;content:""}@keyframes icon-x{100%{-webkit-mask-image:var(--icon-x-16);mask-image:var(--icon-x-16);background-color:var(--icon-color,var(--color-x-500,currentColor))}}@keyframes icon-x-circle{100%{-webkit-mask-image:var(--icon-x-circle-16);mask-image:var(--icon-x-circle-16);background-color:var(--icon-color,var(--color-x-circle-500,currentColor))}}@keyframes icon-x-square{100%{-webkit-mask-image:var(--icon-x-square-16);mask-image:var(--icon-x-square-16);background-color:var(--icon-color,var(--color-x-square-500,currentColor))}}@keyframes icon-cloud-cross{100%{-webkit-mask-image:var(--icon-cloud-cross-16);mask-image:var(--icon-cloud-cross-16);background-color:var(--icon-color,var(--color-cloud-cross-500,currentColor))}}@keyframes icon-loading-motion{100%{-webkit-mask-image:var(--icon-loading-motion-16);mask-image:var(--icon-loading-motion-16);background-color:var(--icon-color,var(--color-loading-motion-500,currentColor))}}@keyframes icon-logo-auth0-color{100%{background-image:var(--icon-auth0-color-16)}}.oidc-select .auth0-oidc-provider::before{--icon-name:icon-logo-auth0-color;content:""}@keyframes icon-logo-ember-circle-color{100%{background-image:var(--icon-logo-ember-circle-color-16)}}@keyframes icon-logo-glimmer-color{100%{background-image:var(--icon-logo-glimmer-color-16)}}@keyframes icon-logo-jwt-color{100%{background-image:var(--icon-logo-jwt-color-16)}}.consul-external-source.jwt::before,.consul-health-check-list .health-check-output dd em.jwt::before,.consul-intention-list td strong.jwt::before,.consul-intention-permission-list strong.jwt::before,.consul-intention-search-bar li button span.jwt::before,.consul-peer-search-bar li button span.jwt::before,.consul-server-card .health-status+dd.jwt::before,.discovery-chain .route-card>header ul li.jwt::before,.jwt.consul-auth-method-type::before,.jwt.consul-kind::before,.jwt.consul-source::before,.jwt.consul-transparent-proxy::before,.jwt.leader::before,.jwt.topology-metrics-source-type::before,.popover-select .jwt button::before,.search-bar-status li.jwt:not(.remove-all)::before,html[data-route^="dc.acls.index"] main td strong.jwt::before,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl.jwt::before,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em.jwt::before,span.jwt.policy-node-identity::before,span.jwt.policy-service-identity::before{--icon-name:icon-logo-jwt-color;content:""}@keyframes icon-logo-microsoft-color{100%{background-image:var(--icon-microsoft-color-16)}}.oidc-select .microsoft-oidc-provider::before{--icon-name:icon-logo-microsoft-color;content:""}@keyframes icon-logo-oidc-color{100%{background-image:var(--icon-logo-oidc-color-16)}}.consul-external-source.oidc::before,.consul-health-check-list .health-check-output dd em.oidc::before,.consul-intention-list td strong.oidc::before,.consul-intention-permission-list strong.oidc::before,.consul-intention-search-bar li button span.oidc::before,.consul-peer-search-bar li button span.oidc::before,.consul-server-card .health-status+dd.oidc::before,.discovery-chain .route-card>header ul li.oidc::before,.oidc.consul-auth-method-type::before,.oidc.consul-kind::before,.oidc.consul-source::before,.oidc.consul-transparent-proxy::before,.oidc.leader::before,.oidc.topology-metrics-source-type::before,.popover-select .oidc button::before,.search-bar-status li.oidc:not(.remove-all)::before,html[data-route^="dc.acls.index"] main td strong.oidc::before,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl.oidc::before,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em.oidc::before,span.oidc.policy-node-identity::before,span.oidc.policy-service-identity::before{--icon-name:icon-logo-oidc-color;content:""}@keyframes icon-logo-okta-color{100%{background-image:var(--icon-okta-color-16)}}.oidc-select .okta-oidc-provider::before{--icon-name:icon-logo-okta-color;content:""}@keyframes icon-mesh{100%{-webkit-mask-image:var(--icon-mesh-16);mask-image:var(--icon-mesh-16);background-color:var(--icon-color,var(--color-mesh-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.mesh dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.mesh dt::before,.consul-upstream-instance-list li>.detail dl.mesh dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.mesh dt::before{--icon-name:icon-mesh;content:""}@keyframes icon-port{100%{-webkit-mask-image:var(--icon-port-16);mask-image:var(--icon-port-16);background-color:var(--icon-color,var(--color-port-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.port dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.port dt::before,.consul-upstream-instance-list li>.detail dl.port dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.port dt::before{--icon-name:icon-port;content:""}@keyframes icon-protocol{100%{-webkit-mask-image:var(--icon-protocol-16);mask-image:var(--icon-protocol-16);background-color:var(--icon-color,var(--color-protocol-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.protocol dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.protocol dt::before,.consul-upstream-instance-list li>.detail dl.protocol dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.protocol dt::before{--icon-name:icon-protocol;content:""}@keyframes icon-redirect{100%{-webkit-mask-image:var(--icon-redirect-16);mask-image:var(--icon-redirect-16);background-color:var(--icon-color,var(--color-redirect-500,currentColor))}}@keyframes icon-search-color{100%{background-image:var(--icon-search-color-16)}}[for=toolbar-toggle]{--icon-name:icon-search-color;content:""}@keyframes icon-sort{100%{-webkit-mask-image:var(--icon-sort-desc-16);mask-image:var(--icon-sort-desc-16);background-color:var(--icon-color,var(--color-sort-500,currentColor))}}.type-sort.popover-select label>::before{--icon-name:icon-sort;content:""}@keyframes icon-union{100%{-webkit-mask-image:var(--icon-union-16);mask-image:var(--icon-union-16);background-color:var(--icon-color,var(--color-union-500,currentColor))}}#downstream-container .topology-metrics-card .details .group span::before,#upstream-container .topology-metrics-card .details .group span::before{--icon-name:icon-union;content:""}.ember-basic-dropdown{position:relative}.ember-basic-dropdown,.ember-basic-dropdown-content,.ember-basic-dropdown-content *{box-sizing:border-box}.ember-basic-dropdown-content{position:absolute;width:auto;z-index:1000;background-color:#fff}.ember-basic-dropdown-content--left{left:0}.ember-basic-dropdown-content--right{right:0}.ember-basic-dropdown-overlay{position:fixed;background:rgba(0,0,0,.5);width:100%;height:100%;z-index:10;top:0;left:0;pointer-events:none}.ember-basic-dropdown-content-wormhole-origin{display:inline}.ember-power-select-dropdown *{box-sizing:border-box}.ember-power-select-trigger{position:relative;border-radius:4px;background-color:#fff;line-height:1.75;overflow-x:hidden;text-overflow:ellipsis;min-height:1.75em;-moz-user-select:none;user-select:none;-webkit-user-select:none;color:inherit}.ember-power-select-trigger:after{content:"";display:table;clear:both}.ember-power-select-trigger--active,.ember-power-select-trigger:focus{box-shadow:none}.ember-basic-dropdown-trigger--below.ember-power-select-trigger[aria-expanded=true],.ember-basic-dropdown-trigger--in-place.ember-power-select-trigger[aria-expanded=true]{border-bottom-left-radius:0;border-bottom-right-radius:0}.ember-basic-dropdown-trigger--above.ember-power-select-trigger[aria-expanded=true]{border-top-left-radius:0;border-top-right-radius:0}.ember-power-select-placeholder{color:#999;display:block;overflow-x:hidden;white-space:nowrap;text-overflow:ellipsis}.ember-power-select-status-icon{position:absolute;display:inline-block;width:0;height:0;top:0;bottom:0;margin:auto;border-style:solid;border-width:7px 4px 0;border-color:#aaa transparent transparent;right:5px}.ember-basic-dropdown-trigger[aria-expanded=true] .ember-power-select-status-icon{transform:rotate(180deg)}.ember-power-select-clear-btn{position:absolute;cursor:pointer;right:25px}.ember-power-select-trigger-multiple-input{font-family:inherit;font-size:inherit;border:none;display:inline-block;line-height:inherit;-webkit-appearance:none;outline:0;padding:0;float:left;background-color:transparent;text-indent:2px}.ember-power-select-trigger-multiple-input:disabled{background-color:#eee}.ember-power-select-trigger-multiple-input::placeholder{opacity:1;color:#999}.ember-power-select-trigger-multiple-input::-webkit-input-placeholder{opacity:1;color:#999}.ember-power-select-trigger-multiple-input::-moz-placeholder{opacity:1;color:#999}.ember-power-select-trigger-multiple-input::-ms-input-placeholder{opacity:1;color:#999}.active.discovery-chain [id*=":"],.discovery-chain path,.ember-power-select-multiple-remove-btn:not(:hover){opacity:.5}.ember-power-select-multiple-options{padding:0;margin:0}.ember-power-select-multiple-option{border:1px solid gray;border-radius:4px;color:#333;background-color:#e4e4e4;padding:0 4px;display:inline-block;line-height:1.45;float:left;margin:2px 0 2px 3px}.ember-power-select-multiple-remove-btn{cursor:pointer}.ember-power-select-search{padding:4px}.ember-power-select-search-input{border:1px solid #aaa;border-radius:0;width:100%;font-size:inherit;line-height:inherit;padding:0 5px}.ember-power-select-search-input:focus{border:1px solid #aaa;box-shadow:none}.ember-power-select-dropdown{border-left:1px solid #aaa;border-right:1px solid #aaa;line-height:1.75;border-radius:4px;box-shadow:none;overflow:hidden;color:inherit}.ember-power-select-dropdown.ember-basic-dropdown-content--above{border-top:1px solid #aaa;border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.ember-power-select-dropdown.ember-basic-dropdown-content--below,.ember-power-select-dropdown.ember-basic-dropdown-content--in-place{border-top:none;border-bottom:1px solid #aaa;border-top-left-radius:0;border-top-right-radius:0}.ember-power-select-dropdown.ember-basic-dropdown-content--in-place{width:100%}.ember-power-select-options{list-style:none;margin:0;padding:0;-moz-user-select:none;user-select:none;-webkit-user-select:none}.ember-power-select-placeholder,.ember-power-select-selected-item,a[rel*=external]::after{margin-left:8px}.ember-power-select-options[role=listbox]{overflow-y:auto;-webkit-overflow-scrolling:touch;max-height:12.25em}.ember-power-select-option{cursor:pointer;padding:0 8px}.ember-power-select-group[aria-disabled=true]{color:#999;cursor:not-allowed}.ember-power-select-group[aria-disabled=true] .ember-power-select-option,.ember-power-select-option[aria-disabled=true]{color:#999;pointer-events:none;cursor:not-allowed}.ember-power-select-option[aria-selected=true]{background-color:#ddd}.ember-power-select-option[aria-current=true]{background-color:#5897fb;color:#fff}.ember-power-select-group-name{cursor:default;font-weight:700}.ember-power-select-trigger[aria-disabled=true]{background-color:#eee}.ember-power-select-trigger{padding:0 16px 0 0}.ember-power-select-group .ember-power-select-group .ember-power-select-group-name{padding-left:24px}.ember-power-select-group .ember-power-select-group .ember-power-select-option{padding-left:40px}.ember-power-select-group .ember-power-select-option{padding-left:24px}.ember-power-select-group .ember-power-select-group-name{padding-left:8px}.ember-power-select-trigger[dir=rtl]{padding:0 0 0 16px}.ember-power-select-trigger[dir=rtl] .ember-power-select-placeholder,.ember-power-select-trigger[dir=rtl] .ember-power-select-selected-item{margin-right:8px}.ember-power-select-trigger[dir=rtl] .ember-power-select-multiple-option,.ember-power-select-trigger[dir=rtl] .ember-power-select-trigger-multiple-input{float:right}.ember-power-select-trigger[dir=rtl] .ember-power-select-status-icon{left:5px;right:initial}.ember-power-select-trigger[dir=rtl] .ember-power-select-clear-btn{left:25px;right:initial}.ember-power-select-dropdown[dir=rtl] .ember-power-select-group .ember-power-select-group .ember-power-select-group-name{padding-right:24px}.ember-power-select-dropdown[dir=rtl] .ember-power-select-group .ember-power-select-group .ember-power-select-option{padding-right:40px}.ember-power-select-dropdown[dir=rtl] .ember-power-select-group .ember-power-select-option{padding-right:24px}.ember-power-select-dropdown[dir=rtl] .ember-power-select-group .ember-power-select-group-name{padding-right:8px}#login-toggle+div footer button:focus,#login-toggle+div footer button:hover,.consul-intention-fieldsets .permissions>button:focus,.consul-intention-fieldsets .permissions>button:hover,.empty-state>ul>li>:focus,.empty-state>ul>li>:hover,.empty-state>ul>li>label>button:focus,.empty-state>ul>li>label>button:hover,.modal-dialog [role=document] dd a:focus,.modal-dialog [role=document] dd a:hover,.modal-dialog [role=document] p a:focus,.modal-dialog [role=document] p a:hover,.oidc-select button.reset:focus,.oidc-select button.reset:hover,.search-bar-status .remove-all button:focus,.search-bar-status .remove-all button:hover,main dd a:focus,main dd a:hover,main p a:focus,main p a:hover{text-decoration:underline}#login-toggle+div footer button,.consul-intention-fieldsets .permissions>button,.empty-state>ul>li>*,.empty-state>ul>li>:active,.empty-state>ul>li>:focus,.empty-state>ul>li>:hover,.empty-state>ul>li>label>button,.empty-state>ul>li>label>button:active,.empty-state>ul>li>label>button:focus,.empty-state>ul>li>label>button:hover,.modal-dialog [role=document] dd a,.modal-dialog [role=document] p a,.oidc-select button.reset,.search-bar-status .remove-all button,main dd a,main dd a:active,main dd a:focus,main dd a:hover,main p a,main p a:active,main p a:focus,main p a:hover{color:var(--token-color-foreground-action)}.modal-dialog [role=document] label a[rel*=help],div.with-confirmation p,main label a[rel*=help]{color:var(--token-color-foreground-disabled)}#login-toggle+div footer button,.consul-intention-fieldsets .permissions>button,.empty-state>ul>li>*,.empty-state>ul>li>label>button,.modal-dialog [role=document] dd a,.modal-dialog [role=document] p a,.oidc-select button.reset,.search-bar-status .remove-all button,main dd a,main p a{cursor:pointer;background-color:transparent}#login-toggle+div footer button:active,.consul-intention-fieldsets .permissions>button:active,.empty-state>ul>li>:active,.empty-state>ul>li>label>button:active,.modal-dialog [role=document] dd a:active,.modal-dialog [role=document] p a:active,.oidc-select button.reset:active,.search-bar-status .remove-all button:active,main dd a:active,main p a:active{outline:0}.modal-dialog [role=document] a[rel*=help]::after,main a[rel*=help]::after{opacity:.4}.modal-dialog [role=document] h2 a,main h2 a{color:var(--token-color-foreground-strong)}.auth-form em,.empty-state,main header nav:first-child ol li a,main header nav:first-child ol li:not(:first-child) a::before{color:var(--token-color-foreground-faint)}.modal-dialog [role=document] h2 a[rel*=help]::after,main h2 a[rel*=help]::after{font-size:.65em;margin-top:.2em;margin-left:.2em}.tab-section>p:only-child [rel*=help]::after{content:none}.auth-form{width:320px;margin:-20px 25px 0}.auth-form em{font-style:normal;display:inline-block;margin-top:1em}.auth-form .oidc-select,.auth-form form{padding-top:1em}.auth-form form{margin-bottom:0!important}.auth-form .ember-basic-dropdown-trigger,.auth-form button:not(.reset){width:100%}.auth-form .progress{margin:0 auto}#login-toggle+div footer button::after{font-size:120%;position:relative;top:-1px;left:-3px}#login-toggle+div footer{border-top:0;background-color:transparent;padding:10px 42px 20px}#login-toggle+div>div>div>div{padding-bottom:0}main header nav:first-child ol li a{text-decoration:none}main header nav:first-child ol li a:hover{color:var(--token-color-foreground-action);text-decoration:underline}main header nav:first-child ol li a::before{text-decoration:none}main header nav:first-child ol{display:grid;grid-auto-flow:column;white-space:nowrap;overflow:hidden}main header nav:first-child ol>li{list-style-type:none;display:inline-flex;overflow:hidden}main header nav:first-child ol li:first-child a::before{background-color:var(--token-color-foreground-faint);margin-right:4px;display:inline-block}main header nav:first-child ol li:not(:first-child) a{margin-left:6px;overflow:hidden;text-overflow:ellipsis}main header nav:first-child ol li:not(:first-child) a::before{content:"/";margin-right:8px;display:inline-block}main header nav:first-child{position:absolute;top:12px}.consul-intention-action-warn-modal button.dangerous,.copy-button button,.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.informed-action>ul>li>*,.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-select label>*,.topology-notices button,.type-sort.popover-select label>*,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{cursor:pointer;white-space:nowrap;text-decoration:none}.consul-intention-action-warn-modal button.dangerous:disabled,.copy-button button:disabled,.disclosure-menu [aria-expanded]~*>ul>[role=treeitem]:disabled,.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem]:disabled,.disclosure-menu [aria-expanded]~*>ul>li>[role=option]:disabled,.informed-action>ul>li>:disabled,.menu-panel>ul>[role=treeitem]:disabled,.menu-panel>ul>li>[role=menuitem]:disabled,.menu-panel>ul>li>[role=option]:disabled,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]:disabled,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]:disabled,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option]:disabled,.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]:disabled,.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]:disabled,.popover-menu>[type=checkbox]+label+div>ul>li>[role=option]:disabled,.popover-select label>:disabled,.topology-notices button:disabled,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]:disabled,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]:disabled,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]:disabled,table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]:disabled,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]:disabled,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]:disabled{cursor:default;box-shadow:none}.checkbox-group label,.more-popover-menu>[type=checkbox]~label,.popover-menu>[type=checkbox]~label,html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label,table.has-actions tr>.actions>[type=checkbox]~label,table.with-details tr>.actions>[type=checkbox]~label{cursor:pointer}.consul-intention-action-warn-modal button.dangerous{border-width:1px;border-radius:var(--decor-radius-100);box-shadow:var(--token-elevation-high-box-shadow)}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.informed-action>ul>li>*,.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{color:var(--token-color-foreground-strong);background-color:var(--token-color-surface-primary)}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem]:focus,.disclosure-menu [aria-expanded]~*>ul>[role=treeitem]:hover,.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem]:focus,.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem]:hover,.disclosure-menu [aria-expanded]~*>ul>li>[role=option]:focus,.disclosure-menu [aria-expanded]~*>ul>li>[role=option]:hover,.informed-action>ul>li>:focus,.informed-action>ul>li>:hover,.menu-panel>ul>[role=treeitem]:focus,.menu-panel>ul>[role=treeitem]:hover,.menu-panel>ul>li>[role=menuitem]:focus,.menu-panel>ul>li>[role=menuitem]:hover,.menu-panel>ul>li>[role=option]:focus,.menu-panel>ul>li>[role=option]:hover,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]:focus,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]:hover,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]:focus,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]:hover,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option]:focus,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option]:hover,.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]:focus,.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]:hover,.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]:focus,.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]:hover,.popover-menu>[type=checkbox]+label+div>ul>li>[role=option]:focus,.popover-menu>[type=checkbox]+label+div>ul>li>[role=option]:hover,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]:focus,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]:hover,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]:focus,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]:hover,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]:focus,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]:hover,table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]:focus,table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]:hover,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]:focus,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]:hover,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]:focus,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]:hover{background-color:var(--token-color-surface-strong)}.type-sort.popover-select label>::before{position:relative;width:16px;height:16px}.type-sort.popover-select label>::after{top:0!important}.consul-intention-action-warn-modal button.dangerous,.copy-button button,.popover-select label>*,.topology-notices button,.type-sort.popover-select label>*{position:relative}.consul-intention-action-warn-modal button.dangerous .progress.indeterminate,.copy-button button .progress.indeterminate,.popover-select label>* .progress.indeterminate,.topology-notices button .progress.indeterminate{position:absolute;top:50%;left:50%;margin-left:-12px;margin-top:-12px}.consul-intention-action-warn-modal button.dangerous:disabled .progress+*,.copy-button button:disabled .progress+*,.popover-select label>:disabled .progress+*,.topology-notices button:disabled .progress+*{visibility:hidden}.consul-intention-action-warn-modal button.dangerous:empty,.copy-button button:empty,.popover-select label>:empty,.topology-notices button:empty{padding-right:0!important;padding-left:18px!important;margin-right:5px}.consul-intention-action-warn-modal button.dangerous:empty::before,.copy-button button:empty::before,.popover-select label>:empty::before,.topology-notices button:empty::before{left:1px}.consul-intention-action-warn-modal button.dangerous:not(:empty),.copy-button button:not(:empty),.popover-select label>:not(:empty),.topology-notices button:not(:empty){display:inline-flex;text-align:center;justify-content:center;align-items:center;padding:calc(.5em - 1px) calc(2.2em - 1px);min-width:100px}.consul-intention-action-warn-modal button.dangerous:not(:last-child),.copy-button button:not(:last-child),.popover-select label>:not(:last-child),.topology-notices button:not(:last-child){margin-right:8px}.app-view>header .actions a{padding-top:calc(.4em - 1px)!important;padding-bottom:calc(.4em - 1px)!important}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.informed-action>ul>li>*,.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{padding:.9em 1em;text-align:center;display:inline-block;box-sizing:border-box}.type-sort.popover-select label>*{height:35px!important}.discovery-chain .resolver-card,.discovery-chain .route-card,.discovery-chain .splitter-card{border:var(--decor-border-100);border-radius:var(--decor-radius-100);background-color:var(--token-color-surface-faint);display:block;position:relative}.discovery-chain .resolver-card>section,.discovery-chain .resolver-card>ul>li,.discovery-chain .route-card>section,.discovery-chain .route-card>ul>li,.discovery-chain .splitter-card>section,.discovery-chain .splitter-card>ul>li{border-top:var(--decor-border-100)}.discovery-chain .resolver-card,.discovery-chain .resolver-card>section,.discovery-chain .resolver-card>ul>li,.discovery-chain .route-card,.discovery-chain .route-card>section,.discovery-chain .route-card>ul>li,.discovery-chain .splitter-card,.discovery-chain .splitter-card>section,.discovery-chain .splitter-card>ul>li{border-color:var(--token-color-surface-interactive-active)}.discovery-chain .resolver-card:focus,.discovery-chain .resolver-card:hover,.discovery-chain .route-card:focus,.discovery-chain .route-card:hover,.discovery-chain .splitter-card:focus,.discovery-chain .splitter-card:hover{box-shadow:var(--token-surface-mid-box-shadow)}.discovery-chain .resolver-card>header,.discovery-chain .route-card>header,.discovery-chain .splitter-card>header{padding:10px}.discovery-chain .resolver-card>section,.discovery-chain .resolver-card>ul>li,.discovery-chain .route-card>section,.discovery-chain .route-card>ul>li,.discovery-chain .splitter-card>section,.discovery-chain .splitter-card>ul>li{padding:5px 10px}.discovery-chain .resolver-card ul,.discovery-chain .route-card ul,.discovery-chain .splitter-card ul{list-style-type:none;margin:0;padding:0}.checkbox-group label{margin-right:10px;white-space:nowrap}.checkbox-group span{display:inline-block;margin-left:10px;min-width:50px}.CodeMirror{max-width:1260px;min-height:300px;height:auto;padding-bottom:20px}.CodeMirror-scroll{overflow-x:hidden!important}.CodeMirror-lint-tooltip{background-color:#f9f9fa;border:1px solid var(--syntax-light-gray);border-radius:0;color:#212121;padding:7px 8px 9px}.cm-s-hashi.CodeMirror{width:100%;background-color:var(--token-color-hashicorp-brand)!important;color:#cfd2d1!important;border:none;font-family:var(--token-typography-font-stack-code);-webkit-font-smoothing:auto;line-height:1.4}.cm-s-hashi .CodeMirror-gutters{color:var(--syntax-dark-grey);background-color:var(--syntax-gutter-grey);border:none}.cm-s-hashi .CodeMirror-cursor{border-left:solid thin #f8f8f0}.cm-s-hashi .CodeMirror-linenumber{color:#6d8a88}.cm-s-hashi.CodeMirror-focused div.CodeMirror-selected{background:#214283}.cm-s-hashi .CodeMirror-line::selection,.cm-s-hashi .CodeMirror-line>span::selection,.cm-s-hashi .CodeMirror-line>span>span::selection{background:#214283}.cm-s-hashi .CodeMirror-line::-moz-selection,.cm-s-hashi .CodeMirror-line>span::-moz-selection,.cm-s-hashi .CodeMirror-line>span>span::-moz-selection{background:var(--token-color-surface-interactive)}.cm-s-hashi span.cm-comment{color:var(--syntax-light-grey)}.cm-s-hashi span.cm-string,.cm-s-hashi span.cm-string-2{color:var(--syntax-packer)}.cm-s-hashi span.cm-number{color:var(--syntax-serf)}.cm-s-hashi span.cm-variable,.cm-s-hashi span.cm-variable-2{color:#9e84c5}.cm-s-hashi span.cm-def{color:var(--syntax-packer)}.cm-s-hashi span.cm-operator{color:var(--syntax-gray)}.cm-s-hashi span.cm-keyword{color:var(--syntax-yellow)}.cm-s-hashi span.cm-atom{color:var(--syntax-serf)}.cm-s-hashi span.cm-meta,.cm-s-hashi span.cm-tag{color:var(--syntax-packer)}.cm-s-hashi span.cm-error{color:var(--syntax-red)}.cm-s-hashi span.cm-attribute,.cm-s-hashi span.cm-qualifier{color:#9fca56}.cm-s-hashi span.cm-property{color:#9e84c5}.cm-s-hashi span.cm-builtin,.cm-s-hashi span.cm-variable-3{color:#9fca56}.cm-s-hashi .CodeMirror-activeline-background{background:#101213}.cm-s-hashi .CodeMirror-matchingbracket{text-decoration:underline;color:var(--token-color-surface-primary)!important}.readonly-codemirror .cm-s-hashi span{color:var(--syntax-light-grey)}.readonly-codemirror .cm-s-hashi span.cm-string,.readonly-codemirror .cm-s-hashi span.cm-string-2{color:var(--syntax-faded-gray)}.readonly-codemirror .cm-s-hashi span.cm-number{color:#a3acbc}.readonly-codemirror .cm-s-hashi span.cm-property,.tippy-box[data-theme~=tooltip]{color:var(--token-color-surface-primary)}.readonly-codemirror .cm-s-hashi span.cm-variable-2{color:var(--syntax-light-grey-blue)}.code-editor .toolbar-container{background:var(--token-color-surface-strong);background:linear-gradient(180deg,var(--token-color-surface-strong) 50%,var(--token-color-surface-interactive-active) 100%);border:1px solid var(--token-color-surface-interactive-active);border-bottom-color:var(--token-color-foreground-faint);border-top-color:var(--token-color-foreground-disabled)}.code-editor .toolbar-container .toolbar .title{color:var(--token-color-foreground-strong);padding:0 8px}.code-editor .toolbar-container .toolbar .toolbar-separator{border-right:1px solid var(--token-color-palette-neutral-300)}.code-editor .toolbar-container .ember-power-select-trigger{background-color:var(--token-color-surface-primary);color:var(--token-color-hashicorp-brand);border-radius:var(--decor-radius-100);border:var(--decor-border-100);border-color:var(--token-color-foreground-faint)}.code-editor{display:block;border:10px;overflow:hidden;position:relative;clear:both}.code-editor::after{position:absolute;bottom:0;width:100%;height:25px;background-color:var(--token-color-hashicorp-brand);content:"";display:block}.code-editor>pre{display:none}.code-editor .toolbar-container,.code-editor .toolbar-container .toolbar{align-items:center;justify-content:space-between;display:flex}.code-editor .toolbar-container{position:relative;margin-top:4px;height:44px}.code-editor .toolbar-container .toolbar{flex:1;white-space:nowrap}.code-editor .toolbar-container .toolbar .toolbar-separator{height:32px;margin:0 4px;width:0}.code-editor .toolbar-container .toolbar .tools{display:flex;flex-direction:row;margin:0 10px;align-items:center}.code-editor .toolbar-container .toolbar .tools .copy-button{margin-left:10px}.code-editor .toolbar-container .ember-basic-dropdown-trigger{margin:0 8px;width:120px;height:32px;display:flex;align-items:center;flex-direction:row}.consul-exposed-path-list>ul>li,.consul-lock-session-list ul>li:not(:first-child),.consul-upstream-instance-list li,.list-collection>ul>li:not(:first-child){display:grid;grid-template-columns:1fr auto;grid-template-rows:50% 50%;grid-template-areas:"header actions" "detail actions"}.consul-exposed-path-list>ul>li>.header,.consul-lock-session-list ul>li:not(:first-child)>.header,.consul-upstream-instance-list li>.header,.list-collection>ul>li:not(:first-child)>.header{grid-area:header;align-self:start}.consul-exposed-path-list>ul>li>.detail,.consul-lock-session-list ul>li:not(:first-child)>.detail,.consul-upstream-instance-list li>.detail,.list-collection>ul>li:not(:first-child)>.detail{grid-area:detail;align-self:end}.consul-exposed-path-list>ul>li>.detail *,.consul-lock-session-list ul>li:not(:first-child)>.detail *,.consul-upstream-instance-list li>.detail *,.list-collection>ul>li:not(:first-child)>.detail *{flex-wrap:nowrap!important}.consul-exposed-path-list>ul>li>.actions,.consul-lock-session-list ul>li:not(:first-child)>.actions,.consul-upstream-instance-list li>.actions,.list-collection>ul>li:not(:first-child)>.actions{grid-area:actions;display:inline-flex}.consul-nspace-list>ul>li:not(:first-child) dt,.consul-policy-list>ul li:not(:first-child) dl:not(.datacenter) dt,.consul-role-list>ul>li:not(:first-child) dt,.consul-service-instance-list .port dt,.consul-service-instance-list .port dt::before,.consul-token-list>ul>li:not(:first-child) dt{display:none}.consul-exposed-path-list>ul>li>.header:nth-last-child(2),.consul-lock-session-list ul>li:not(:first-child)>.header:nth-last-child(2),.consul-upstream-instance-list li>.header:nth-last-child(2),.list-collection>ul>li:not(:first-child)>.header:nth-last-child(2){grid-column-start:header;grid-column-end:actions}.consul-exposed-path-list>ul>li>.detail:last-child,.consul-lock-session-list ul>li:not(:first-child)>.detail:last-child,.consul-upstream-instance-list li>.detail:last-child,.list-collection>ul>li:not(:first-child)>.detail:last-child{grid-column-start:detail;grid-column-end:actions}.consul-nspace-list>ul>li:not(:first-child) dt+dd,.consul-policy-list>ul li:not(:first-child) dl:not(.datacenter) dt+dd,.consul-role-list>ul>li:not(:first-child) dt+dd,.consul-token-list>ul>li:not(:first-child) dt+dd{margin-left:0!important}.consul-policy-list dl.datacenter dt,.consul-service-list li>div:first-child>dl:first-child dd{margin-top:1px}.consul-service-instance-list .detail,.consul-service-list .detail{overflow-x:visible!important}.consul-intention-permission-list>ul{border-top:1px solid var(--token-color-surface-interactive-active)}.consul-service-instance-list .port .copy-button{margin-right:0}.consul-exposed-path-list>ul>li .copy-button,.consul-lock-session-list ul>li:not(:first-child) .copy-button,.consul-upstream-instance-list li .copy-button,.list-collection>ul>li:not(:first-child) .copy-button{display:inline-flex}.consul-exposed-path-list>ul>li>.header .copy-button,.consul-lock-session-list ul>li:not(:first-child)>.header .copy-button,.consul-upstream-instance-list li>.header .copy-button,.list-collection>ul>li:not(:first-child)>.header .copy-button{margin-left:4px}.consul-exposed-path-list>ul>li>.detail .copy-button,.consul-lock-session-list ul>li:not(:first-child)>.detail .copy-button,.consul-upstream-instance-list li>.detail .copy-button,.list-collection>ul>li:not(:first-child)>.detail .copy-button{margin-top:2px}.consul-exposed-path-list>ul>li .copy-button button,.consul-lock-session-list ul>li:not(:first-child) .copy-button button,.consul-upstream-instance-list li .copy-button button,.list-collection>ul>li:not(:first-child) .copy-button button{padding:0!important;margin:0!important}.consul-exposed-path-list>ul>li>.header .copy-button button,.consul-lock-session-list ul>li:not(:first-child)>.header .copy-button button,.consul-upstream-instance-list li>.header .copy-button button,.list-collection>ul>li:not(:first-child)>.header .copy-button button{display:none}.consul-exposed-path-list>ul>li>.header:hover .copy-button button,.consul-lock-session-list ul>li:not(:first-child)>.header:hover .copy-button button,.consul-upstream-instance-list li>.header:hover .copy-button button,.list-collection>ul>li:not(:first-child)>.header:hover .copy-button button{display:block}.consul-exposed-path-list>ul>li .copy-button button:hover,.consul-lock-session-list ul>li:not(:first-child) .copy-button button:hover,.consul-upstream-instance-list li .copy-button button:hover,.list-collection>ul>li:not(:first-child) .copy-button button:hover{background-color:transparent!important}.consul-exposed-path-list>ul>li>.detail>.consul-external-source:first-child,.consul-exposed-path-list>ul>li>.detail>.consul-kind:first-child,.consul-lock-session-list ul>li:not(:first-child)>.detail>.consul-external-source:first-child,.consul-lock-session-list ul>li:not(:first-child)>.detail>.consul-kind:first-child,.consul-upstream-instance-list li>.detail>.consul-external-source:first-child,.consul-upstream-instance-list li>.detail>.consul-kind:first-child,.list-collection>ul>li:not(:first-child)>.detail>.consul-external-source:first-child,.list-collection>ul>li:not(:first-child)>.detail>.consul-kind:first-child{margin-left:-5px}.consul-exposed-path-list>ul>li>.detail .policy-management::before,.consul-exposed-path-list>ul>li>.detail .policy::before,.consul-exposed-path-list>ul>li>.detail .role::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .policy-management::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .policy::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .role::before,.consul-upstream-instance-list li>.detail .policy-management::before,.consul-upstream-instance-list li>.detail .policy::before,.consul-upstream-instance-list li>.detail .role::before,.list-collection>ul>li:not(:first-child)>.detail .policy-management::before,.list-collection>ul>li:not(:first-child)>.detail .policy::before,.list-collection>ul>li:not(:first-child)>.detail .role::before{margin-right:3px}table div.with-confirmation.confirming{background-color:var(--token-color-surface-primary)}div.with-confirmation p{margin-right:12px;padding-left:12px;margin-bottom:0!important}div.with-confirmation{justify-content:end;width:100%;display:flex;align-items:center}table td>div.with-confirmation.confirming{position:absolute;right:0}@media (max-width:420px){div.with-confirmation{float:none;margin-top:1em;display:block}div.with-confirmation p{margin-bottom:1em}}.copy-button button{color:var(--token-color-foreground-action);--icon-color:transparent;min-height:17px}.copy-button button::after{--icon-color:var(--token-color-surface-strong)}.copy-button button:focus,.copy-button button:hover:not(:disabled):not(:active){color:var(--token-color-foreground-action);--icon-color:var(--token-color-surface-strong)}.copy-button button:hover::before{--icon-color:var(--token-color-foreground-action)}.copy-button button:active{--icon-color:var(--token-color-surface-interactive-active)}.copy-button button:empty{padding:0!important;margin-right:0;top:-1px}.copy-button button:empty::after{content:"";display:none;position:absolute;top:-2px;left:-3px;width:20px;height:22px}.copy-button button:empty:hover::after{display:block}.copy-button button:empty::before{position:relative;z-index:1}.copy-button button:not(:empty)::before{margin-right:4px}.consul-bucket-list .copy-button,.consul-exposed-path-list>ul>li>.detail dl .copy-button,.consul-instance-checks .copy-button,.consul-lock-session-list dl .copy-button,.consul-lock-session-list ul>li:not(:first-child)>.detail dl .copy-button,.consul-upstream-instance-list dl .copy-button,.list-collection>ul>li:not(:first-child)>.detail dl .copy-button,.tag-list .copy-button,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .copy-button,section[data-route="dc.show.license"] .validity dl .copy-button,td.tags .copy-button{margin-top:0!important}.consul-bucket-list .copy-btn,.consul-exposed-path-list>ul>li>.detail dl .copy-btn,.consul-instance-checks .copy-btn,.consul-lock-session-list dl .copy-btn,.consul-lock-session-list ul>li:not(:first-child)>.detail dl .copy-btn,.consul-upstream-instance-list dl .copy-btn,.list-collection>ul>li:not(:first-child)>.detail dl .copy-btn,.tag-list .copy-btn,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .copy-btn,section[data-route="dc.show.license"] .validity dl .copy-btn,td.tags .copy-btn{top:0!important}.consul-bucket-list .copy-btn:empty::before,.consul-exposed-path-list>ul>li>.detail dl .copy-btn:empty::before,.consul-instance-checks .copy-btn:empty::before,.consul-lock-session-list dl .copy-btn:empty::before,.consul-upstream-instance-list dl .copy-btn:empty::before,.list-collection>ul>li:not(:first-child)>.detail dl .copy-btn:empty::before,.tag-list .copy-btn:empty::before,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .copy-btn:empty::before,section[data-route="dc.show.license"] .validity dl .copy-btn:empty::before,td.tags .copy-btn:empty::before{left:0!important}.definition-table>dl{display:grid;grid-template-columns:140px auto;grid-gap:.4em 20px;margin-bottom:1.4em}.disclosure-menu{position:relative}.disclosure-menu [aria-expanded]~*{overflow-y:auto!important;will-change:scrollPosition}.more-popover-menu>[type=checkbox],.more-popover-menu>[type=checkbox]~:not(.animating):not(label),.popover-menu>[type=checkbox],.popover-menu>[type=checkbox]~:not(.animating):not(label),table.has-actions tr>.actions>[type=checkbox],table.has-actions tr>.actions>[type=checkbox]~:not(.animating):not(label),table.with-details tr>.actions>[type=checkbox],table.with-details tr>.actions>[type=checkbox]~:not(.animating):not(label){display:none}.more-popover-menu>[type=checkbox]:checked~:not(label),.popover-menu>[type=checkbox]:checked~:not(label),table.has-actions tr>.actions>[type=checkbox]:checked~:not(label),table.with-details tr>.actions>[type=checkbox]:checked~:not(label){display:block}table.dom-recycling{position:relative}table.dom-recycling tr>*{overflow:hidden}.list-collection-scroll-virtual>ul,table.dom-recycling tbody{overflow-x:hidden!important}table.dom-recycling dd{flex-wrap:nowrap}table.dom-recycling dd>*{margin-bottom:0}.empty-state,.empty-state>div{display:flex;flex-direction:column}.empty-state header :first-child{padding:0;margin:0}.empty-state{margin-top:0!important;padding-bottom:2.8em;background-color:var(--token-color-surface-faint)}.empty-state>*{width:370px;margin:0 auto}.empty-state button{margin:0 auto;display:inline}.empty-state header :first-child{margin-bottom:-3px;border-bottom:none}.empty-state header{margin-top:1.8em;margin-bottom:.5em}.empty-state>ul{display:flex;justify-content:space-between;margin-top:1em}.empty-state>ul>li>*,.empty-state>ul>li>label>button{display:inline-flex;align-items:center}.empty-state>div:only-child{padding:50px 0 10px;text-align:center}.empty-state header::before{font-size:2.6em;position:relative;top:-3px;float:left;margin-right:10px}.oidc-select button.reset,.type-dialog{float:right}.empty-state>ul>li>::before,.empty-state>ul>li>label>button::before{margin-top:-1px;margin-right:.5em}.empty-state li[class*=-link]>::after{margin-left:5px}html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup]{border:var(--decor-border-100);border-color:var(--token-color-palette-neutral-300);border-radius:var(--decor-radius-100)}html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] input[type=radio]:checked+*,html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] input[type=radio]:focus+*,html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] input[type=radio]:hover+*{box-shadow:var(--token-elevation-high-box-shadow);background-color:var(--token-color-surface-primary)}@media (min-width:996px){html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup]{display:flex}html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label{flex-grow:1}}html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] input[type=radio]{display:none}.app-view>div form:not(.filter-bar) [role=radiogroup] label,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea,.app-view>div form:not(.filter-bar) [role=radiogroup] label>em,.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.modal-dialog [role=document] .type-password,.modal-dialog [role=document] .type-password textarea,.modal-dialog [role=document] .type-password>em,.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-select,.modal-dialog [role=document] .type-select textarea,.modal-dialog [role=document] .type-select>em,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] .type-text,.modal-dialog [role=document] .type-text textarea,.modal-dialog [role=document] .type-text>em,.modal-dialog [role=document] .type-text>span,.modal-dialog [role=document] [role=radiogroup] label,.modal-dialog [role=document] [role=radiogroup] label textarea,.modal-dialog [role=document] [role=radiogroup] label>em,.modal-dialog [role=document] [role=radiogroup] label>span,.modal-dialog [role=document] form button+em,.oidc-select label,.oidc-select label textarea,.oidc-select label>em,.oidc-select label>span,.type-toggle,.type-toggle textarea,.type-toggle>em,.type-toggle>span,html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label,html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label span,main .type-password,main .type-password textarea,main .type-password>em,main .type-password>span,main .type-select,main .type-select textarea,main .type-select>em,main .type-select>span,main .type-text,main .type-text textarea,main .type-text>em,main .type-text>span,main form button+em,span.label{display:block}html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup],html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label,html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label span{height:100%}html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label span{padding:5px 14px}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password],.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text],.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea,.modal-dialog [role=document] .type-password [type=password],.modal-dialog [role=document] .type-password [type=text],.modal-dialog [role=document] .type-password textarea,.modal-dialog [role=document] .type-select [type=password],.modal-dialog [role=document] .type-select [type=text],.modal-dialog [role=document] .type-select textarea,.modal-dialog [role=document] .type-text [type=password],.modal-dialog [role=document] .type-text [type=text],.modal-dialog [role=document] .type-text textarea,.modal-dialog [role=document] [role=radiogroup] label [type=password],.modal-dialog [role=document] [role=radiogroup] label [type=text],.modal-dialog [role=document] [role=radiogroup] label textarea,.oidc-select label [type=password],.oidc-select label [type=text],.oidc-select label textarea,.type-toggle [type=password],.type-toggle [type=text],.type-toggle textarea,main .type-password [type=password],main .type-password [type=text],main .type-password textarea,main .type-select [type=password],main .type-select [type=text],main .type-select textarea,main .type-text [type=password],main .type-text [type=text],main .type-text textarea{-moz-appearance:none;-webkit-appearance:none;box-shadow:var(--token-surface-inset-box-shadow);border-radius:var(--decor-radius-100);border:var(--decor-border-100);outline:0}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]:-moz-read-only,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]:-moz-read-only,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea:-moz-read-only,.modal-dialog [role=document] .type-password [type=password]:-moz-read-only,.modal-dialog [role=document] .type-password [type=text]:-moz-read-only,.modal-dialog [role=document] .type-password textarea:-moz-read-only,.modal-dialog [role=document] .type-select [type=password]:-moz-read-only,.modal-dialog [role=document] .type-select [type=text]:-moz-read-only,.modal-dialog [role=document] .type-select textarea:-moz-read-only,.modal-dialog [role=document] .type-text [type=password]:-moz-read-only,.modal-dialog [role=document] .type-text [type=text]:-moz-read-only,.modal-dialog [role=document] .type-text textarea:-moz-read-only,.modal-dialog [role=document] [role=radiogroup] label [type=password]:-moz-read-only,.modal-dialog [role=document] [role=radiogroup] label [type=text]:-moz-read-only,.modal-dialog [role=document] [role=radiogroup] label textarea:-moz-read-only,.oidc-select label [type=password]:-moz-read-only,.oidc-select label [type=text]:-moz-read-only,.oidc-select label textarea:-moz-read-only,.type-toggle [type=password]:-moz-read-only,.type-toggle [type=text]:-moz-read-only,.type-toggle textarea:-moz-read-only,main .type-password [type=password]:-moz-read-only,main .type-password [type=text]:-moz-read-only,main .type-password textarea:-moz-read-only,main .type-select [type=password]:-moz-read-only,main .type-select [type=text]:-moz-read-only,main .type-select textarea:-moz-read-only,main .type-text [type=password]:-moz-read-only,main .type-text [type=text]:-moz-read-only,main .type-text textarea:-moz-read-only{cursor:not-allowed}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]:disabled,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]:read-only,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]:disabled,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]:read-only,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea:disabled,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea:read-only,.modal-dialog [role=document] .type-password [type=password]:disabled,.modal-dialog [role=document] .type-password [type=password]:read-only,.modal-dialog [role=document] .type-password [type=text]:disabled,.modal-dialog [role=document] .type-password [type=text]:read-only,.modal-dialog [role=document] .type-password textarea:disabled,.modal-dialog [role=document] .type-password textarea:read-only,.modal-dialog [role=document] .type-select [type=password]:disabled,.modal-dialog [role=document] .type-select [type=password]:read-only,.modal-dialog [role=document] .type-select [type=text]:disabled,.modal-dialog [role=document] .type-select [type=text]:read-only,.modal-dialog [role=document] .type-select textarea:disabled,.modal-dialog [role=document] .type-select textarea:read-only,.modal-dialog [role=document] .type-text [type=password]:disabled,.modal-dialog [role=document] .type-text [type=password]:read-only,.modal-dialog [role=document] .type-text [type=text]:disabled,.modal-dialog [role=document] .type-text [type=text]:read-only,.modal-dialog [role=document] .type-text textarea:disabled,.modal-dialog [role=document] .type-text textarea:read-only,.modal-dialog [role=document] [role=radiogroup] label [type=password]:disabled,.modal-dialog [role=document] [role=radiogroup] label [type=password]:read-only,.modal-dialog [role=document] [role=radiogroup] label [type=text]:disabled,.modal-dialog [role=document] [role=radiogroup] label [type=text]:read-only,.modal-dialog [role=document] [role=radiogroup] label textarea:disabled,.modal-dialog [role=document] [role=radiogroup] label textarea:read-only,.oidc-select label [type=password]:disabled,.oidc-select label [type=password]:read-only,.oidc-select label [type=text]:disabled,.oidc-select label [type=text]:read-only,.oidc-select label textarea:disabled,.oidc-select label textarea:read-only,.type-toggle [type=password]:disabled,.type-toggle [type=password]:read-only,.type-toggle [type=text]:disabled,.type-toggle [type=text]:read-only,.type-toggle textarea:disabled,.type-toggle textarea:read-only,main .type-password [type=password]:disabled,main .type-password [type=password]:read-only,main .type-password [type=text]:disabled,main .type-password [type=text]:read-only,main .type-password textarea:disabled,main .type-password textarea:read-only,main .type-select [type=password]:disabled,main .type-select [type=password]:read-only,main .type-select [type=text]:disabled,main .type-select [type=text]:read-only,main .type-select textarea:disabled,main .type-select textarea:read-only,main .type-text [type=password]:disabled,main .type-text [type=password]:read-only,main .type-text [type=text]:disabled,main .type-text [type=text]:read-only,main .type-text textarea:disabled,main .type-text textarea:read-only,textarea:disabled+.CodeMirror{cursor:not-allowed}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]::-moz-placeholder,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]::-moz-placeholder,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea::-moz-placeholder,.modal-dialog [role=document] .type-password [type=password]::-moz-placeholder,.modal-dialog [role=document] .type-password [type=text]::-moz-placeholder,.modal-dialog [role=document] .type-password textarea::-moz-placeholder,.modal-dialog [role=document] .type-select [type=password]::-moz-placeholder,.modal-dialog [role=document] .type-select [type=text]::-moz-placeholder,.modal-dialog [role=document] .type-select textarea::-moz-placeholder,.modal-dialog [role=document] .type-text [type=password]::-moz-placeholder,.modal-dialog [role=document] .type-text [type=text]::-moz-placeholder,.modal-dialog [role=document] .type-text textarea::-moz-placeholder,.modal-dialog [role=document] [role=radiogroup] label [type=password]::-moz-placeholder,.modal-dialog [role=document] [role=radiogroup] label [type=text]::-moz-placeholder,.modal-dialog [role=document] [role=radiogroup] label textarea::-moz-placeholder,.oidc-select label [type=password]::-moz-placeholder,.oidc-select label [type=text]::-moz-placeholder,.oidc-select label textarea::-moz-placeholder,.type-toggle [type=password]::-moz-placeholder,.type-toggle [type=text]::-moz-placeholder,.type-toggle textarea::-moz-placeholder,main .type-password [type=password]::-moz-placeholder,main .type-password [type=text]::-moz-placeholder,main .type-password textarea::-moz-placeholder,main .type-select [type=password]::-moz-placeholder,main .type-select [type=text]::-moz-placeholder,main .type-select textarea::-moz-placeholder,main .type-text [type=password]::-moz-placeholder,main .type-text [type=text]::-moz-placeholder,main .type-text textarea::-moz-placeholder{color:var(--token-color-foreground-disabled)}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]::placeholder,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]::placeholder,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea::placeholder,.app-view>div form:not(.filter-bar) [role=radiogroup] label>em,.modal-dialog [role=document] .type-password [type=password]::placeholder,.modal-dialog [role=document] .type-password [type=text]::placeholder,.modal-dialog [role=document] .type-password textarea::placeholder,.modal-dialog [role=document] .type-password>em,.modal-dialog [role=document] .type-select [type=password]::placeholder,.modal-dialog [role=document] .type-select [type=text]::placeholder,.modal-dialog [role=document] .type-select textarea::placeholder,.modal-dialog [role=document] .type-select>em,.modal-dialog [role=document] .type-text [type=password]::placeholder,.modal-dialog [role=document] .type-text [type=text]::placeholder,.modal-dialog [role=document] .type-text textarea::placeholder,.modal-dialog [role=document] .type-text>em,.modal-dialog [role=document] [role=radiogroup] label [type=password]::placeholder,.modal-dialog [role=document] [role=radiogroup] label [type=text]::placeholder,.modal-dialog [role=document] [role=radiogroup] label textarea::placeholder,.modal-dialog [role=document] [role=radiogroup] label>em,.modal-dialog [role=document] form button+em,.modal-dialog [role=document] form fieldset>p,.oidc-select label [type=password]::placeholder,.oidc-select label [type=text]::placeholder,.oidc-select label textarea::placeholder,.oidc-select label>em,.type-toggle [type=password]::placeholder,.type-toggle [type=text]::placeholder,.type-toggle textarea::placeholder,.type-toggle>em,main .type-password [type=password]::placeholder,main .type-password [type=text]::placeholder,main .type-password textarea::placeholder,main .type-password>em,main .type-select [type=password]::placeholder,main .type-select [type=text]::placeholder,main .type-select textarea::placeholder,main .type-select>em,main .type-text [type=password]::placeholder,main .type-text [type=text]::placeholder,main .type-text textarea::placeholder,main .type-text>em,main form button+em,main form fieldset>p{color:var(--token-color-foreground-disabled)}.has-error>input,.has-error>textarea{border-color:var(--decor-error,var(--token-color-foreground-critical))!important}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password],.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text],.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea,.modal-dialog [role=document] .type-password [type=password],.modal-dialog [role=document] .type-password [type=text],.modal-dialog [role=document] .type-password textarea,.modal-dialog [role=document] .type-select [type=password],.modal-dialog [role=document] .type-select [type=text],.modal-dialog [role=document] .type-select textarea,.modal-dialog [role=document] .type-text [type=password],.modal-dialog [role=document] .type-text [type=text],.modal-dialog [role=document] .type-text textarea,.modal-dialog [role=document] [role=radiogroup] label [type=password],.modal-dialog [role=document] [role=radiogroup] label [type=text],.modal-dialog [role=document] [role=radiogroup] label textarea,.oidc-select label [type=password],.oidc-select label [type=text],.oidc-select label textarea,.type-toggle [type=password],.type-toggle [type=text],.type-toggle textarea,main .type-password [type=password],main .type-password [type=text],main .type-password textarea,main .type-select [type=password],main .type-select [type=text],main .type-select textarea,main .type-text [type=password],main .type-text [type=text],main .type-text textarea{color:var(--token-color-foreground-faint);border-color:var(--token-color-palette-neutral-300)}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]:hover,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]:hover,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea:hover,.modal-dialog [role=document] .type-password [type=password]:hover,.modal-dialog [role=document] .type-password [type=text]:hover,.modal-dialog [role=document] .type-password textarea:hover,.modal-dialog [role=document] .type-select [type=password]:hover,.modal-dialog [role=document] .type-select [type=text]:hover,.modal-dialog [role=document] .type-select textarea:hover,.modal-dialog [role=document] .type-text [type=password]:hover,.modal-dialog [role=document] .type-text [type=text]:hover,.modal-dialog [role=document] .type-text textarea:hover,.modal-dialog [role=document] [role=radiogroup] label [type=password]:hover,.modal-dialog [role=document] [role=radiogroup] label [type=text]:hover,.modal-dialog [role=document] [role=radiogroup] label textarea:hover,.oidc-select label [type=password]:hover,.oidc-select label [type=text]:hover,.oidc-select label textarea:hover,.type-toggle [type=password]:hover,.type-toggle [type=text]:hover,.type-toggle textarea:hover,main .type-password [type=password]:hover,main .type-password [type=text]:hover,main .type-password textarea:hover,main .type-select [type=password]:hover,main .type-select [type=text]:hover,main .type-select textarea:hover,main .type-text [type=password]:hover,main .type-text [type=text]:hover,main .type-text textarea:hover{border-color:var(--token-color-foreground-faint)}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]:focus,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]:focus,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea:focus,.modal-dialog [role=document] .type-password [type=password]:focus,.modal-dialog [role=document] .type-password [type=text]:focus,.modal-dialog [role=document] .type-password textarea:focus,.modal-dialog [role=document] .type-select [type=password]:focus,.modal-dialog [role=document] .type-select [type=text]:focus,.modal-dialog [role=document] .type-select textarea:focus,.modal-dialog [role=document] .type-text [type=password]:focus,.modal-dialog [role=document] .type-text [type=text]:focus,.modal-dialog [role=document] .type-text textarea:focus,.modal-dialog [role=document] [role=radiogroup] label [type=password]:focus,.modal-dialog [role=document] [role=radiogroup] label [type=text]:focus,.modal-dialog [role=document] [role=radiogroup] label textarea:focus,.oidc-select label [type=password]:focus,.oidc-select label [type=text]:focus,.oidc-select label textarea:focus,.type-toggle [type=password]:focus,.type-toggle [type=text]:focus,.type-toggle textarea:focus,main .type-password [type=password]:focus,main .type-password [type=text]:focus,main .type-password textarea:focus,main .type-select [type=password]:focus,main .type-select [type=text]:focus,main .type-select textarea:focus,main .type-text [type=password]:focus,main .type-text [type=text]:focus,main .type-text textarea:focus{border-color:var(--typo-action,var(--token-color-foreground-action))}.app-view>div form:not(.filter-bar) [role=radiogroup] label a,.modal-dialog [role=document] .type-password a,.modal-dialog [role=document] .type-select a,.modal-dialog [role=document] .type-text a,.modal-dialog [role=document] [role=radiogroup] label a,.oidc-select label a,.type-toggle a,main .type-password a,main .type-select a,main .type-text a{display:inline}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password],.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text],.modal-dialog [role=document] .type-password [type=password],.modal-dialog [role=document] .type-password [type=text],.modal-dialog [role=document] .type-select [type=password],.modal-dialog [role=document] .type-select [type=text],.modal-dialog [role=document] .type-text [type=password],.modal-dialog [role=document] .type-text [type=text],.modal-dialog [role=document] [role=radiogroup] label [type=password],.modal-dialog [role=document] [role=radiogroup] label [type=text],.oidc-select label [type=password],.oidc-select label [type=text],.type-toggle [type=password],.type-toggle [type=text],main .type-password [type=password],main .type-password [type=text],main .type-select [type=password],main .type-select [type=text],main .type-text [type=password],main .type-text [type=text]{display:inline-flex;justify-content:flex-start;max-width:100%;width:100%;height:0;padding:17px 13px}.consul-exposed-path-list>ul>li>.header dt,.consul-lock-session-list ul>li:not(:first-child)>.header dt,.consul-upstream-instance-list li>.header dt,.list-collection>ul>li:not(:first-child)>.header dt,.type-toggle input{display:none}.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea,.modal-dialog [role=document] .type-password textarea,.modal-dialog [role=document] .type-select textarea,.modal-dialog [role=document] .type-text textarea,.modal-dialog [role=document] [role=radiogroup] label textarea,.oidc-select label textarea,.type-toggle textarea,main .type-password textarea,main .type-select textarea,main .type-text textarea{resize:vertical;max-width:100%;min-width:100%;min-height:70px;padding:6px 13px}.app-view>div form:not(.filter-bar) [role=radiogroup],.app-view>div form:not(.filter-bar) [role=radiogroup] label,.checkbox-group,.modal-dialog [role=document] .type-password,.modal-dialog [role=document] .type-select,.modal-dialog [role=document] .type-text,.modal-dialog [role=document] [role=radiogroup],.modal-dialog [role=document] [role=radiogroup] label,.modal-dialog [role=document] form table,.oidc-select label,.type-toggle,main .type-password,main .type-select,main .type-text,main form table{margin-bottom:1.4em}.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] .type-text>span,.modal-dialog [role=document] [role=radiogroup] label>span,.oidc-select label>span,.type-toggle>span,main .type-password>span,main .type-select>span,main .type-text>span,span.label{color:var(--typo-contrast,inherit);margin-bottom:.3em}.app-view>div form:not(.filter-bar) [role=radiogroup] label>em,.modal-dialog [role=document] .type-password>em,.modal-dialog [role=document] .type-select>em,.modal-dialog [role=document] .type-text>em,.modal-dialog [role=document] [role=radiogroup] label>em,.modal-dialog [role=document] form button+em,.oidc-select label>em,.type-toggle>em,main .type-password>em,main .type-select>em,main .type-text>em,main form button+em{margin-top:2px}.app-view>div form:not(.filter-bar) [role=radiogroup] label>span+em,.modal-dialog [role=document] .type-password>span+em,.modal-dialog [role=document] .type-select>span+em,.modal-dialog [role=document] .type-text>span+em,.modal-dialog [role=document] [role=radiogroup] label>span+em,.oidc-select label>span+em,.type-toggle>span+em,main .type-password>span+em,main .type-select>span+em,main .type-text>span+em,span.label+em{margin-top:-.5em;margin-bottom:.5em}.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] label.type-text>span,main .type-password>span,main .type-select>span,main label.type-text>span{line-height:2.2em}.type-toggle+.checkbox-group{margin-top:-1em}.consul-exposed-path-list>ul>li,.consul-intention-permission-header-list>ul>li,.consul-intention-permission-list>ul>li,.consul-lock-session-list ul>li:not(:first-child),.consul-upstream-instance-list li,.list-collection>ul>li:not(:first-child){list-style-type:none;border:var(--decor-border-100);border-top-color:transparent;border-bottom-color:var(--token-color-surface-interactive-active);border-right-color:transparent;border-left-color:transparent;--horizontal-padding:12px;--vertical-padding:10px;padding:var(--vertical-padding) 0;padding-left:var(--horizontal-padding)}.consul-auth-method-list>ul>li:active:not(:first-child),.consul-auth-method-list>ul>li:focus:not(:first-child),.consul-auth-method-list>ul>li:hover:not(:first-child),.consul-exposed-path-list>ul>li.linkable:active,.consul-exposed-path-list>ul>li.linkable:focus,.consul-exposed-path-list>ul>li.linkable:hover,.consul-intention-permission-list:not(.readonly)>ul>li:active,.consul-intention-permission-list:not(.readonly)>ul>li:focus,.consul-intention-permission-list:not(.readonly)>ul>li:hover,.consul-lock-session-list ul>li.linkable:active:not(:first-child),.consul-lock-session-list ul>li.linkable:focus:not(:first-child),.consul-lock-session-list ul>li.linkable:hover:not(:first-child),.consul-node-list>ul>li:active:not(:first-child),.consul-node-list>ul>li:focus:not(:first-child),.consul-node-list>ul>li:hover:not(:first-child),.consul-policy-list>ul>li:active:not(:first-child),.consul-policy-list>ul>li:focus:not(:first-child),.consul-policy-list>ul>li:hover:not(:first-child),.consul-role-list>ul>li:active:not(:first-child),.consul-role-list>ul>li:focus:not(:first-child),.consul-role-list>ul>li:hover:not(:first-child),.consul-service-instance-list>ul>li:active:not(:first-child),.consul-service-instance-list>ul>li:focus:not(:first-child),.consul-service-instance-list>ul>li:hover:not(:first-child),.consul-token-list>ul>li:active:not(:first-child),.consul-token-list>ul>li:focus:not(:first-child),.consul-token-list>ul>li:hover:not(:first-child),.consul-upstream-instance-list li.linkable:active,.consul-upstream-instance-list li.linkable:focus,.consul-upstream-instance-list li.linkable:hover,.list-collection>ul>li.linkable:active:not(:first-child),.list-collection>ul>li.linkable:focus:not(:first-child),.list-collection>ul>li.linkable:hover:not(:first-child){border-color:var(--token-color-surface-interactive-active);box-shadow:var(--token-elevation-high-box-shadow);border-top-color:transparent;cursor:pointer}.radio-card,.tippy-box{box-shadow:var(--token-surface-mid-box-shadow)}.consul-exposed-path-list>ul>li>.header,.consul-lock-session-list ul>li:not(:first-child)>.header,.consul-upstream-instance-list li>.header,.list-collection>ul>li:not(:first-child)>.header{color:var(--token-color-hashicorp-brand)}.consul-exposed-path-list>ul>li>.header *,.consul-lock-session-list ul>li:not(:first-child)>.header *,.consul-upstream-instance-list li>.header *,.list-collection>ul>li:not(:first-child)>.header *{color:inherit}.consul-exposed-path-list>ul>li>.detail,.consul-lock-session-list ul>li:not(:first-child)>.detail,.consul-upstream-instance-list li>.detail,.list-collection>ul>li:not(:first-child)>.detail,.radio-card{color:var(--token-color-foreground-faint)}.consul-exposed-path-list>ul>li>.detail a,.consul-lock-session-list ul>li:not(:first-child)>.detail a,.consul-upstream-instance-list li>.detail a,.list-collection>ul>li:not(:first-child)>.detail a{color:inherit}.consul-exposed-path-list>ul>li>.detail a:hover,.consul-lock-session-list ul>li:not(:first-child)>.detail a:hover,.consul-upstream-instance-list li>.detail a:hover,.list-collection>ul>li:not(:first-child)>.detail a:hover{color:var(--token-color-foreground-action);text-decoration:underline}.consul-exposed-path-list>ul>li>.detail,.consul-exposed-path-list>ul>li>.header>dl:first-child,.consul-lock-session-list ul>li:not(:first-child)>.detail,.consul-lock-session-list ul>li:not(:first-child)>.header>dl:first-child,.consul-upstream-instance-list li>.detail,.consul-upstream-instance-list li>.header>dl:first-child,.list-collection>ul>li:not(:first-child)>.detail,.list-collection>ul>li:not(:first-child)>.header>dl:first-child{margin-right:6px}.consul-exposed-path-list>ul>li>.header dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header dd::before,.consul-upstream-instance-list li>.header dd::before,.list-collection>ul>li:not(:first-child)>.header dd::before{font-size:.9em}.consul-exposed-path-list>ul>li>.detail,.consul-exposed-path-list>ul>li>.header,.consul-lock-session-list ul>li:not(:first-child)>.detail,.consul-lock-session-list ul>li:not(:first-child)>.header,.consul-upstream-instance-list li>.detail,.consul-upstream-instance-list li>.header,.list-collection>ul>li:not(:first-child)>.detail,.list-collection>ul>li:not(:first-child)>.header{display:flex;flex-wrap:nowrap;overflow-x:hidden}.consul-exposed-path-list>ul>li>.detail *,.consul-exposed-path-list>ul>li>.header *,.consul-lock-session-list ul>li:not(:first-child)>.detail *,.consul-lock-session-list ul>li:not(:first-child)>.header *,.consul-upstream-instance-list li>.detail *,.consul-upstream-instance-list li>.header *,.list-collection>ul>li:not(:first-child)>.detail *,.list-collection>ul>li:not(:first-child)>.header *{white-space:nowrap;flex-wrap:nowrap}.consul-exposed-path-list>ul>li>.detail>span,.consul-lock-session-list ul>li:not(:first-child)>.detail>span,.consul-upstream-instance-list li>.detail>span,.list-collection>ul>li:not(:first-child)>.detail>span{margin-right:18px}.consul-intention-permission-header-list>ul>li,.consul-intention-permission-list>ul>li{padding-top:0!important;padding-bottom:0!important}.consul-intention-permission-header-list>ul>li .detail,.consul-intention-permission-list>ul>li .detail{grid-row-start:header!important;grid-row-end:detail!important;align-self:center!important;padding:5px 0}.consul-intention-permission-header-list>ul>li .popover-menu>[type=checkbox]+label,.consul-intention-permission-list>ul>li .popover-menu>[type=checkbox]+label{padding:0}.consul-intention-permission-header-list>ul>li .popover-menu>[type=checkbox]+label+div:not(.above),.consul-intention-permission-list>ul>li .popover-menu>[type=checkbox]+label+div:not(.above){top:30px}.has-error>strong{font-style:normal;font-weight:var(--token-typography-font-weight-regular);color:inherit;color:var(--token-color-foreground-critical);position:relative;padding-left:20px}.has-error>strong::before{color:var(--token-color-foreground-critical);position:absolute;top:50%;left:0;margin-top:-8px}.more-popover-menu .popover-menu>[type=checkbox]+label,table.has-actions tr>.actions .popover-menu>[type=checkbox]+label,table.with-details tr>.actions .popover-menu>[type=checkbox]+label{padding:7px}.more-popover-menu .popover-menu>[type=checkbox]+label>*,table.has-actions tr>.actions .popover-menu>[type=checkbox]+label>*,table.with-details tr>.actions .popover-menu>[type=checkbox]+label>*{background-color:transparent;border-radius:var(--decor-radius-100);width:30px;height:30px;font-size:0}.more-popover-menu .popover-menu>[type=checkbox]+label>:active,.more-popover-menu .popover-menu>[type=checkbox]+label>:focus,.more-popover-menu .popover-menu>[type=checkbox]+label>:hover,.radio-card>:first-child,table.has-actions tr>.actions .popover-menu>[type=checkbox]+label>:active,table.has-actions tr>.actions .popover-menu>[type=checkbox]+label>:focus,table.has-actions tr>.actions .popover-menu>[type=checkbox]+label>:hover,table.with-details tr>.actions .popover-menu>[type=checkbox]+label>:active,table.with-details tr>.actions .popover-menu>[type=checkbox]+label>:focus,table.with-details tr>.actions .popover-menu>[type=checkbox]+label>:hover{background-color:var(--token-color-surface-strong)}.more-popover-menu .popover-menu>[type=checkbox]+label>::after,table.has-actions tr>.actions .popover-menu>[type=checkbox]+label>::after,table.with-details tr>.actions .popover-menu>[type=checkbox]+label>::after{--icon-name:icon-more-horizontal;--icon-color:var(--token-color-foreground-strong);--icon-size:icon-300;content:"";position:absolute;top:50%;left:50%;margin-top:-8px;margin-left:-8px}.oidc-select [class$=-oidc-provider]::before{width:22px;height:22px;flex:0 0 auto;margin-right:10px}.oidc-select .ember-power-select-trigger,.oidc-select li{margin-bottom:1em}.informed-action header,.radio-card header{margin-bottom:.5em}.oidc-select .ember-power-select-trigger{width:100%}.radio-card{border:var(--decor-border-100);border-radius:var(--decor-radius-100);border-color:var(--token-color-surface-interactive-active);cursor:pointer;float:none!important;margin-right:0!important;display:flex!important}.checked.radio-card{border-color:var(--token-color-foreground-action)}.checked.radio-card>:first-child{background-color:var(--token-color-surface-action)}.radio-card header{color:var(--token-color-hashicorp-brand)}.consul-intention-fieldsets .radio-card>:last-child{padding-left:47px;position:relative}.consul-intention-fieldsets .radio-card>:last-child::before{position:absolute;left:14px;font-size:1rem}.radio-card>:first-child{padding:10px;display:grid;align-items:center;justify-items:center}.radio-card>:last-child{padding:18px}.consul-server-card,.disclosure-menu [aria-expanded]~*,.menu-panel,.more-popover-menu>[type=checkbox]+label+div,.popover-menu>[type=checkbox]+label+div,section[data-route="dc.show.serverstatus"] .server-failure-tolerance,section[data-route="dc.show.license"] aside,table.has-actions tr>.actions>[type=checkbox]+label+div,table.with-details tr>.actions>[type=checkbox]+label+div{--tone-border:var(--token-color-palette-neutral-300);border:var(--decor-border-100);border-radius:var(--decor-radius-200);box-shadow:var(--token-surface-high-box-shadow);color:var(--token-color-foreground-strong);background-color:var(--token-color-surface-primary);--padding-x:14px;--padding-y:14px;position:relative}.disclosure-menu [aria-expanded]~* [role=separator],.menu-panel [role=separator],.more-popover-menu>[type=checkbox]+label+div [role=separator],.popover-menu>[type=checkbox]+label+div [role=separator],table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator],table.with-details tr>.actions>[type=checkbox]+label+div [role=separator]{border-top:var(--decor-border-100);margin:0}.consul-server-card,.disclosure-menu [aria-expanded]~*,.disclosure-menu [aria-expanded]~* [role=separator],.menu-panel,.menu-panel [role=separator],.more-popover-menu>[type=checkbox]+label+div,.more-popover-menu>[type=checkbox]+label+div [role=separator],.popover-menu>[type=checkbox]+label+div,.popover-menu>[type=checkbox]+label+div [role=separator],section[data-route="dc.show.serverstatus"] .server-failure-tolerance,section[data-route="dc.show.license"] aside,table.has-actions tr>.actions>[type=checkbox]+label+div,table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator],table.with-details tr>.actions>[type=checkbox]+label+div,table.with-details tr>.actions>[type=checkbox]+label+div [role=separator]{border-color:var(--tone-border)}.paged-collection-scroll,[style*="--paged-row-height"]{overflow-y:auto!important;will-change:scrollPosition}[style*="--paged-start"]::before{content:"";display:block;height:var(--paged-start)}.consul-auth-method-type,.consul-external-source,.consul-health-check-list .health-check-output dd em,.consul-intention-list td strong,.consul-intention-permission-list strong,.consul-intention-search-bar li button span,.consul-kind,.consul-peer-search-bar li button span,.consul-server-card .health-status+dd,.consul-source,.consul-transparent-proxy,.discovery-chain .route-card>header ul li,.leader,.search-bar-status li:not(.remove-all),.topology-metrics-source-type,html[data-route^="dc.acls.index"] main td strong,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em,span.policy-node-identity,span.policy-service-identity{border-radius:var(--decor-radius-100);display:inline-flex;position:relative;align-items:center;white-space:nowrap}.consul-auth-method-type::before,.consul-external-source::before,.consul-health-check-list .health-check-output dd em::before,.consul-intention-list td strong::before,.consul-intention-permission-list strong::before,.consul-intention-search-bar li button span::before,.consul-kind::before,.consul-peer-search-bar li button span::before,.consul-server-card .health-status+dd::before,.consul-source::before,.consul-transparent-proxy::before,.discovery-chain .route-card>header ul li::before,.leader::before,.search-bar-status li:not(.remove-all)::before,.topology-metrics-source-type::before,html[data-route^="dc.acls.index"] main td strong::before,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl::before,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em::before,span.policy-node-identity::before,span.policy-service-identity::before{margin-right:4px;--icon-size:icon-300}.consul-auth-method-type,.consul-external-source,.consul-kind,.consul-server-card .health-status+dd,.consul-source,.consul-transparent-proxy,.leader,.search-bar-status li:not(.remove-all),.topology-metrics-source-type,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em,span.policy-node-identity,span.policy-service-identity{padding:0 8px;--icon-size:icon-200}.consul-intention-permission-list strong,.consul-peer-search-bar li button span,.discovery-chain .route-card>header ul li,html[data-route^="dc.acls.index"] main td strong,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl{padding:1px 5px}.consul-intention-list td strong,.consul-intention-search-bar li button span{padding:4px 8px}span.policy-node-identity::before,span.policy-service-identity::before{vertical-align:unset}span.policy-node-identity::before{content:"Node Identity: "}span.policy-service-identity::before{content:"Service Identity: "}.more-popover-menu>[type=checkbox]+label>*,.popover-menu>[type=checkbox]+label>*,table.has-actions tr>.actions>[type=checkbox]+label>*,table.with-details tr>.actions>[type=checkbox]+label>*{cursor:pointer}.more-popover-menu>[type=checkbox]+label>::after,.popover-menu>[type=checkbox]+label>::after,table.has-actions tr>.actions>[type=checkbox]+label>::after,table.with-details tr>.actions>[type=checkbox]+label>::after{width:16px;height:16px;position:relative}.more-popover-menu,.popover-menu,table.has-actions tr>.actions,table.with-details tr>.actions{position:relative}.more-popover-menu>[type=checkbox]+label,.popover-menu>[type=checkbox]+label,table.has-actions tr>.actions>[type=checkbox]+label,table.with-details tr>.actions>[type=checkbox]+label{display:block}.more-popover-menu>[type=checkbox]+label+div,.popover-menu>[type=checkbox]+label+div,table.has-actions tr>.actions>[type=checkbox]+label+div,table.with-details tr>.actions>[type=checkbox]+label+div{min-width:192px}.more-popover-menu>[type=checkbox]+label+div:not(.above),.popover-menu>[type=checkbox]+label+div:not(.above),table.has-actions tr>.actions>[type=checkbox]+label+div:not(.above),table.with-details tr>.actions>[type=checkbox]+label+div:not(.above){top:38px}.more-popover-menu>[type=checkbox]+label+div:not(.left),.popover-menu>[type=checkbox]+label+div:not(.left),table.has-actions tr>.actions>[type=checkbox]+label+div:not(.left),table.with-details tr>.actions>[type=checkbox]+label+div:not(.left){right:5px}.popover-menu .menu-panel{position:absolute!important}.popover-select label{height:100%}.popover-select label>*{padding:0 8px!important;height:100%!important;justify-content:space-between!important;min-width:auto!important}.popover-select label>::after{margin-left:6px}.popover-select button::before{margin-right:10px}.popover-select .value-passing button::before{color:var(--token-color-foreground-success)}.popover-select .value-warning button::before{color:var(--token-color-foreground-warning)}.popover-select .value-critical button::before{color:var(--token-color-foreground-critical)}.popover-select .value-empty button::before{color:var(--token-color-foreground-disabled)}.popover-select .value-unknown button::before,.type-source.popover-select li.partition button::before{color:var(--token-color-foreground-faint)}.type-source.popover-select li.aws button{text-transform:uppercase}.progress.indeterminate{width:100%;display:flex;align-items:center;justify-content:center;--icon-size:icon-700;--icon-name:var(--icon-loading);--icon-color:var(--token-color-foreground-faint)}.progress.indeterminate::before{content:""}.app-view>div form:not(.filter-bar) [role=radiogroup],.modal-dialog [role=document] [role=radiogroup]{overflow:hidden;padding-left:1px}.app-view>div form:not(.filter-bar) [role=radiogroup] label,.modal-dialog [role=document] [role=radiogroup] label{float:left}.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.modal-dialog [role=document] [role=radiogroup] label>span{float:right;margin-left:1em}.app-view>div form:not(.filter-bar) [role=radiogroup] label:not(:last-child),.modal-dialog [role=document] [role=radiogroup] label:not(:last-child){margin-right:25px}.app-view>div form:not(.filter-bar) [role=radiogroup] label,.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.modal-dialog [role=document] [role=radiogroup] label,.modal-dialog [role=document] [role=radiogroup] label>span{margin-bottom:0!important}.type-toggle label span{cursor:pointer}.type-toggle label span::after{border-radius:var(--decor-radius-full)}.type-toggle label span::before{border-radius:7px;left:0;width:24px;height:12px;margin-top:-5px}.type-negative.type-toggle{border:0}.app-view>header .title,.modal-dialog [role=document] table td,.modal-dialog [role=document] table th,main table td,main table th{border-bottom:var(--decor-border-100)}.type-toggle label span::after{background-color:var(--token-color-surface-primary);margin-top:-3px;width:8px;height:8px}.type-negative.type-toggle label input+span::before,.type-toggle label input:checked+span::before{background-color:var(--token-color-foreground-action)}.type-negative.type-toggle label input:checked+span::before,.type-toggle label span::before{background-color:var(--token-color-palette-neutral-300)}.type-toggle label{position:relative}.type-toggle label span{color:var(--token-color-foreground-strong);display:inline-block;padding-left:34px}.type-toggle label span::after,.type-toggle label span::before{position:absolute;display:block;content:"";top:50%}.type-negative.type-toggle label input+span::after,.type-toggle label input:checked+span::after{left:14px}.type-negative.type-toggle label input:checked+span::after,.type-toggle label span::after{left:2px}.consul-intention-list td.destination,.consul-intention-list td.source,.modal-dialog [role=document] table th,main table th{border-color:var(--token-color-palette-neutral-300)}.modal-dialog [role=document] table td,main table td{border-color:var(--token-color-palette-neutral-300);color:var(--token-color-foreground-faint);height:50px;vertical-align:middle}.modal-dialog [role=document] table td strong,.modal-dialog [role=document] table th,main table td strong,main table th{color:var(--token-color-foreground-faint)}.modal-dialog [role=document] table a,.tomography-graph .tick text,main table a{color:var(--token-color-foreground-strong)}.modal-dialog [role=document] table,main table{width:100%;border-collapse:collapse}table.dom-recycling tr{display:flex}table.dom-recycling tr>*{flex:1 1 auto;display:inline-flex;align-items:center}.modal-dialog [role=document] table th.actions input,main table th.actions input{display:none}.modal-dialog [role=document] table th.actions,main table th.actions{text-align:right}.modal-dialog [role=document] table td a,main table td a{display:block}.modal-dialog [role=document] table td.no-actions~.actions,main table td.no-actions~.actions{display:none}.modal-dialog [role=document] table td:not(.actions)>:only-child,main table td:not(.actions)>:only-child{overflow:hidden;text-overflow:ellipsis}.modal-dialog [role=document] table td:not(.actions)>*,main table td:not(.actions)>*{white-space:nowrap}.modal-dialog [role=document] table caption,main table caption{margin-bottom:.8em}.modal-dialog [role=document] table th,main table th{padding:.6em 0}.modal-dialog [role=document] table td a,.modal-dialog [role=document] table td:not(.actions),.modal-dialog [role=document] table th:not(.actions),main table td a,main table td:not(.actions),main table th:not(.actions){padding-right:.9em}.modal-dialog [role=document] table tbody td em,main table tbody td em{display:block;font-style:normal;font-weight:var(--token-typography-font-weight-regular);color:var(--token-color-foreground-faint)}table.has-actions tr>.actions,table.with-details tr>.actions{width:60px!important;overflow:visible}table.has-actions tr>.actions>[type=checkbox]+label,table.with-details tr>.actions>[type=checkbox]+label{position:absolute;right:5px}table.consul-metadata-list tbody tr{cursor:default}table.consul-metadata-list tbody tr:hover{box-shadow:none}.modal-dialog [role=document] table th span::after,main table th span::after{color:var(--token-color-foreground-faint);margin-left:4px}.modal-dialog [role=document] table tbody tr,main table tbody tr{cursor:pointer}.modal-dialog [role=document] table td:first-child,main table td:first-child{padding:0}.modal-dialog [role=document] table tbody tr:hover,main table tbody tr:hover{box-shadow:var(--token-elevation-high-box-shadow)}.modal-dialog [role=document] table td.folder::before,main table td.folder::before{background-color:var(--token-color-palette-neutral-300);margin-top:1px;margin-right:5px}@media (max-width:420px){.consul-intention-list tr>:nth-last-child(2),.modal-dialog [role=document] table tr>.actions,main table tr>.actions{display:none}}.voting-status-leader.consul-server-card .name{width:var(--tile-size,3rem);height:var(--tile-size,3rem)}.voting-status-leader.consul-server-card .name::before{display:block;content:"";width:100%;height:100%;border-radius:var(--decor-radius-250);border:var(--decor-border-100);background-image:linear-gradient(135deg,var(--token-color-consul-surface) 0,var(--token-color-consul-border) 100%);border-color:var(--token-color-border-faint)}.voting-status-leader.consul-server-card .name::after{content:"";position:absolute;top:calc(var(--tile-size,3rem)/ 4);left:calc(var(--tile-size,3rem)/ 4);--icon-name:icon-star-fill;--icon-size:icon-700;color:var(--token-color-consul-brand)}table.with-details td:only-child>div>label,table.with-details td>label{border-radius:var(--decor-radius-100);cursor:pointer;min-width:30px;min-height:30px;display:inline-flex;align-items:center;justify-content:center}table.with-details td:only-child>div>label:active,table.with-details td:only-child>div>label:focus,table.with-details td:only-child>div>label:hover,table.with-details td>label:active,table.with-details td>label:focus,table.with-details td>label:hover{background-color:var(--token-color-surface-strong)}table.dom-recycling tbody{top:33px!important;width:100%}table.dom-recycling caption~tbody{top:57px!important}table tr>:nth-last-child(2):first-child,table tr>:nth-last-child(2):first-child~*{width:50%}table tr>:nth-last-child(3):first-child,table tr>:nth-last-child(3):first-child~*{width:33.3333333333%}table tr>:nth-last-child(4):first-child,table tr>:nth-last-child(4):first-child~*{width:25%}table tr>:nth-last-child(5):first-child,table tr>:nth-last-child(5):first-child~*{width:20%}table.has-actions tr>:nth-last-child(2):first-child,table.has-actions tr>:nth-last-child(2):first-child~*{width:calc(100% - 60px)}table.has-actions tr>:nth-last-child(3):first-child,table.has-actions tr>:nth-last-child(3):first-child~*{width:calc(50% - 30px)}table.has-actions tr>:nth-last-child(4):first-child,table.has-actions tr>:nth-last-child(4):first-child~*{width:calc(33% - 20px)}table.has-actions tr>:nth-last-child(5):first-child,table.has-actions tr>:nth-last-child(5):first-child~*{width:calc(25% - 15px)}html[data-route^="dc.acls.policies"] [role=dialog] table tr>:not(last-child),html[data-route^="dc.acls.policies"] table tr>:not(last-child),html[data-route^="dc.acls.roles"] [role=dialog] table tr>:not(last-child),html[data-route^="dc.acls.roles"] main table.token-list tr>:not(last-child){width:120px}html[data-route^="dc.acls.policies"] table tr>:last-child,html[data-route^="dc.acls.roles"] [role=dialog] table tr>:last-child,html[data-route^="dc.acls.roles"] main table.token-list tr>:last-child{width:calc(100% - 240px)!important}table.with-details td:only-child{cursor:default;border:0}table.with-details td:only-child>div::before,table.with-details td:only-child>div>div,table.with-details td:only-child>div>label{background-color:var(--token-color-surface-primary)}table.with-details td:only-child>div>label::before{transform:rotate(180deg)}table.with-details td:only-child>div::before{background:var(--token-color-surface-interactive-active);content:"";display:block;height:1px;position:absolute;bottom:-20px;left:10px;width:calc(100% - 20px)}table.with-details tr>.actions{position:relative}table.with-details td:only-child>div>label,table.with-details td>label{pointer-events:auto;position:absolute;top:8px}table.with-details td:only-child>div>label span,table.with-details td>label span{display:none}table.with-details td>label{right:2px}table.with-details tr:nth-child(even) td{height:auto;position:relative;display:table-cell}table.with-details tr:nth-child(even) td>*{display:none}table.with-details td:only-child>div>label{right:11px}table.with-details tr:nth-child(even) td>input:checked+*{display:block}table.with-details td:only-child{overflow:visible;width:100%}table.with-details td:only-child>div{border:1px solid var(--token-color-palette-neutral-300);border-radius:var(--decor-radius-100);box-shadow:var(--token-surface-high-box-shadow);margin-bottom:20px;position:relative;left:-10px;right:-10px;width:calc(100% + 20px);margin-top:-51px;pointer-events:none;padding:10px}table.with-details td:only-child>div::after{content:"";display:block;clear:both}table.with-details td:only-child>div>div{pointer-events:auto;margin-top:36px}.consul-auth-method-binding-list dl,.consul-auth-method-view dl,.consul-auth-method-view section dl{display:flex;flex-wrap:wrap}.consul-auth-method-binding-list dl dd,.consul-auth-method-binding-list dl dt,.consul-auth-method-view dl dd,.consul-auth-method-view dl dt{padding:12px 0;margin:0;border-top:1px solid!important}.consul-auth-method-binding-list dl dt,.consul-auth-method-view dl dt{width:20%;font-weight:var(--token-typography-font-weight-bold)}.consul-auth-method-binding-list dl dd,.consul-auth-method-view dl dd{margin-left:auto;width:80%;display:flex}.consul-auth-method-binding-list dl dd>ul li,.consul-auth-method-view dl dd>ul li{display:flex}.consul-auth-method-binding-list dl dd>ul li:not(:last-of-type),.consul-auth-method-view dl dd>ul li:not(:last-of-type){padding-bottom:12px}.consul-auth-method-binding-list dl dt.check+dd,.consul-auth-method-view dl dt.check+dd{padding-top:16px}.consul-auth-method-binding-list dl>dd:last-of-type,.consul-auth-method-binding-list dl>dt:last-of-type,.consul-auth-method-view dl>dd:last-of-type,.consul-auth-method-view dl>dt:last-of-type{border-bottom:1px solid!important;border-color:var(--token-color-palette-neutral-300)!important}.consul-auth-method-binding-list dl dd,.consul-auth-method-binding-list dl dt,.consul-auth-method-view dl dd,.consul-auth-method-view dl dt{border-color:var(--token-color-palette-neutral-300)!important;color:var(--token-color-hashicorp-brand)!important}.consul-auth-method-binding-list dl dd .copy-button button::before,.consul-auth-method-view dl dd .copy-button button::before{background-color:var(--token-color-hashicorp-brand)}.consul-auth-method-binding-list dl dt.type+dd span::before,.consul-auth-method-view dl dt.type+dd span::before{margin-left:4px;background-color:var(--token-color-foreground-faint)}.tooltip-panel dt{cursor:pointer}.tooltip-panel dd>div::before{width:12px;height:12px;background-color:var(--token-color-surface-primary);border-top:1px solid var(--token-color-palette-neutral-300);border-right:1px solid var(--token-color-palette-neutral-300);transform:rotate(-45deg);position:absolute;left:16px;top:-7px}.tooltip-panel,.tooltip-panel dt{display:flex;flex-direction:column}.tooltip-panel dd>div.menu-panel{top:auto;overflow:visible}.tooltip-panel dd{display:none;position:relative;z-index:1;padding-top:10px;margin-bottom:-10px}.tooltip-panel:hover dd{display:block}.tooltip-panel dd>div{width:250px}.app-view>header .title{display:grid;grid-template-columns:1fr auto;grid-template-areas:"title actions";position:relative;z-index:5;padding-bottom:1.4em}.app-view>div form:not(.filter-bar) fieldset{border-bottom:var(--decor-border-200)}.app-view>header h1>em{color:var(--token-color-foreground-faint)}.app-view>header dd>a{color:var(--token-color-hashicorp-brand)}.app-view>div div>dl>dd{color:var(--token-color-foreground-disabled)}.app-view>div form:not(.filter-bar) fieldset,.app-view>header .title{border-color:var(--token-color-surface-interactive-active)}.app-view>header .title .title-left-container{grid-area:title;display:flex;flex-wrap:wrap;align-items:center;white-space:normal}.app-view>header .title .title-left-container>:first-child{flex-basis:100%}.app-view>header .title .title-left-container>:not(:first-child){margin-right:8px}.app-view>header .actions{grid-area:actions;align-self:end;display:flex;align-items:flex-start;margin-left:auto;margin-top:9px}.app-view>div form:not(.filter-bar) fieldset{padding-bottom:.3em;margin-bottom:2em}[for=toolbar-toggle]{background-position:0 4px;display:inline-block;width:26px;height:26px;cursor:pointer;color:var(--token-color-foreground-action)}#toolbar-toggle{display:none}@media (max-width:849px){.app-view>header .actions{margin-top:9px}}@media (min-width:996px){[for=toolbar-toggle]{display:none}}@media (max-width:995px){.app-view>header h1{display:inline-block}html[data-route$="dc.services.instance.show"] h1{display:block}#toolbar-toggle+*{display:none}#toolbar-toggle:checked+*{display:flex}}.brand-loader{position:absolute;top:50%;margin-top:-26px;left:50%}.app .notifications{position:fixed;z-index:100;bottom:2rem;left:1.5rem;pointer-events:none}.app .notifications .app-notification>*{min-width:400px}.app .notifications .app-notification{transition-property:opacity;width:-moz-fit-content;width:fit-content;max-width:80%;pointer-events:auto}.hashicorp-consul .consul-side-nav li.consul-disabled-nav{width:100%;min-height:var(--token-side-nav-body-list-item-height);padding:var(--token-side-nav-body-list-item-padding-vertical) var(--token-side-nav-body-list-item-padding-horizontal);color:var(--token-color-foreground-disabled)}.hashicorp-consul .consul-side-nav li.consul-side-nav__selector .consul-side-nav__selector-toggle{min-width:15.5rem}.hashicorp-consul .consul-side-nav li.consul-side-nav__selector .consul-side-nav__selector-toggle:disabled{color:var(--token-color-foreground-disabled);border-color:var(--token-color-border-primary)}.hashicorp-consul .consul-side-nav li.consul-side-nav__selector .consul-side-nav__selector-toggle:disabled:hover{background-color:transparent}.hashicorp-consul .consul-side-nav li.consul-side-nav__selector .hds-dropdown__content{min-width:15.5rem;max-height:500px}.hashicorp-consul .consul-side-nav .hds-side-nav__wrapper-body{overflow-y:unset;overflow-x:unset}.hashicorp-consul .consul-side-nav li.consul-side-nav__datacenter{display:flex;gap:.5rem;align-items:center;padding-left:.5rem}.hashicorp-consul .consul-side-nav .consul-side-nav__selector-group{margin-bottom:1.5rem}.hashicorp-consul .consul-side-nav .consul-datacenter-selector__dc-name{display:flex;align-items:center;gap:.5rem}.hashicorp-consul .consul-side-nav .consul-datacenter-selector__dc-name .consul-datacenter-selector__badges{display:flex;gap:.25rem}.hashicorp-consul .consul-side-nav .consul-side-nav__selector-title{margin-top:.5rem}.hashicorp-consul .consul-side-nav .consul-side-nav__selector-description{padding-top:.5rem}.disclosure-menu [aria-expanded]~*>div+ul,.menu-panel>div+ul,.more-popover-menu>[type=checkbox]+label+div>div+ul,.popover-menu>[type=checkbox]+label+div>div+ul,table.has-actions tr>.actions>[type=checkbox]+label+div>div+ul,table.with-details tr>.actions>[type=checkbox]+label+div>div+ul{border-top:var(--decor-border-100);border-color:var(--token-form--base-border-color-default)}.disclosure-menu [aria-expanded]~* [role=separator]:first-child:not(:empty),.menu-panel [role=separator]:first-child:not(:empty),.more-popover-menu>[type=checkbox]+label+div [role=separator]:first-child:not(:empty),.popover-menu>[type=checkbox]+label+div [role=separator]:first-child:not(:empty),table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator]:first-child:not(:empty),table.with-details tr>.actions>[type=checkbox]+label+div [role=separator]:first-child:not(:empty){border:none}.disclosure-menu [aria-expanded]~*>ul>li,.menu-panel>ul>li,.more-popover-menu>[type=checkbox]+label+div>ul>li,.popover-menu>[type=checkbox]+label+div>ul>li,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li{list-style-type:none}.disclosure-menu [aria-expanded]~*>ul .informed-action,.menu-panel>ul .informed-action,.more-popover-menu>[type=checkbox]+label+div>ul .informed-action,.popover-menu>[type=checkbox]+label+div>ul .informed-action,table.has-actions tr>.actions>[type=checkbox]+label+div>ul .informed-action,table.with-details tr>.actions>[type=checkbox]+label+div>ul .informed-action{border:0!important}.disclosure-menu [aria-expanded]~*>div,.menu-panel>div,.more-popover-menu>[type=checkbox]+label+div>div,.popover-menu>[type=checkbox]+label+div>div,table.has-actions tr>.actions>[type=checkbox]+label+div>div,table.with-details tr>.actions>[type=checkbox]+label+div>div{padding:.625rem var(--padding-x);white-space:normal;max-width:-moz-fit-content;max-width:fit-content}@supports not ((max-width:-moz-fit-content) or (max-width:fit-content)){.disclosure-menu [aria-expanded]~*>div,.menu-panel>div,.more-popover-menu>[type=checkbox]+label+div>div,.popover-menu>[type=checkbox]+label+div>div,table.has-actions tr>.actions>[type=checkbox]+label+div>div,table.with-details tr>.actions>[type=checkbox]+label+div>div{max-width:200px}}.disclosure-menu [aria-expanded]~*>div::before,.menu-panel>div::before,.more-popover-menu>[type=checkbox]+label+div>div::before,.popover-menu>[type=checkbox]+label+div>div::before,table.has-actions tr>.actions>[type=checkbox]+label+div>div::before,table.with-details tr>.actions>[type=checkbox]+label+div>div::before{position:absolute;left:15px;top:calc(10px + .1em)}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem]+*,.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem]+*,.disclosure-menu [aria-expanded]~*>ul>li>[role=option]+*,.menu-panel-deprecated>ul>li>div[role=menu],.menu-panel>ul>[role=treeitem]+*,.menu-panel>ul>li>[role=menuitem]+*,.menu-panel>ul>li>[role=option]+*,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]+*,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]+*,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option]+*,.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]+*,.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]+*,.popover-menu>[type=checkbox]+label+div>ul>li>[role=option]+*,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]+*,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]+*,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]+*,table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]+*,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]+*,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]+*{position:absolute;top:0;left:calc(100% + 10px)}.disclosure-menu [aria-expanded]~*>ul,.menu-panel>ul,.more-popover-menu>[type=checkbox]+label+div>ul,.popover-menu>[type=checkbox]+label+div>ul,table.has-actions tr>.actions>[type=checkbox]+label+div>ul,table.with-details tr>.actions>[type=checkbox]+label+div>ul{margin:0;padding:calc(var(--padding-y) - .625rem) 0;transition:transform 150ms}.disclosure-menu [aria-expanded]~*>ul,.disclosure-menu [aria-expanded]~*>ul>li,.disclosure-menu [aria-expanded]~*>ul>li>*,.menu-panel>ul,.menu-panel>ul>li,.menu-panel>ul>li>*,.more-popover-menu>[type=checkbox]+label+div>ul,.more-popover-menu>[type=checkbox]+label+div>ul>li,.more-popover-menu>[type=checkbox]+label+div>ul>li>*,.popover-menu>[type=checkbox]+label+div>ul,.popover-menu>[type=checkbox]+label+div>ul>li,.popover-menu>[type=checkbox]+label+div>ul>li>*,table.has-actions tr>.actions>[type=checkbox]+label+div>ul,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>*,table.with-details tr>.actions>[type=checkbox]+label+div>ul,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>*{width:100%}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{display:flex}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem]::after,.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem]::after,.disclosure-menu [aria-expanded]~*>ul>li>[role=option]::after,.menu-panel>ul>[role=treeitem]::after,.menu-panel>ul>li>[role=menuitem]::after,.menu-panel>ul>li>[role=option]::after,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]::after,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]::after,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option]::after,.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]::after,.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]::after,.popover-menu>[type=checkbox]+label+div>ul>li>[role=option]::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]::after{margin-left:auto;padding-right:var(--padding-x);transform:translate(calc(var(--padding-x)/ 2),0)}.disclosure-menu [aria-expanded]~* [role=separator],.menu-panel [role=separator],.more-popover-menu>[type=checkbox]+label+div [role=separator],.popover-menu>[type=checkbox]+label+div [role=separator],table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator],table.with-details tr>.actions>[type=checkbox]+label+div [role=separator]{text-transform:uppercase;color:var(--token-color-foreground-faint);padding-top:.375rem}.disclosure-menu [aria-expanded]~* [role=separator]:not(:first-child),.menu-panel [role=separator]:not(:first-child),.more-popover-menu>[type=checkbox]+label+div [role=separator]:not(:first-child),.popover-menu>[type=checkbox]+label+div [role=separator]:not(:first-child),table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator]:not(:first-child),table.with-details tr>.actions>[type=checkbox]+label+div [role=separator]:not(:first-child){margin-top:.275rem}.disclosure-menu [aria-expanded]~* [role=separator]:not(:empty),.menu-panel [role=separator]:not(:empty),.more-popover-menu>[type=checkbox]+label+div [role=separator]:not(:empty),.popover-menu>[type=checkbox]+label+div [role=separator]:not(:empty),table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator]:not(:empty),table.with-details tr>.actions>[type=checkbox]+label+div [role=separator]:not(:empty){padding-left:var(--padding-x);padding-right:var(--padding-x);padding-bottom:.125rem}.disclosure-menu [aria-expanded]~.menu-panel-confirming,.menu-panel-confirming.menu-panel,.more-popover-menu>[type=checkbox]+label+div.menu-panel-confirming,.popover-menu>[type=checkbox]+label+div.menu-panel-confirming,table.has-actions tr>.actions>[type=checkbox]+label+div.menu-panel-confirming,table.with-details tr>.actions>[type=checkbox]+label+div.menu-panel-confirming{overflow:hidden}.disclosure-menu [aria-expanded]~.menu-panel-confirming>ul,.menu-panel-confirming.menu-panel>ul,.more-popover-menu>[type=checkbox]+label+div.menu-panel-confirming>ul,.popover-menu>[type=checkbox]+label+div.menu-panel-confirming>ul,table.has-actions tr>.actions>[type=checkbox]+label+div.menu-panel-confirming>ul,table.with-details tr>.actions>[type=checkbox]+label+div.menu-panel-confirming>ul{transform:translateX(calc(-100% - 10px))}.disclosure-menu [aria-expanded]~*,.menu-panel,.more-popover-menu>[type=checkbox]+label+div,.popover-menu>[type=checkbox]+label+div,table.has-actions tr>.actions>[type=checkbox]+label+div,table.with-details tr>.actions>[type=checkbox]+label+div{overflow:hidden}.menu-panel-deprecated{position:absolute;transition:max-height 150ms;transition:min-height 150ms,max-height 150ms;min-height:0}.menu-panel-deprecated [type=checkbox]{display:none}.menu-panel-deprecated:not(.confirmation) [type=checkbox]~*{transition:transform 150ms}.confirmation.menu-panel-deprecated [role=menu]{min-height:205px!important}.menu-panel-deprecated [type=checkbox]:checked~*{transform:translateX(calc(-100% - 10px));min-height:143px;max-height:143px}.menu-panel-deprecated [id$="-"]:first-child:checked~ul label[for$="-"] * [role=menu],.menu-panel-deprecated [id$="-"]:first-child:checked~ul>li>[role=menu]{display:block}.menu-panel-deprecated>ul>li>:not(div[role=menu]),.tippy-box{position:relative}.menu-panel-deprecated:not(.left){right:0!important;left:auto!important}.left.menu-panel-deprecated{left:0}.menu-panel-deprecated:not(.above){top:28px}.above.menu-panel-deprecated{bottom:42px}.consul-upstream-instance-list dl.local-bind-socket-mode dt::after{display:inline;content:var(--horizontal-kv-list-key-separator)}.consul-bucket-list,.consul-exposed-path-list>ul>li>.detail dl,.consul-instance-checks,.consul-lock-session-list dl,.consul-lock-session-list ul>li:not(:first-child)>.detail dl,.consul-upstream-instance-list dl,.consul-upstream-instance-list li>.detail dl,.list-collection>ul>li:not(:first-child)>.detail dl,.tag-list,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl,section[data-route="dc.show.license"] .validity dl,td.tags{display:inline-flex;flex-wrap:nowrap;align-items:center}.consul-bucket-list:empty,.consul-exposed-path-list>ul>li>.detail dl:empty,.consul-instance-checks:empty,.consul-lock-session-list dl:empty,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:empty,.consul-upstream-instance-list dl:empty,.list-collection>ul>li:not(:first-child)>.detail dl:empty,.tag-list:empty,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:empty,section[data-route="dc.show.license"] .validity dl:empty,td.tags:empty{display:none}.consul-bucket-list>*>*,.consul-exposed-path-list>ul>li>.detail dl>*>*,.consul-instance-checks>*>*,.consul-lock-session-list dl>*>*,.consul-lock-session-list ul>li:not(:first-child)>.detail dl>*>*,.consul-upstream-instance-list dl>*>*,.consul-upstream-instance-list li>.detail dl>*>*,.list-collection>ul>li:not(:first-child)>.detail dl>*>*,.tag-list>*>*,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl>*>*,section[data-route="dc.show.license"] .validity dl>*>*,td.tags>*>*{display:inline-block}.consul-bucket-list>*,.consul-exposed-path-list>ul>li>.detail dl>*,.consul-instance-checks>*,.consul-lock-session-list dl>*,.consul-lock-session-list ul>li:not(:first-child)>.detail dl>*,.consul-upstream-instance-list dl>*,.consul-upstream-instance-list li>.detail dl>*,.list-collection>ul>li:not(:first-child)>.detail dl>*,.tag-list>*,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl>*,section[data-route="dc.show.license"] .validity dl>*,td.tags>*{white-space:nowrap}.consul-bucket-list>dd,.consul-exposed-path-list>ul>li>.detail dl>dd,.consul-instance-checks>dd,.consul-lock-session-list dl>dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl>dd,.consul-upstream-instance-list dl>dd,.consul-upstream-instance-list li>.detail dl>dd,.list-collection>ul>li:not(:first-child)>.detail dl>dd,.tag-list>dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl>dd,section[data-route="dc.show.license"] .validity dl>dd,td.tags>dd{flex-wrap:wrap}.consul-upstream-instance-list dl.local-bind-socket-mode dt{display:inline-flex;min-width:18px;overflow:hidden}.consul-lock-session-list .checks dd,.discovery-chain .resolver-card ol,.filter-bar,.filter-bar>div,.modal-dialog,.tag-list dd,td.tags dd{display:flex}.consul-bucket-list .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-bucket-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-bucket-list .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.consul-bucket-list .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.consul-bucket-list .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-bucket-list .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-bucket-list .tag-list:not([class]) dd+dt:not([class])+dd,.consul-bucket-list dd+dt,.consul-bucket-list td.tags:not([class]) dd+dt:not([class])+dd,.consul-bucket-list+.consul-bucket-list:not(:first-of-type),.consul-bucket-list+.consul-instance-checks:not(:first-of-type),.consul-bucket-list+.tag-list:not(:first-of-type),.consul-bucket-list+td.tags:not(:first-of-type),.consul-bucket-list:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,.consul-bucket-list:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-bucket-list:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,.consul-bucket-list:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-bucket-list:not([class]) .tag-list dd+dt:not([class])+dd,.consul-bucket-list:not([class]) dd+dt:not([class])+dd,.consul-bucket-list:not([class]) td.tags dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail .consul-bucket-list+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail .consul-instance-checks+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail .consul-lock-session-list dl+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail .consul-upstream-instance-list dl+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.consul-exposed-path-list>ul>li>.detail .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.consul-exposed-path-list>ul>li>.detail .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail .tag-list+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail dl .consul-bucket-list:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl .consul-instance-checks:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl .consul-lock-session-list dl:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl .consul-upstream-instance-list dl:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl .tag-list:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl dd+dt,.consul-exposed-path-list>ul>li>.detail dl section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl td.tags:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl+.consul-bucket-list:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail dl+.consul-instance-checks:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail dl+.tag-list:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail dl+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail dl+td.tags:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail dl:not([class]) .consul-bucket-list dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) .consul-instance-checks dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) .consul-lock-session-list dl dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) .consul-upstream-instance-list dl dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) .tag-list dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) section[data-route="dc.show.license"] .validity dl dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) td.tags dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail td.tags+dl:not(:first-of-type),.consul-instance-checks .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-instance-checks .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-instance-checks .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.consul-instance-checks .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.consul-instance-checks .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-instance-checks .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-instance-checks .tag-list:not([class]) dd+dt:not([class])+dd,.consul-instance-checks dd+dt,.consul-instance-checks td.tags:not([class]) dd+dt:not([class])+dd,.consul-instance-checks+.consul-bucket-list:not(:first-of-type),.consul-instance-checks+.consul-instance-checks:not(:first-of-type),.consul-instance-checks+.tag-list:not(:first-of-type),.consul-instance-checks+td.tags:not(:first-of-type),.consul-instance-checks:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,.consul-instance-checks:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-instance-checks:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,.consul-instance-checks:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-instance-checks:not([class]) .tag-list dd+dt:not([class])+dd,.consul-instance-checks:not([class]) dd+dt:not([class])+dd,.consul-instance-checks:not([class]) td.tags dd+dt:not([class])+dd,.consul-lock-session-list .consul-bucket-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-bucket-list+dl:not(:first-of-type),.consul-lock-session-list .consul-bucket-list:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list .consul-exposed-path-list>ul>li>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-exposed-path-list>ul>li>.detail dl+dl:not(:first-of-type),.consul-lock-session-list .consul-exposed-path-list>ul>li>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list .consul-instance-checks ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-instance-checks+dl:not(:first-of-type),.consul-lock-session-list .consul-instance-checks:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list dl li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list dl ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list dl+dl:not(:first-of-type),.consul-lock-session-list .consul-upstream-instance-list dl.local-bind-address dl dd+dt+dd,.consul-lock-session-list .consul-upstream-instance-list dl.local-bind-socket-path dl dd+dt+dd,.consul-lock-session-list .consul-upstream-instance-list dl:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list dl:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list li>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list li>.detail dl+dl:not(:first-of-type),.consul-lock-session-list .consul-upstream-instance-list li>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),.consul-lock-session-list .consul-upstream-instance-list ul>li:not(:first-child)>.detail dl.local-bind-address dd+dt+dd,.consul-lock-session-list .consul-upstream-instance-list ul>li:not(:first-child)>.detail dl.local-bind-socket-path dd+dt+dd,.consul-lock-session-list .consul-upstream-instance-list ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list .list-collection>ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .list-collection>ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),.consul-lock-session-list .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list .list-collection>ul>li:not(:first-child)>.detail ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .tag-list dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .tag-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .tag-list+dl:not(:first-of-type),.consul-lock-session-list .tag-list:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list .tag-list:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list dl .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.consul-lock-session-list dl .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.consul-lock-session-list dl .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl .tag-list:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl dd+dt,.consul-lock-session-list dl td.tags:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl+.consul-bucket-list:not(:first-of-type),.consul-lock-session-list dl+.consul-instance-checks:not(:first-of-type),.consul-lock-session-list dl+.tag-list:not(:first-of-type),.consul-lock-session-list dl+dl:not(:first-of-type),.consul-lock-session-list dl+td.tags:not(:first-of-type),.consul-lock-session-list dl:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) .tag-list dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) td.tags dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),.consul-lock-session-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list section[data-route="dc.show.license"] .validity dl ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),.consul-lock-session-list section[data-route="dc.show.license"] .validity dl:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list td.tags dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list td.tags ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list td.tags+dl:not(:first-of-type),.consul-lock-session-list td.tags:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list td.tags:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail .consul-bucket-list+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail .consul-instance-checks+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail .consul-upstream-instance-list dl+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail .tag-list+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail dl .consul-bucket-list:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl .consul-instance-checks:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl .consul-upstream-instance-list dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl .tag-list:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt,.consul-lock-session-list ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl td.tags:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl+.consul-bucket-list:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail dl+.consul-instance-checks:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail dl+.tag-list:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail dl+td.tags:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) .consul-bucket-list dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) .consul-instance-checks dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) .consul-upstream-instance-list dl dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) .tag-list dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) section[data-route="dc.show.license"] .validity dl dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) td.tags dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail td.tags+dl:not(:first-of-type),.consul-upstream-instance-list .consul-bucket-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-bucket-list+dl:not(:first-of-type),.consul-upstream-instance-list .consul-bucket-list:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-exposed-path-list>ul>li>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-exposed-path-list>ul>li>.detail dl+dl:not(:first-of-type),.consul-upstream-instance-list .consul-exposed-path-list>ul>li>.detail dl.local-bind-address dd+dt+dd,.consul-upstream-instance-list .consul-exposed-path-list>ul>li>.detail dl.local-bind-socket-path dd+dt+dd,.consul-upstream-instance-list .consul-exposed-path-list>ul>li>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-instance-checks li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-instance-checks+dl:not(:first-of-type),.consul-upstream-instance-list .consul-instance-checks:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-lock-session-list dl li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-lock-session-list dl+dl:not(:first-of-type),.consul-upstream-instance-list .consul-lock-session-list dl:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),.consul-upstream-instance-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl.local-bind-address dd+dt+dd,.consul-upstream-instance-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl.local-bind-socket-path dd+dt+dd,.consul-upstream-instance-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-upstream-instance-list .list-collection>ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .list-collection>ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),.consul-upstream-instance-list .list-collection>ul>li:not(:first-child)>.detail dl.local-bind-address dd+dt+dd,.consul-upstream-instance-list .list-collection>ul>li:not(:first-child)>.detail dl.local-bind-socket-path dd+dt+dd,.consul-upstream-instance-list .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-upstream-instance-list .list-collection>ul>li:not(:first-child)>.detail li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .tag-list dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .tag-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .tag-list+dl:not(:first-of-type),.consul-upstream-instance-list .tag-list:not([class]) dl dd+dt:not([class])+dd,.consul-upstream-instance-list .tag-list:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list dl .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl .tag-list:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl dd+dt,.consul-upstream-instance-list dl li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl td.tags:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl+.consul-bucket-list:not(:first-of-type),.consul-upstream-instance-list dl+.consul-instance-checks:not(:first-of-type),.consul-upstream-instance-list dl+.tag-list:not(:first-of-type),.consul-upstream-instance-list dl+dl:not(:first-of-type),.consul-upstream-instance-list dl+td.tags:not(:first-of-type),.consul-upstream-instance-list dl.local-bind-address .consul-bucket-list dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address .consul-instance-checks dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address .consul-lock-session-list dl dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address .tag-list dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address section[data-route="dc.show.license"] .validity dl dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address td.tags dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path .consul-bucket-list dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path .consul-instance-checks dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path .consul-lock-session-list dl dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path .tag-list dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path section[data-route="dc.show.license"] .validity dl dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path td.tags dd+dt+dd,.consul-upstream-instance-list dl:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) .tag-list dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) td.tags dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail .consul-bucket-list+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail .consul-instance-checks+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail .consul-lock-session-list dl+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail .tag-list+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail dl .consul-bucket-list:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl .consul-instance-checks:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl .consul-lock-session-list dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl .tag-list:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl dd+dt,.consul-upstream-instance-list li>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl td.tags:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl+.consul-bucket-list:not(:first-of-type),.consul-upstream-instance-list li>.detail dl+.consul-instance-checks:not(:first-of-type),.consul-upstream-instance-list li>.detail dl+.tag-list:not(:first-of-type),.consul-upstream-instance-list li>.detail dl+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail dl+td.tags:not(:first-of-type),.consul-upstream-instance-list li>.detail dl.local-bind-address dd+dt+dd,.consul-upstream-instance-list li>.detail dl.local-bind-socket-path dd+dt+dd,.consul-upstream-instance-list li>.detail dl:not([class]) .consul-bucket-list dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) .consul-instance-checks dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) .consul-lock-session-list dl dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) .tag-list dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) section[data-route="dc.show.license"] .validity dl dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) td.tags dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail td.tags+dl:not(:first-of-type),.consul-upstream-instance-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),.consul-upstream-instance-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list section[data-route="dc.show.license"] .validity dl li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),.consul-upstream-instance-list section[data-route="dc.show.license"] .validity dl:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list td.tags dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list td.tags li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list td.tags+dl:not(:first-of-type),.consul-upstream-instance-list td.tags:not([class]) dl dd+dt:not([class])+dd,.consul-upstream-instance-list td.tags:not([class]) li>.detail dl dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail .consul-bucket-list+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail .consul-instance-checks+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail .consul-lock-session-list dl+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail .consul-upstream-instance-list dl+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.list-collection>ul>li:not(:first-child)>.detail .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.list-collection>ul>li:not(:first-child)>.detail .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail .tag-list+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail dl .consul-bucket-list:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl .consul-instance-checks:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl .consul-lock-session-list dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl .consul-upstream-instance-list dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl .tag-list:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl dd+dt,.list-collection>ul>li:not(:first-child)>.detail dl section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl td.tags:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl+.consul-bucket-list:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail dl+.consul-instance-checks:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail dl+.tag-list:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail dl+td.tags:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) .consul-bucket-list dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) .consul-instance-checks dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) .consul-lock-session-list dl dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) .consul-upstream-instance-list dl dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) .tag-list dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) section[data-route="dc.show.license"] .validity dl dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) td.tags dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail td.tags+dl:not(:first-of-type),.tag-list .consul-bucket-list:not([class]) dd+dt:not([class])+dd,.tag-list .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.tag-list .consul-instance-checks:not([class]) dd+dt:not([class])+dd,.tag-list .consul-lock-session-list dl:not([class]) dd+dt:not([class])+dd,.tag-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.tag-list .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.tag-list .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.tag-list .consul-upstream-instance-list dl:not([class]) dd+dt:not([class])+dd,.tag-list .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.tag-list .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.tag-list dd+dt,.tag-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,.tag-list section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,.tag-list td.tags:not([class]) dd+dt:not([class])+dd,.tag-list+.consul-bucket-list:not(:first-of-type),.tag-list+.consul-instance-checks:not(:first-of-type),.tag-list+.tag-list:not(:first-of-type),.tag-list+td.tags:not(:first-of-type),.tag-list:not([class]) .consul-bucket-list dd+dt:not([class])+dd,.tag-list:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,.tag-list:not([class]) .consul-instance-checks dd+dt:not([class])+dd,.tag-list:not([class]) .consul-lock-session-list dl dd+dt:not([class])+dd,.tag-list:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.tag-list:not([class]) .consul-upstream-instance-list dl dd+dt:not([class])+dd,.tag-list:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,.tag-list:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.tag-list:not([class]) dd+dt:not([class])+dd,.tag-list:not([class]) section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt:not([class])+dd,.tag-list:not([class]) section[data-route="dc.show.license"] .validity dl dd+dt:not([class])+dd,.tag-list:not([class]) td.tags dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-bucket-list+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-exposed-path-list>ul>li>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-exposed-path-list>ul>li>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-exposed-path-list>ul>li>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-instance-checks+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-lock-session-list dl ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-lock-session-list dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-lock-session-list dl:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-lock-session-list ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-lock-session-list ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list dl li>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list dl.local-bind-address dl dd+dt+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list dl.local-bind-socket-path dl dd+dt+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list dl:not([class]) li>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list li>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list li>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list li>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .list-collection>ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .list-collection>ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .tag-list dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .tag-list+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .tag-list:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .tag-list:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl td.tags:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+.consul-bucket-list:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+.consul-instance-checks:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+.tag-list:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+td.tags:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) .tag-list dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) td.tags dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header td.tags dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header td.tags+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header td.tags:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section[data-route="dc.show.license"] .validity header dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section[data-route="dc.show.license"] header .validity dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-bucket-list+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-exposed-path-list>ul>li>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-exposed-path-list>ul>li>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-exposed-path-list>ul>li>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-instance-checks+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-lock-session-list dl ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-lock-session-list dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-lock-session-list dl:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-lock-session-list ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-lock-session-list ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-upstream-instance-list dl li>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-upstream-instance-list dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-upstream-instance-list dl.local-bind-address dl dd+dt+dd,section[data-route="dc.show.license"] .validity .consul-upstream-instance-list dl.local-bind-socket-path dl dd+dt+dd,section[data-route="dc.show.license"] .validity .consul-upstream-instance-list dl:not([class]) li>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-upstream-instance-list li>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-upstream-instance-list li>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-upstream-instance-list li>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .list-collection>ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .list-collection>ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .tag-list dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .tag-list+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .tag-list:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,section[data-route="dc.show.license"] .validity dl .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,section[data-route="dc.show.license"] .validity dl .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl .tag-list:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl dd+dt,section[data-route="dc.show.license"] .validity dl td.tags:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl+.consul-bucket-list:not(:first-of-type),section[data-route="dc.show.license"] .validity dl+.consul-instance-checks:not(:first-of-type),section[data-route="dc.show.license"] .validity dl+.tag-list:not(:first-of-type),section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity dl+td.tags:not(:first-of-type),section[data-route="dc.show.license"] .validity dl:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) .tag-list dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) td.tags dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity td.tags dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity td.tags+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity td.tags:not([class]) dl dd+dt:not([class])+dd,td.tags .consul-bucket-list:not([class]) dd+dt:not([class])+dd,td.tags .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,td.tags .consul-instance-checks:not([class]) dd+dt:not([class])+dd,td.tags .consul-lock-session-list dl:not([class]) dd+dt:not([class])+dd,td.tags .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,td.tags .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,td.tags .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,td.tags .consul-upstream-instance-list dl:not([class]) dd+dt:not([class])+dd,td.tags .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,td.tags .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,td.tags .tag-list:not([class]) dd+dt:not([class])+dd,td.tags dd+dt,td.tags section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,td.tags section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,td.tags+.consul-bucket-list:not(:first-of-type),td.tags+.consul-instance-checks:not(:first-of-type),td.tags+.tag-list:not(:first-of-type),td.tags+td.tags:not(:first-of-type),td.tags:not([class]) .consul-bucket-list dd+dt:not([class])+dd,td.tags:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,td.tags:not([class]) .consul-instance-checks dd+dt:not([class])+dd,td.tags:not([class]) .consul-lock-session-list dl dd+dt:not([class])+dd,td.tags:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,td.tags:not([class]) .consul-upstream-instance-list dl dd+dt:not([class])+dd,td.tags:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,td.tags:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,td.tags:not([class]) .tag-list dd+dt:not([class])+dd,td.tags:not([class]) dd+dt:not([class])+dd,td.tags:not([class]) section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt:not([class])+dd,td.tags:not([class]) section[data-route="dc.show.license"] .validity dl dd+dt:not([class])+dd{margin-left:var(--horizontal-kv-list-separator-width)}.consul-bucket-list dt+dd,.consul-exposed-path-list>ul>li>.detail dl dt+dd,.consul-instance-checks dt+dd,.consul-lock-session-list dl dt+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl dt+dd,.consul-upstream-instance-list dl dt+dd,.consul-upstream-instance-list li>.detail dl dt+dd,.list-collection>ul>li:not(:first-child)>.detail dl dt+dd,.tag-list dt+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dt+dd,section[data-route="dc.show.license"] .validity dl dt+dd,td.tags dt+dd{margin-left:4px}.consul-bucket-list:not([class]) dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) dt:not([class])+dd,.consul-instance-checks:not([class]) dt:not([class])+dd,.consul-lock-session-list dl:not([class]) dt:not([class])+dd,.consul-upstream-instance-list dl.local-bind-address dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path dt+dd,.consul-upstream-instance-list dl:not([class]) dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dt:not([class])+dd,.tag-list:not([class]) dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) dt:not([class])+dd,td.tags:not([class]) dt:not([class])+dd{margin-left:0!important}.consul-lock-session-list .checks dd>:not(:last-child)::after,.discovery-chain .resolver-card ol>:not(:last-child)::after,.tag-list dd>:not(:last-child)::after,td.tags dd>:not(:last-child)::after{display:inline;content:var(--csv-list-separator);vertical-align:initial;margin-right:.3em}.freetext-filter_label::after,.tippy-box .tippy-arrow::before{content:"";position:absolute}.tag-list dt::before,td.tags dt::before{color:inherit;color:var(--token-color-foreground-faint)}.consul-exposed-path-list>ul>li>.detail dl>dt>*,.consul-lock-session-list ul>li:not(:first-child)>.detail dl>dt>*,.consul-upstream-instance-list li>.detail dl>dt>*,.list-collection>ul>li:not(:first-child)>.detail dl>dt>*{display:none}.consul-exposed-path-list>ul>li>.detail dl.passing dt::before,.consul-exposed-path-list>ul>li>.header .passing dd::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.passing dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .passing dd::before,.consul-upstream-instance-list li>.detail dl.passing dt::before,.consul-upstream-instance-list li>.header .passing dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.passing dt::before,.list-collection>ul>li:not(:first-child)>.header .passing dd::before{color:var(--token-color-foreground-success)}.consul-exposed-path-list>ul>li>.detail dl.warning dt::before,.consul-exposed-path-list>ul>li>.header .warning dd::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.warning dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .warning dd::before,.consul-upstream-instance-list li>.detail dl.warning dt::before,.consul-upstream-instance-list li>.header .warning dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.warning dt::before,.list-collection>ul>li:not(:first-child)>.header .warning dd::before{color:var(--token-color-foreground-warning)}.consul-exposed-path-list>ul>li>.detail dl.critical dt::before,.consul-exposed-path-list>ul>li>.header .critical dd::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.critical dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .critical dd::before,.consul-upstream-instance-list li>.detail dl.critical dt::before,.consul-upstream-instance-list li>.header .critical dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.critical dt::before,.list-collection>ul>li:not(:first-child)>.header .critical dd::before{color:var(--token-color-foreground-critical)}.consul-exposed-path-list>ul>li>.detail dl.empty dt::before,.consul-exposed-path-list>ul>li>.detail dl.unknown dt::before,.consul-exposed-path-list>ul>li>.header .empty dd::before,.consul-exposed-path-list>ul>li>.header .unknown dd::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.empty dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.unknown dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .empty dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header .unknown dd::before,.consul-upstream-instance-list li>.detail dl.empty dt::before,.consul-upstream-instance-list li>.detail dl.unknown dt::before,.consul-upstream-instance-list li>.header .empty dd::before,.consul-upstream-instance-list li>.header .unknown dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.empty dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.unknown dt::before,.list-collection>ul>li:not(:first-child)>.header .empty dd::before,.list-collection>ul>li:not(:first-child)>.header .unknown dd::before{color:var(--token-color-foreground-faint)}.consul-exposed-path-list>ul>li>.header [rel=me] dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header [rel=me] dd::before,.consul-upstream-instance-list li>.header [rel=me] dd::before,.list-collection>ul>li:not(:first-child)>.header [rel=me] dd::before{color:var(--token-color-foreground-action)}.app-view>div form:not(.filter-bar) [role=radiogroup] label>em>code,.modal-dialog [role=document] .type-password>em>code,.modal-dialog [role=document] .type-select>em>code,.modal-dialog [role=document] .type-text>em>code,.modal-dialog [role=document] [role=radiogroup] label>em>code,.modal-dialog [role=document] form button+em>code,.modal-dialog [role=document] p code,.oidc-select label>em>code,.type-toggle>em>code,main .type-password>em>code,main .type-select>em>code,main .type-text>em>code,main form button+em>code,main p code{border:1px solid;color:var(--token-color-consul-brand);background-color:var(--token-color-surface-strong);border-color:var(--token-color-surface-interactive-active);display:inline-block;padding:0 4px}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{outline:0;background-color:var(--token-color-surface-primary);border-radius:var(--decor-radius-100)}[data-animation=fade][data-state=hidden].tippy-box{opacity:0}[data-inertia][data-state=visible].tippy-box{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-box .tippy-arrow{--size:5px}[data-placement^=top].tippy-box>.tippy-arrow{bottom:0}[data-placement^=top].tippy-box>.tippy-arrow::before{left:0;bottom:calc(0px - var(--size));transform-origin:center top}[data-placement^=bottom].tippy-box>.tippy-arrow{top:0}[data-placement^=bottom].tippy-box>.tippy-arrow::before{left:0;top:calc(0px - var(--size));transform-origin:center bottom}[data-placement^=left].tippy-box>.tippy-arrow{right:0}[data-placement^=left].tippy-box>.tippy-arrow::before{right:calc(0px - var(--size));transform-origin:center left}[data-placement^=right].tippy-box>.tippy-arrow{left:0}[data-placement^=right].tippy-box>.tippy-arrow::before{left:calc(0px - var(--size));transform-origin:center right}[data-theme~=square-tail] .tippy-arrow{--size:18px;left:calc(0px - var(--size)/ 2)!important}[data-theme~=square-tail] .tippy-arrow::before{background-color:var(--token-color-surface-primary);width:calc(1px + var(--size));height:calc(1px + var(--size));border:var(--decor-border-100);border-color:var(--token-color-palette-neutral-300)}[data-theme~=square-tail] .tippy-arrow::after{position:absolute;left:1px}[data-theme~=square-tail][data-placement^=top]{bottom:-10px}[data-theme~=square-tail][data-placement^=top] .informed-action{border-bottom-left-radius:0!important}[data-theme~=square-tail][data-placement^=top] .tippy-arrow::before{border-bottom-left-radius:var(--decor-radius-200);border-bottom-right-radius:var(--decor-radius-200);border-top:0!important}[data-theme~=square-tail][data-placement^=top] .tippy-arrow::after{bottom:calc(0px - var(--size))}[data-theme~=square-tail][data-placement^=bottom]{top:-10px}[data-theme~=square-tail][data-placement^=bottom] .informed-action{border-top-left-radius:0!important}[data-theme~=square-tail][data-placement^=bottom] .tippy-arrow::before{border-top-left-radius:var(--decor-radius-200);border-top-right-radius:var(--decor-radius-200);border-bottom:0!important}[data-theme~=square-tail][data-placement^=bottom] .tippy-arrow::after{top:calc(0px - var(--size))}.tippy-box[data-theme~=tooltip] .tippy-content{padding:12px;max-width:224px;position:relative;z-index:1}.tippy-box[data-theme~=tooltip]{background-color:var(--token-color-foreground-faint)}.tippy-box[data-theme~=tooltip] .tippy-arrow{--size:5px;color:var(--token-color-foreground-faint);width:calc(var(--size) * 2);height:calc(var(--size) * 2)}.tippy-box[data-theme~=tooltip] .tippy-arrow::before{border-color:transparent;border-style:solid}.tippy-box[data-theme~=tooltip][data-placement^=top]>.tippy-arrow::before{border-width:var(--size) var(--size) 0;border-top-color:initial}.tippy-box[data-theme~=tooltip][data-placement^=bottom]>.tippy-arrow::before{border-width:0 var(--size) var(--size);border-bottom-color:initial}.tippy-box[data-theme~=tooltip][data-placement^=left]>.tippy-arrow::before{border-width:var(--size) 0 var(--size) var(--size);border-left-color:initial}.tippy-box[data-theme~=tooltip][data-placement^=right]>.tippy-arrow::before{border-width:var(--size) var(--size) var(--size) 0;border-right-color:initial}.warning.modal-dialog header{background-color:var(--token-color-vault-gradient-faint-start);border-color:var(--token-color-vault-brand);color:var(--token-color-vault-foreground)}.warning.modal-dialog header::before{color:var(--token-color-vault-brand);float:left;margin-top:2px;margin-right:3px}.modal-dialog>div:first-child{background-color:var(--token-color-surface-interactive);opacity:.9}.modal-dialog [role=document]>footer,.modal-dialog [role=document]>header,.modal-dialog-body{border-color:var(--token-color-palette-neutral-300)}.modal-dialog-body{border-style:solid;border-left-width:1px;border-right-width:1px}.modal-layer{height:0}.modal-dialog [role=document] table{height:150px!important}.modal-dialog [role=document] tbody{max-height:100px}.modal-dialog table{min-height:149px}.modal-dialog,.modal-dialog>div:first-child{position:fixed;top:0;right:0;bottom:0;left:0}.modal-dialog{z-index:500;align-items:center;justify-content:center;height:100%}[aria-hidden=true].modal-dialog{display:none}.modal-dialog [role=document]{background-color:var(--token-color-surface-primary);margin:auto;z-index:2;max-width:855px;position:relative}.modal-dialog [role=document]>*{padding-left:15px;padding-right:15px}.modal-dialog [role=document]>div{overflow-y:auto;max-height:80vh;padding:20px 23px}.modal-dialog [role=document]>footer,.modal-dialog [role=document]>header{border-width:1px;padding-top:12px;padding-bottom:10px}.modal-dialog [role=document]>header{position:relative}.modal-dialog [role=document]>header button{float:right;margin-top:-3px}.list-collection>ul{border-top:1px solid;border-color:var(--token-color-surface-interactive-active)}.list-collection>button{cursor:pointer;background-color:var(--token-color-surface-strong);color:var(--token-color-foreground-action);width:100%;padding:15px}.list-collection-scroll-virtual,.list-collection>ul>li{position:relative}.list-collection-scroll-virtual{height:500px}.filter-bar{background-color:var(--token-color-foreground-high-contrast);border-bottom:var(--decor-border-100);border-color:var(--token-color-surface-interactive-active);padding:4px 8px}.filter-bar .filters .popover-menu>[type=checkbox]:checked+label button,.filter-bar .sort .popover-menu>[type=checkbox]:checked+label button{color:var(--token-color-foreground-action);background-color:var(--token-color-foreground-high-contrast)}.filter-bar .sort{margin-left:auto}.filter-bar .popover-select{position:relative;z-index:3}.filter-bar .popover-menu>[type=checkbox]+label button{padding-left:1.5rem!important;padding-right:1.5rem!important}.filter-bar .popover-menu [role=menuitem]{justify-content:normal!important}@media (max-width:1379px){.filter-bar,.filter-bar>div{flex-wrap:wrap}.filter-bar .search{position:relative;z-index:4;width:100%;margin-bottom:.3rem}}@media (max-width:995px){.filter-bar .filters,.filter-bar .sort{display:none}}html[data-route^="dc.acls.index"] .filter-bar{color:inherit}.freetext-filter{border:var(--decor-border-100);border-radius:var(--decor-radius-100);background-color:var(--token-color-surface-primary);border-color:var(--token-color-surface-interactive-active);color:var(--token-color-foreground-disabled)}.freetext-filter:hover,.freetext-filter:hover *{border-color:var(--token-color-foreground-disabled)}.freetext-filter_input::-moz-placeholder{cursor:inherit;color:inherit;border-color:inherit}.freetext-filter *,.freetext-filter_input::placeholder{cursor:inherit;color:inherit;border-color:inherit}.freetext-filter_input{-webkit-appearance:none;border:none}.freetext-filter_label::after{visibility:visible;--icon-name:icon-search;top:50%;left:50%;width:16px;height:16px;margin-left:-8px;margin-top:-8px}.freetext-filter .popover-menu{background-color:var(--token-color-surface-strong);color:var(--token-color-foreground-primary);border-left:1px solid;border-color:inherit}.freetext-filter .popover-menu>[type=checkbox]:checked+label button{background-color:var(--token-color-surface-interactive-active)}.freetext-filter{--height:2.2rem;display:flex;position:relative;height:var(--height);width:100%}.freetext-filter_input,.freetext-filter_label{height:100%}.freetext-filter_input{padding:8px 10px;padding-left:var(--height);min-width:12.7rem;width:100%}.freetext-filter_label{visibility:hidden;position:absolute;z-index:1;width:var(--height)}.informed-action{border-radius:var(--decor-radius-200);border:var(--decor-border-100);border-color:var(--token-color-palette-neutral-300);background-color:var(--token-color-surface-primary);min-width:190px}.informed-action>div{border-top-left-radius:var(--decor-radius-200);border-top-right-radius:var(--decor-radius-200);cursor:default;padding:1rem}.informed-action p{color:var(--token-color-hashicorp-brand)}.informed-action>ul>li>:focus,.informed-action>ul>li>:hover{background-color:var(--token-color-surface-strong)}.info.informed-action header{color:var(--token-color-foreground-action-active)}.info.informed-action header::before{background-color:var(--token-color-foreground-action);margin-right:5px}.info.informed-action>div{background-color:var(--token-color-surface-action)}.dangerous.informed-action header{color:var(--token-color-palette-red-400)}.dangerous.informed-action header::before{background-color:var(--token-color-foreground-critical)}.dangerous.informed-action>div{background-color:var(--token-color-surface-critical)}.warning.informed-action header{color:var(--token-color-foreground-warning-on-surface)}.warning.informed-action header::before{background-color:var(--token-color-vault-brand);margin-right:5px}.warning.informed-action>div{background-color:var(--token-color-vault-gradient-faint-start)}.copyable-code::after,.tab-nav li:not(.selected)>:active,.tab-nav li:not(.selected)>:focus,.tab-nav li:not(.selected)>:hover{background-color:var(--token-color-surface-strong)}.informed-action>ul>.action>*{color:var(--token-color-foreground-action)}.documentation.informed-action{min-width:270px}.informed-action header::before{float:left;margin-right:5px}.informed-action>ul{list-style:none;display:flex;margin:0;padding:4px}.informed-action>ul>li{width:50%}.informed-action>ul>li>*{width:100%}.tab-nav ul{list-style-type:none;display:inline-flex;align-items:center;position:relative;padding:0;margin:0}.tab-nav li>:not(:disabled){cursor:pointer}.tab-nav{border-bottom:var(--decor-border-100)}.animatable.tab-nav ul::after,.tab-nav li>*{border-bottom:var(--decor-border-300)}.tab-nav{border-color:var(--token-color-surface-interactive-active);clear:both;overflow:auto}.tab-nav li>*{white-space:nowrap;text-decoration:none;transition-property:background-color,border-color;border-color:transparent;color:var(--token-color-foreground-faint);display:inline-block;padding:16px 13px}.tab-nav li:not(.selected)>:focus,.tab-nav li:not(.selected)>:hover{border-color:var(--token-color-palette-neutral-300)}.animatable.tab-nav .selected a{border-color:transparent!important}.animatable.tab-nav ul::after{position:absolute;bottom:0;height:0;border-top:0;width:calc(var(--selected-width,0) * 1px);transform:translate(calc(var(--selected-left,0) * 1px),0);transition-property:transform,width}.search-bar-status{border-bottom:var(--decor-border-100);border-bottom-color:var(--token-color-surface-interactive-active);padding:.5rem 0 .5rem .5rem}.search-bar-status li:not(.remove-all) button::before{color:var(--token-color-foreground-faint);margin-top:1px;margin-right:.2rem}.search-bar-status dt::after{content:":";padding-right:.3rem}.search-bar-status>dl>dt{float:left}.search-bar-status dt{white-space:nowrap}.search-bar-status li{display:inline-flex}.search-bar-status li:not(:last-child){margin-right:.3rem;margin-bottom:.3rem}.search-bar-status li:not(.remove-all){border:var(--decor-border-100);border-color:var(--token-color-surface-interactive-active);color:var(--token-color-foreground-faint);padding:0 .2rem}.search-bar-status li:not(.remove-all) dl{display:flex}.search-bar-status li:not(.remove-all) button{cursor:pointer;padding:0}.copyable-code{display:flex;align-items:flex-start;position:relative;width:100%;padding:8px 14px 3px;border:var(--decor-border-100);border-color:var(--token-color-surface-interactive-active);border-radius:var(--decor-radius-200)}.copyable-code.obfuscated{padding-left:4px}.copyable-code::after{position:absolute;top:0;right:0;width:40px;height:100%;display:block;content:""}.copyable-code .copy-button{position:absolute;top:0;right:0;z-index:1}.copyable-code .copy-button button{width:40px;height:40px}.copyable-code .copy-button button:empty::after{display:none}.copyable-code button[aria-expanded]{margin-top:1px;margin-right:4px;cursor:pointer}.copyable-code button[aria-expanded]::before{content:"";--icon-size:icon-000;--icon-color:var(--token-color-foreground-faint)}.copyable-code button[aria-expanded=true]::before{--icon-name:icon-eye-off}.copyable-code button[aria-expanded=false]::before{--icon-name:icon-eye}.copyable-code pre{padding-right:30px}.copyable-code code{display:inline-block;overflow:hidden;text-overflow:ellipsis;width:100%}.copyable-code hr{width:calc(100% - 80px);margin:8px 0 13px;border:3px dashed var(--token-color-palette-neutral-300);background-color:var(--token-color-surface-primary)}.consul-loader circle{fill:var(--token-color-consul-gradient-faint-stop);animation:loader-animation 1.5s infinite ease-in-out;transform-origin:50% 50%}.consul-loader g:nth-last-child(2) circle{animation-delay:.2s}.consul-loader g:nth-last-child(3) circle{animation-delay:.3s}.consul-loader g:nth-last-child(4) circle{animation-delay:.4s}.consul-loader g:nth-last-child(5) circle{animation-delay:.5s}@keyframes loader-animation{0%,100%{transform:scale3D(1,1,1)}33%{transform:scale3D(0,0,1)}}.consul-loader{display:flex;align-items:center;justify-content:center;height:100%;position:absolute;width:100%;top:0;margin-top:0!important}.tomography-graph .background{fill:var(--token-color-surface-strong)}.tomography-graph .axis{fill:none;stroke:var(--token-color-palette-neutral-300);stroke-dasharray:4 4}.tomography-graph .border{fill:none;stroke:var(--token-color-palette-neutral-300)}.tomography-graph .point{stroke:var(--token-color-foreground-disabled);fill:var(--token-color-consul-foreground)}.tomography-graph .lines rect{fill:var(--token-color-consul-foreground);stroke:transparent;stroke-width:5px}.tomography-graph .lines rect:hover{fill:var(--token-color-palette-neutral-300);height:3px;y:-1px}.tomography-graph .tick line{stroke:var(--token-color-palette-neutral-300)}.tomography-graph .tick text{text-anchor:start}.discovery-chain .resolver-card,.discovery-chain .route-card,.discovery-chain .splitter-card,.discovery-chain path{transition-duration:.1s;transition-timing-function:linear;cursor:pointer}.discovery-chain path{transition-property:stroke;fill:none;stroke:var(--token-color-foreground-disabled);stroke-width:2;vector-effect:non-scaling-stroke}#downstream-lines svg circle,#upstream-lines svg circle,.discovery-chain circle{fill:var(--token-color-surface-primary)}.discovery-chain .resolver-card,.discovery-chain .resolver-card a,.discovery-chain .route-card,.discovery-chain .route-card a,.discovery-chain .splitter-card,.discovery-chain .splitter-card a{color:var(--token-color-foreground-strong)!important}.discovery-chain path:focus,.discovery-chain path:hover{stroke:var(--token-color-foreground-strong)}.discovery-chain .resolvers,.discovery-chain .routes,.discovery-chain .splitters{border-radius:var(--decor-radius-100);border:1px solid;border-color:var(--token-color-surface-interactive-active);background-color:var(--token-color-surface-strong);pointer-events:none}.discovery-chain .resolver-card,.discovery-chain .resolvers>header span,.discovery-chain .route-card,.discovery-chain .routes>header span,.discovery-chain .splitter-card,.discovery-chain .splitters>header span{pointer-events:all}.discovery-chain .resolvers>header>*,.discovery-chain .routes>header>*,.discovery-chain .splitters>header>*{text-transform:uppercase}.discovery-chain .resolvers>header span::after,.discovery-chain .routes>header span::after,.discovery-chain .splitters>header span::after{width:1.2em;height:1.2em;opacity:.6}.discovery-chain .resolver-card,.discovery-chain .route-card,.discovery-chain .splitter-card{transition-property:opacity background-color border-color;margin-top:0!important}.discovery-chain [id*=":"]:not(path):hover{opacity:1;background-color:var(--token-color-surface-primary);border-color:var(--token-color-foreground-faint)}.discovery-chain .route-card header:not(.short) dd{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.discovery-chain .route-card section header>*{visibility:hidden}.discovery-chain .route-card .match-headers header ::before{content:"H"}.discovery-chain .route-card .match-queryparams header>::before{content:"Q"}.discovery-chain .resolver-card dt::before{content:"";--icon-size:icon-999}.discovery-chain .resolver-card dl.failover dt::before{--icon-name:icon-cloud-cross}.discovery-chain .resolver-card dl.redirect dt::before{--icon-name:icon-redirect}.discovery-chain circle{stroke-width:2;stroke:var(--token-color-foreground-disabled)}.discovery-chain{position:relative;display:flex;justify-content:space-between}.discovery-chain svg{position:absolute}.discovery-chain .resolvers,.discovery-chain .routes,.discovery-chain .splitters{padding:10px 1%;width:32%}.discovery-chain .resolvers>header,.discovery-chain .routes>header,.discovery-chain .splitters>header{height:18px}.discovery-chain .resolvers>header span,.discovery-chain .routes>header span,.discovery-chain .splitters>header span{position:relative;z-index:1;margin-left:2px}.discovery-chain .resolvers [role=group],.discovery-chain .routes [role=group],.discovery-chain .splitters [role=group]{position:relative;z-index:1;display:flex;flex-direction:column;justify-content:space-around;height:100%}.discovery-chain .resolver-card dl,.discovery-chain .route-card dl,.discovery-chain .splitter-card dl{margin:0;float:none}.discovery-chain .resolver-card,.discovery-chain .route-card,.discovery-chain .splitter-card{margin-bottom:20px}.discovery-chain .route-card header.short dl{display:flex}.discovery-chain .route-card header.short dt::after{content:" ";display:inline-block}.discovery-chain .route-card>header ul{float:right;margin-top:-2px}.discovery-chain .route-card>header ul li{margin-left:5px}.discovery-chain .route-card section{display:flex}.discovery-chain .route-card section header{display:block;width:19px;margin-right:14px}.discovery-chain .resolver-card a{display:block}.discovery-chain .resolver-card dl{display:flex;flex-wrap:wrap;margin-top:5px}.discovery-chain .resolver-card dt{font-size:0;margin-right:6px;margin-top:1px;width:23px;height:20px}.discovery-chain .resolver-card ol{display:flex;flex-wrap:wrap;list-style-type:none}.discovery-chain .route-card,.discovery-chain .splitter-card{position:relative}.discovery-chain .route-card::before,.discovery-chain .splitter-card::before{background-color:var(--token-color-surface-primary);border-radius:var(--decor-radius-full);border:2px solid;border-color:var(--token-color-foreground-disabled);position:absolute;z-index:1;right:-5px;top:50%;margin-top:-5px;width:10px;height:10px}.discovery-chain .resolver-inlets,.discovery-chain .splitter-inlets{width:10px;height:100%;z-index:1}.discovery-chain .splitter-inlets{left:50%;margin-left:calc(-15% - 3px)}.discovery-chain .resolver-inlets{right:calc(31% - 7px)}.consul-bucket-list dd:not(:last-child)::after{display:inline-block;content:"/";margin:0 6px 0 3px}.consul-bucket-list .service+dd,.consul-bucket-list dd+dt{margin-left:0!important}.consul-upstream-instance-list dl.local-bind-socket-mode dt{text-transform:lowercase;font-weight:var(--token-typography-font-weight-semibold)}.consul-health-check-list .health-check-output::before{min-width:20px;min-height:20px;margin-right:15px}@media (max-width:650px){.consul-health-check-list .health-check-output::before{min-width:18px;min-height:18px;margin-right:8px}}.consul-health-check-list .health-check-output dd em{background-color:var(--token-color-surface-strong);cursor:default;font-style:normal;margin-top:-2px;margin-left:.5em}.consul-health-check-list .passing.health-check-output::before{color:var(--token-color-foreground-success)}.consul-health-check-list .warning.health-check-output::before{color:var(--token-color-foreground-warning)}.consul-health-check-list .critical.health-check-output::before{color:var(--token-color-foreground-critical)}.consul-health-check-list .health-check-output,.consul-health-check-list .health-check-output pre{border-radius:var(--decor-radius-100)}.consul-health-check-list .health-check-output dd:first-of-type{color:var(--token-color-foreground-disabled)}.consul-health-check-list .health-check-output pre{background-color:var(--token-color-surface-strong);color:var(--token-color-foreground-faint)}.consul-health-check-list .health-check-output{border-width:1px 1px 1px 4px;color:var(--token-color-foreground-strong);border-color:var(--token-color-surface-interactive-active);border-style:solid;display:flex;padding:20px 24px 20px 16px}.consul-health-check-list .passing.health-check-output{border-left-color:var(--token-color-foreground-success)}.consul-health-check-list .warning.health-check-output{border-left-color:var(--token-color-vault-brand)}.consul-health-check-list .critical.health-check-output{border-left-color:var(--token-color-foreground-critical)}.consul-health-check-list .health-check-output:not(:last-child){margin-bottom:24px}.consul-health-check-list .health-check-output dl:last-of-type,.consul-health-check-list .health-check-output header{width:100%}.consul-health-check-list .health-check-output header{margin-bottom:.9em}.consul-health-check-list .health-check-output>div{flex:1 1 auto;width:calc(100% - 26px);display:flex;flex-wrap:wrap;justify-content:space-between}.consul-health-check-list .health-check-output dl{min-width:110px}.consul-health-check-list .health-check-output dl>*{display:block;width:auto;position:static;padding-left:0}.consul-health-check-list .health-check-output dt{margin-bottom:0}.consul-health-check-list .health-check-output dd{position:relative}.consul-health-check-list .health-check-output dl:nth-last-of-type(2){width:50%}.consul-health-check-list .health-check-output dl:last-of-type{margin-top:1em;margin-bottom:0}.consul-health-check-list .health-check-output dl:last-of-type dt{margin-bottom:.3em}.consul-health-check-list .health-check-output pre{padding:12px 40px 12px 12px;white-space:pre-wrap;position:relative}.consul-health-check-list .health-check-output pre code{word-wrap:break-word}.consul-health-check-list .health-check-output .copy-button{position:absolute;right:.5em;top:.7em}@media (max-width:650px){.consul-health-check-list .health-check-output{padding:15px 19px 15px 14px}.consul-health-check-list .health-check-output::before{margin-right:8px}.consul-health-check-list .health-check-output dl:nth-last-of-type(2){width:100%}.consul-health-check-list .health-check-output dl:not(:last-of-type){margin-right:0}}.consul-instance-checks.passing dt::before{color:var(--token-color-foreground-success)}.consul-instance-checks.warning dt::before{color:var(--token-color-foreground-warning)}.consul-instance-checks.critical dt::before{color:var(--token-color-foreground-critical)}.consul-instance-checks.empty dt::before{color:var(--token-color-foreground-faint)}.consul-exposed-path-list>ul{border-top:1px solid var(--token-color-surface-interactive-active)}.consul-external-source::before,.consul-kind::before{--icon-size:icon-300}.consul-intention-list td.intent- strong::before,.consul-intention-list td.intent-allow strong::before,.consul-intention-list td.intent-deny strong::before,.consul-intention-permission-list .intent-allow::before,.consul-intention-permission-list .intent-deny::before,.consul-intention-search-bar .value- span::before,.consul-intention-search-bar .value-allow span::before,.consul-intention-search-bar .value-deny span::before{margin-right:5px}.consul-intention-list td.intent- strong,.consul-intention-list td.intent-allow strong,.consul-intention-list td.intent-deny strong,.consul-intention-permission-list .intent-allow,.consul-intention-permission-list .intent-deny,.consul-intention-search-bar .value- span,.consul-intention-search-bar .value-allow span,.consul-intention-search-bar .value-deny span{font-weight:var(--token-typography-font-weight-regular);font-size:var(--token-typography-body-200-font-size);display:inline-block}.consul-intention-list td.intent-allow strong,.consul-intention-permission-list .intent-allow,.consul-intention-search-bar .value-allow span{color:var(--token-color-foreground-success-on-surface);background-color:var(--token-color-border-success)}.consul-intention-list td.intent-deny strong,.consul-intention-permission-list .intent-deny,.consul-intention-search-bar .value-deny span{color:var(--token-color-foreground-critical-on-surface);background-color:var(--token-color-border-critical)}.consul-intention-list td.permissions{color:var(--token-color-foreground-action)}.consul-intention-list em{--word-spacing:0.25rem}.consul-intention-list em span::before,.consul-intention-list em span:first-child{margin-right:var(--word-spacing)}.consul-intention-list em span:last-child{margin-left:var(--word-spacing)}.consul-intention-list td{height:59px}.consul-intention-list tr>:nth-child(1){width:calc(30% - 50px)}.consul-intention-list tr>:nth-child(2){width:120px}.consul-intention-list tr>:nth-child(3){width:calc(30% - 50px)}.consul-intention-list tr>:nth-child(4){width:calc(40% - 240px)}.consul-intention-list tr>:nth-child(5){width:160px}.consul-intention-list tr>:last-child{width:60px}.consul-intention-list .menu-panel.confirmation{width:200px}@media (max-width:849px){.consul-intention-list tr>:not(.source):not(.destination):not(.intent){display:none}}.consul-intention-action-warn-modal .modal-dialog-window{max-width:450px}.consul-intention-fieldsets [role=radiogroup]{overflow:visible!important;display:grid;grid-gap:12px;grid-template-columns:repeat(auto-fit,minmax(270px,auto))}.consul-intention-fieldsets .radio-card header>*{display:inline}.consul-intention-fieldsets .permissions>button{float:right}.consul-intention-permission-modal [role=dialog]{width:100%}.consul-intention-permission-list dl.permission-methods dt::before{content:"M"}.consul-intention-permission-list dl.permission-path dt::before{content:"P"}.consul-intention-permission-header-list dt::before,.consul-intention-permission-list dl.permission-header dt::before{content:"H"}.consul-intention-permission-list .detail>div{display:flex;width:100%}.consul-intention-permission-list strong{margin-right:8px}.consul-intention-permission-form h2{border-top:1px solid var(--token-color-foreground-action);padding-top:1.4em;margin-top:.2em;margin-bottom:.6em}.consul-intention-permission-form .consul-intention-permission-header-form{margin-top:10px}.consul-intention-permission-form .consul-intention-permission-header-form fieldset>div,.consul-intention-permission-form fieldset:nth-child(2)>div{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));grid-gap:12px}.consul-intention-permission-form fieldset:nth-child(2)>div label:last-child{grid-column:span 2}.consul-intention-permission-form .ember-basic-dropdown-trigger{padding:5px}.consul-intention-permission-form .checkbox-group{flex-direction:column}.consul-intention-permission-header-list{max-height:200px;overflow:auto}.consul-lock-session-list button{margin-right:var(--horizontal-padding)}.consul-lock-session-form{overflow:hidden}.consul-server-list ul{display:grid;grid-template-columns:repeat(4,minmax(215px,25%));gap:12px}.consul-server-list a:hover div{--tone-border:var(--token-color-foreground-faint)}.consul-server-card .name+dd{color:var(--token-color-hashicorp-brand);animation-name:typo-truncate}.voting-status-non-voter.consul-server-card .health-status+dd{background-color:var(--token-color-surface-strong);color:var(--token-color-foreground-faint)}.consul-server-card:not(.voting-status-non-voter) .health-status.healthy+dd{background-color:var(--token-color-surface-success);color:var(--token-color-palette-green-400)}.consul-server-card:not(.voting-status-non-voter) .health-status:not(.healthy)+dd{background-color:var(--token-color-surface-critical);color:var(--token-color-foreground-critical)}.consul-server-card .health-status+dd::before{--icon-size:icon-000;content:""}.consul-server-card .health-status.healthy+dd::before{--icon-name:icon-check}.consul-server-card .health-status:not(.healthy)+dd::before{--icon-name:icon-x}.consul-server-card{position:relative;overflow:hidden;--padding-x:24px;--padding-y:24px;padding:var(--padding-y) var(--padding-x);--tile-size:3rem}.consul-auth-method-binding-list h2,.consul-auth-method-view section h2{padding-bottom:12px}.voting-status-leader.consul-server-card .name{position:absolute!important}.consul-server-card dd:not(:last-of-type){margin-bottom:calc(var(--padding-y)/ 2)}.voting-status-leader.consul-server-card dd{margin-left:calc(var(--tile-size) + 1rem)}.consul-auth-method-list ul .locality::before{margin-right:4px}.consul-auth-method-view{margin-bottom:32px}.consul-auth-method-view section{width:100%;position:relative;overflow-y:auto}.consul-auth-method-view section table thead td{color:var(--token-color-foreground-faint)}.consul-auth-method-view section table tbody td{color:var(--token-color-hashicorp-brand)}.consul-auth-method-view section table tbody tr{cursor:default}.consul-auth-method-view section table tbody tr:hover{box-shadow:none}.consul-auth-method-view section dt{width:30%}.consul-auth-method-view section dd{width:70%}.consul-auth-method-binding-list p{margin-bottom:4px!important}.consul-auth-method-binding-list code{background-color:var(--token-color-surface-strong);padding:0 12px}.consul-auth-method-nspace-list thead td{color:var(--token-color-foreground-faint)!important}.consul-auth-method-nspace-list tbody td{color:var(--token-color-hashicorp-brand)}.consul-auth-method-nspace-list tbody tr{cursor:default}.consul-auth-method-nspace-list tbody tr:hover{box-shadow:none}.role-selector [name="role[state]"],.role-selector [name="role[state]"]+*{display:none}.role-selector [name="role[state]"]:checked+*{display:block}.topology-notices button{color:var(--token-color-foreground-action);float:right;margin-top:16px;margin-bottom:32px}#metrics-container .link a,.topology-container{color:var(--token-color-foreground-faint)}#downstream-container .topology-metrics-card:not(:last-child),#upstream-column #upstream-container:not(:last-child),#upstream-container .topology-metrics-card:not(:last-child){margin-bottom:8px}#downstream-container,#metrics-container,#upstream-container{border-radius:var(--decor-radius-100);border:1px solid;border-color:var(--token-color-surface-interactive-active)}#downstream-container,#upstream-container{background-color:var(--token-color-surface-strong);padding:12px}#downstream-container>div:first-child{display:inline-flex}#downstream-container>div:first-child span::before{background-color:var(--token-color-foreground-faint)}#metrics-container div:first-child{background-color:var(--token-color-surface-primary);padding:12px;border:none}#metrics-container .link{background-color:var(--token-color-surface-strong);padding:18px}#metrics-container .link a:hover{color:var(--token-color-foreground-action)}#downstream-lines svg path,#upstream-lines svg path{fill:transparent}#downstream-lines svg .allow-arrow,#upstream-lines svg .allow-arrow{fill:var(--token-color-palette-neutral-300);stroke-linejoin:round}#downstream-lines svg .allow-arrow,#downstream-lines svg .allow-dot,#downstream-lines svg path,#upstream-lines svg .allow-arrow,#upstream-lines svg .allow-dot,#upstream-lines svg path{stroke:var(--token-color-palette-neutral-300);stroke-width:2}#downstream-lines svg path[data-permission=empty],#downstream-lines svg path[data-permission=not-defined],#upstream-lines svg path[data-permission=empty],#upstream-lines svg path[data-permission=not-defined]{stroke-dasharray:4}#downstream-lines svg path[data-permission=deny],#upstream-lines svg path[data-permission=deny]{stroke:var(--token-color-foreground-critical)}#downstream-lines svg .deny-dot,#upstream-lines svg .deny-dot{stroke:var(--token-color-foreground-critical);stroke-width:2}#downstream-lines svg .deny-arrow,#upstream-lines svg .deny-arrow{fill:var(--token-color-foreground-critical);stroke:var(--token-color-foreground-critical);stroke-linejoin:round}.topology-notices{display:flow-root}.topology-container{display:grid;height:100%;align-items:start;grid-template-columns:2fr 1fr 2fr 1fr 2fr;grid-template-rows:50px 1fr 50px;grid-template-areas:"down-cards down-lines . up-lines up-cards" "down-cards down-lines metrics up-lines up-cards" "down-cards down-lines . up-lines up-cards"}#downstream-container{grid-area:down-cards}#downstream-lines{grid-area:down-lines;margin-left:-20px}#upstream-lines{grid-area:up-lines;margin-right:-20px}#upstream-column{grid-area:up-cards}#downstream-lines,#upstream-lines{position:relative}#metrics-container{grid-area:metrics}#metrics-container .link a::before{background-color:var(--token-color-foreground-faint);margin-right:4px}#downstream-container .topology-metrics-card,#upstream-container .topology-metrics-card{display:block;color:var(--token-color-foreground-faint);overflow:hidden;background-color:var(--token-color-surface-primary);border-radius:var(--decor-radius-100);border:1px solid;border-color:var(--token-color-surface-interactive-active)}#downstream-container .topology-metrics-card p,#upstream-container .topology-metrics-card p{padding:12px 12px 0;margin-bottom:0!important}#downstream-container .topology-metrics-card p.empty,#upstream-container .topology-metrics-card p.empty{padding:12px!important}#downstream-container .topology-metrics-card div dl,#upstream-container .topology-metrics-card div dl{display:inline-flex;margin-right:8px}#downstream-container .topology-metrics-card div dd,#upstream-container .topology-metrics-card div dd{color:var(--token-color-foreground-faint)}#downstream-container .topology-metrics-card div span,#upstream-container .topology-metrics-card div span{margin-right:8px}#downstream-container .topology-metrics-card div dt::before,#downstream-container .topology-metrics-card div span::before,#upstream-container .topology-metrics-card div dt::before,#upstream-container .topology-metrics-card div span::before{margin-right:4px}#downstream-container .topology-metrics-card div .health dt::before,#downstream-container .topology-metrics-card div .nspace dt::before,#upstream-container .topology-metrics-card div .health dt::before,#upstream-container .topology-metrics-card div .nspace dt::before{margin-top:2px}#downstream-container .topology-metrics-card div .health dt::before,#downstream-container .topology-metrics-card div .nspace dt::before,#downstream-container .topology-metrics-card div .partition dt::before,#upstream-container .topology-metrics-card div .health dt::before,#upstream-container .topology-metrics-card div .nspace dt::before,#upstream-container .topology-metrics-card div .partition dt::before{--icon-color:var(--token-color-foreground-faint)}#downstream-container .topology-metrics-card div .passing::before,#upstream-container .topology-metrics-card div .passing::before{--icon-color:var(--token-color-foreground-success)}#downstream-container .topology-metrics-card div .warning::before,#upstream-container .topology-metrics-card div .warning::before{--icon-color:var(--token-color-foreground-warning)}#downstream-container .topology-metrics-card div .critical::before,#upstream-container .topology-metrics-card div .critical::before{--icon-color:var(--token-color-foreground-critical)}#downstream-container .topology-metrics-card div .empty::before,#upstream-container .topology-metrics-card div .empty::before{--icon-color:var(--token-color-foreground-faint)}#downstream-container .topology-metrics-card .details,#upstream-container .topology-metrics-card .details{padding:0 12px 12px}#downstream-container .topology-metrics-card .details>:not(:last-child),#upstream-container .topology-metrics-card .details>:not(:last-child){padding-bottom:6px}#downstream-container .topology-metrics-card .details .group,#upstream-container .topology-metrics-card .details .group{display:grid;grid-template-columns:20px 1fr;grid-template-rows:repeat(2,1fr);grid-template-areas:"partition partition" "union namespace"}#downstream-container .topology-metrics-card .details .group span,#upstream-container .topology-metrics-card .details .group span{display:inline-block;grid-area:union;padding-left:7px;margin-right:0}#downstream-container .topology-metrics-card .details .group span::before,#upstream-container .topology-metrics-card .details .group span::before{margin-right:0;--icon-color:var(--token-color-foreground-faint)}#downstream-container .topology-metrics-card .details .group dl:first-child,#upstream-container .topology-metrics-card .details .group dl:first-child{grid-area:partition;padding-bottom:6px}#downstream-container .topology-metrics-card .details .group dl:nth-child(2),#upstream-container .topology-metrics-card .details .group dl:nth-child(2){grid-area:namespace}.topology-metrics-source-type{margin:6px 0 6px 12px;display:table}.topology-metrics-popover>button{position:absolute;transform:translate(-50%,-50%);background-color:var(--token-color-surface-primary);padding:1px}.topology-metrics-popover>button:hover{cursor:pointer}.topology-metrics-popover>button:disabled,html[data-route^="dc.nodes.show.metadata"] table tr{cursor:default}.topology-metrics-popover>button:active,.topology-metrics-popover>button:focus{outline:0}.topology-metrics-popover.deny .informed-action header::before{display:none}.topology-metrics-popover.deny .tippy-arrow::after,.topology-metrics-popover.deny>button::before{--icon-color:var(--token-color-foreground-critical)}.topology-metrics-popover.not-defined .tippy-arrow::after,.topology-metrics-popover.not-defined>button::before{--icon-color:var(--token-color-vault-brand)}#metrics-container .sparkline-wrapper svg path{stroke-width:0}#metrics-container .sparkline-wrapper .tooltip{padding:0 0 10px;border:1px solid var(--token-color-palette-neutral-300);background:#fff;border-radius:2px;box-sizing:border-box;box-shadow:var(--token-elevation-higher-box-shadow)}#metrics-container .sparkline-wrapper .tooltip .sparkline-time{padding:8px 10px;color:#000;border-bottom:1px solid var(--token-color-surface-interactive-active);margin-bottom:4px;text-align:center}#metrics-container .sparkline-wrapper .tooltip .sparkline-tt-legend,#metrics-container .sparkline-wrapper .tooltip .sparkline-tt-sum{border:0;padding:3px 10px 0}#metrics-container .sparkline-wrapper .tooltip .sparkline-tt-sum{border-top:1px solid var(--token-color-surface-interactive-active);margin-top:4px;padding:8px 10px 0}#metrics-container .sparkline-wrapper .tooltip .sparkline-tt-legend-color{width:12px;height:12px;border-radius:2px;margin:0 5px 0 0;padding:0}#metrics-container .sparkline-wrapper .tooltip .sparkline-tt-legend-value,#metrics-container .sparkline-wrapper .tooltip .sparkline-tt-sum-value{float:right}#metrics-container .sparkline-wrapper div.tooltip:before{content:"";display:block;position:absolute;width:12px;height:12px;left:15px;bottom:-7px;border:1px solid var(--token-color-palette-neutral-300);border-top:0;border-left:0;background:#fff;transform:rotate(45deg)}.sparkline-key h3::before{margin:2px 3px 0 0;font-size:var(--token-typography-body-200-font-size)}.sparkline-key h3{color:var(--token-color-foreground-strong)}.sparkline-key .sparkline-key-content dd,.sparkline-key-link{color:var(--token-color-foreground-faint)}.sparkline-key-link:hover{color:var(--token-color-foreground-action)}#metrics-container:hover .sparkline-key-link::before{margin:1px 3px 0 0;font-size:12px}#metrics-container div .sparkline-wrapper,#metrics-container div .sparkline-wrapper svg.sparkline{width:100%;height:70px;padding:0;margin:0}#metrics-container div .sparkline-wrapper{position:relative}#metrics-container div .sparkline-wrapper .tooltip{visibility:hidden;position:absolute;z-index:10;bottom:78px;width:217px}#metrics-container div .sparkline-wrapper .sparkline-tt-legend-color{display:inline-block}#metrics-container div .sparkline-wrapper .topology-metrics-error,#metrics-container div .sparkline-wrapper .topology-metrics-loader{padding-top:15px}.sparkline-key .sparkline-key-content{width:500px;min-height:100px}.sparkline-key .sparkline-key-content dl{padding:10px 0 0}.sparkline-key .sparkline-key-content dt{font-weight:var(--token-typography-font-weight-semibold);width:125px;float:left}.sparkline-key .sparkline-key-content dd{margin:0 0 12px 135px}.sparkline-key-link{visibility:hidden;float:right;margin-top:-35px;margin-right:12px}#metrics-container:hover .sparkline-key-link{visibility:visible}.topology-metrics-stats{padding:12px 12px 0;display:flex;flex-flow:row wrap;justify-content:space-between;align-items:stretch;width:100%;border-top:1px solid var(--token-color-surface-interactive-active)}.topology-metrics-stats dl{display:flex;padding-bottom:12px}.topology-metrics-stats dt{margin-right:5px;line-height:1.5em!important}.topology-metrics-stats dd{color:var(--token-color-foreground-disabled)!important}.topology-metrics-stats span{padding-bottom:12px}.topology-metrics-status-error,.topology-metrics-status-loader{color:var(--token-color-foreground-faint);text-align:center;margin:0 auto!important;display:block}.topology-metrics-status-error span::before,.topology-metrics-status-loader span::before{background-color:var(--token-color-foreground-faint)}span.topology-metrics-status-loader::after{--icon-name:var(--icon-loading);content:"";margin-left:.5rem}.consul-node-peer-info .consul-node-peer-info__name,.consul-peer-info .consul-peer-info__description{margin-left:4px}.consul-intention-list-table__meta-info{display:flex}.consul-intention-list-table__meta-info .consul-intention-list-table__meta-info__peer{display:flex;align-items:center}.consul-node-peer-info,.peerings-badge{align-items:center;display:flex}.consul-peer-search-bar .value-active span::before,.consul-peer-search-bar .value-deleting span::before,.consul-peer-search-bar .value-establishing span::before,.consul-peer-search-bar .value-failing span::before,.consul-peer-search-bar .value-pending span::before,.consul-peer-search-bar .value-terminated span::before{--icon-size:icon-000;content:""}.consul-peer-search-bar .value-active span,.consul-peer-search-bar .value-deleting span,.consul-peer-search-bar .value-establishing span,.consul-peer-search-bar .value-failing span,.consul-peer-search-bar .value-pending span,.consul-peer-search-bar .value-terminated span{font-size:var(--token-typography-body-200-font-size)}.consul-peer-search-bar .value-pending span::before{--icon-name:icon-running;--icon-color:var(--token-color-palette-neutral-600)}.consul-peer-search-bar .value-pending span{background-color:var(--token-color-consul-surface);color:var(--token-color-consul-foreground)}.consul-peer-search-bar .value-establishing span::before{--icon-name:icon-running;--icon-color:var(--token-color-palette-neutral-600)}.consul-peer-search-bar .value-establishing span{background-color:var(--token-color-palette-blue-50);color:var(--token-color-palette-blue-200)}.consul-peer-search-bar .value-active span::before{--icon-name:icon-check;--icon-color:var(--token-color-palette-green-400)}.consul-peer-search-bar .value-active span{background-color:var(--token-color-palette-green-50);color:var(--token-color-palette-green-200)}.consul-peer-search-bar .value-failing span::before{--icon-name:icon-x;--icon-color:var(--token-color-palette-red-200)}.consul-peer-search-bar .value-failing span{background-color:var(--token-color-palette-red-50);color:var(--token-color-palette-red-200)}.consul-peer-search-bar .value-terminated span::before{--icon-name:icon-x-square;--icon-color:var(--token-color-palette-neutral-600)}.consul-peer-search-bar .value-terminated span{background-color:var(--token-color-palette-neutral-200);color:var(--token-color-palette-neutral-600)}.consul-peer-search-bar .value-deleting span::before{--icon-name:icon-loading;--icon-color:var(--token-color-foreground-warning-on-surface)}.consul-peer-search-bar .value-deleting span{background-color:var(--token-color-surface-warning);color:var(--token-color-foreground-warning-on-surface)}.peers__list__peer-detail{display:flex;align-content:center;gap:18px}.border-bottom-primary{border-bottom:1px solid var(--token-color-border-primary)}.peerings-badge{justify-content:center;padding:2px 8px;border-radius:5px;gap:4px}.peerings-badge.active{background:var(--token-color-surface-success);color:var(--token-color-foreground-success)}.peerings-badge.pending{background:var(--token-color-consul-surface);color:var(--token-color-consul-brand)}.peerings-badge.establishing{background:var(--token-color-surface-action);color:var(--token-color-foreground-action)}.peerings-badge.failing{background:var(--token-color-surface-critical);color:var(--token-color-foreground-critical)}.peerings-badge.deleting{background:var(--token-color-surface-warning);color:var(--token-color-foreground-warning-on-surface)}.peerings-badge.terminated,.peerings-badge.undefined{background:var(--token-color-surface-interactive-active);color:var(--token-color-foreground-primary)}.consul-peer-info,section[data-route="dc.show.serverstatus"] .server-failure-tolerance dt{color:var(--token-color-foreground-faint)}.consul-peer-info{background:var(--token-color-surface-faint);padding:0 8px;border-radius:2px;display:flex;align-items:center}.consul-peer-form{width:416px}.consul-peer-form nav{margin-bottom:20px}.consul-peer-form-generate{width:416px;min-height:200px}.consul-peer-form-generate ol{list-style-position:outside;list-style-type:none;counter-reset:hexagonal-counter;position:relative}.consul-peer-form-generate ol::before{content:"";border-left:var(--decor-border-100);border-color:var(--token-color-palette-neutral-300);height:100%;position:absolute;left:2rem}.consul-peer-form-generate li{counter-increment:hexagonal-counter;position:relative;margin-left:60px;margin-bottom:1rem}.consul-peer-form-generate li .copyable-code{margin-top:1rem}.consul-peer-form-generate li::before{--icon-name:icon-hexagon;--icon-size:icon-600;content:"";position:absolute;z-index:2}.consul-peer-form-generate li::after{content:counter(hexagonal-counter);position:absolute;top:0;background-color:var(--token-color-palette-neutral-0);z-index:1;text-align:center}.consul-peer-form-generate li::after,.consul-peer-form-generate li::before{left:-2.4rem;width:20px;height:20px}.agentless-node-notice .hds-alert__title{display:flex;justify-content:space-between}.definition-table dt{line-height:var(--token-typography-body-300-line-height)}.app-view>div form:not(.filter-bar) [role=radiogroup] label,.modal-dialog [role=document] [role=radiogroup] label{line-height:var(--token-typography-body-100-line-height)}.app-view h1 em,.app-view>div form:not(.filter-bar) [role=radiogroup] label>em,.consul-intention-list td.destination em,.consul-intention-list td.source em,.modal-dialog [role=document] .type-password>em,.modal-dialog [role=document] .type-select>em,.modal-dialog [role=document] .type-text>em,.modal-dialog [role=document] [role=radiogroup] label>em,.modal-dialog [role=document] form button+em,.modal-dialog [role=document] table th em,.oidc-select label>em,.type-toggle>em,main .type-password>em,main .type-select>em,main .type-text>em,main form button+em,main table th em{font-style:normal}.consul-exposed-path-list>ul>li>.header :not(button),.consul-lock-session-list ul>li:not(:first-child)>.header :not(button),.consul-upstream-instance-list li>.header :not(button),.list-collection>ul>li:not(:first-child)>.header :not(button){font-size:inherit;font-weight:inherit}@media (max-width:420px) and (-webkit-min-device-pixel-ratio:0){input{font-size:var(--token-typography-body-300-font-size)!important}}#wrapper,#wrapper>footer>*,.modal-dialog>*,main>*{box-sizing:border-box}html[data-route$=create] main,html[data-route$=edit] main{max-width:1260px}fieldset [role=group]{display:flex;flex-wrap:wrap;flex-direction:row}.outlet[data-state=loading],html.ember-loading .view-loader,html:not(.has-nspaces) [class*=nspace-],html:not(.has-partitions) [class*=partition-],html[data-state=idle] .view-loader{display:none}[role=group] fieldset{width:50%}[role=group] fieldset:not(:first-of-type){padding-left:20px;border-left:1px solid;border-left:var(--token-color-foreground-faint)}[role=group] fieldset:not(:last-of-type){padding-right:20px}.app-view{margin-top:50px}@media (max-width:849px){html:not(.with-breadcrumbs) .app-view{margin-top:10px}}html body>.brand-loader{transition-property:transform,opacity;transform:translate(0,0);opacity:1}html[data-state]:not(.ember-loading) body>.brand-loader{opacity:0}@media (min-width:900px){html[data-state] body>.brand-loader{transform:translate(calc(var(--chrome-width)/ 2),0)}}html[data-route$=create] .app-view>header+div>:first-child,html[data-route$=edit] .app-view>header+div>:first-child{margin-top:1.8em}.app-view>div .container,.app-view>div .tab-section .consul-health-check-list,.app-view>div .tab-section>.search-bar+p,.app-view>div .tab-section>:first-child:not(.filter-bar):not(table){margin-top:1.25em}.consul-upstream-instance-list,html[data-route^="dc.nodes.show.sessions"] .consul-lock-session-list{margin-top:0!important}.consul-auth-method-list ul,.consul-node-list ul,.consul-nspace-list ul,.consul-peer-list ul,.consul-policy-list ul,.consul-role-list ul,.consul-service-instance-list ul,.consul-token-list ul,html[data-route="dc.services.index"] .consul-service-list ul,html[data-route^="dc.nodes.show.sessions"] .consul-lock-session-list ul{border-top-width:0!important}#wrapper{display:flex;min-height:100vh}main{padding:0 48px;position:relative;flex:1}html:not([data-route$=index]):not([data-route$=instances]) main{margin-bottom:2em}@media (max-width:849px){.actions button.copy-btn{margin-top:-56px;padding:0}}.modal-dialog [role=document] p:not(:last-child),main p:not(:last-child){margin-bottom:1em}.modal-dialog [role=document] form+div .with-confirmation,.modal-dialog [role=document] form:not(.filter-bar),main form+div .with-confirmation,main form:not(.filter-bar){margin-bottom:2em}@media (max-width:420px){main form [type=reset]{float:right;margin-right:0!important}}html[data-route^="dc.services.show"] .app-view .actions .external-dashboard{position:absolute;top:50px;right:0}html[data-route^="dc.services.instance"] .app-view>header dl{float:left;margin-top:19px;margin-bottom:23px;margin-right:50px}html[data-route^="dc.services.instance"] .app-view>header dt{font-weight:var(--token-typography-font-weight-bold)}html[data-route^="dc.services.instance"] .tab-nav{border-top:var(--decor-border-100)}html[data-route^="dc.services.instance"] .tab-section section:not(:last-child){border-bottom:var(--decor-border-100);padding-bottom:24px}html[data-route^="dc.services.instance"] .tab-nav,html[data-route^="dc.services.instance"] .tab-section section:not(:last-child){border-color:var(--token-color-surface-interactive-active)}html[data-route^="dc.services.instance.metadata"] .tab-section section h2{margin:24px 0 12px}html[data-route^="dc.kv"] .type-toggle{float:right;margin-bottom:0!important}html[data-route^="dc.kv.edit"] h2{border-bottom:var(--decor-border-200);border-color:var(--token-color-surface-interactive-active);padding-bottom:.2em;margin-bottom:.5em}html[data-route^="dc.acls.index"] main td strong{margin-right:3px}@media (max-width:420px){html[data-route^="dc.acls.create"] main header .actions,html[data-route^="dc.acls.edit"] main header .actions{float:none;display:flex;justify-content:space-between;margin-bottom:1em}html[data-route^="dc.acls.create"] main header .actions .with-feedback,html[data-route^="dc.acls.edit"] main header .actions .with-feedback{position:absolute;right:0}html[data-route^="dc.acls.create"] main header .actions .with-confirmation,html[data-route^="dc.acls.edit"] main header .actions .with-confirmation{margin-top:0}}html[data-route^="dc.intentions.edit"] .definition-table{margin-bottom:1em}section[data-route="dc.show.serverstatus"] .server-failure-tolerance{box-shadow:none;padding:var(--padding-y) var(--padding-x);max-width:770px;display:flex;flex-wrap:wrap}section[data-route="dc.show.serverstatus"] .server-failure-tolerance>header{width:100%;display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding-bottom:.5rem;margin-bottom:1rem;border-bottom:var(--decor-border-100);border-color:var(--tone-border)}section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em{background-color:var(--token-color-surface-interactive-active);text-transform:uppercase;font-style:normal}section[data-route="dc.show.serverstatus"] .server-failure-tolerance>section{width:50%}section[data-route="dc.show.serverstatus"] .server-failure-tolerance dl,section[data-route="dc.show.serverstatus"] .server-failure-tolerance>section{display:flex;flex-direction:column}section[data-route="dc.show.serverstatus"] .server-failure-tolerance dl{flex-grow:1;justify-content:space-between}section[data-route="dc.show.serverstatus"] .server-failure-tolerance dl.warning dd::before{--icon-name:icon-alert-circle;--icon-size:icon-800;--icon-color:var(--token-color-foreground-warning);content:"";margin-right:.5rem}section[data-route="dc.show.serverstatus"] .server-failure-tolerance section:first-of-type dl{padding-right:1.5rem}section[data-route="dc.show.serverstatus"] .server-failure-tolerance dd{display:flex;align-items:center;color:var(--token-color-hashicorp-brand)}section[data-route="dc.show.serverstatus"] .server-failure-tolerance header span::before{--icon-name:icon-info;--icon-size:icon-300;--icon-color:var(--token-color-foreground-faint);vertical-align:unset;content:""}section[data-route="dc.show.serverstatus"] section:not([class*=-tolerance]) h2{margin-top:1.5rem;margin-bottom:1.5rem}section[data-route="dc.show.serverstatus"] section:not([class*=-tolerance]) header{margin-top:18px;margin-bottom:18px}section[data-route="dc.show.serverstatus"] .redundancy-zones section header{display:flow-root}section[data-route="dc.show.serverstatus"] .redundancy-zones section header h3{float:left;margin-right:.5rem}section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not(.warning){background-color:var(--token-color-surface-strong)}section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl.warning{background-color:var(--token-color-border-warning);color:var(--token-color-palette-amber-400)}section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl.warning::before{--icon-name:icon-alert-circle;--icon-size:icon-000;margin-right:.312rem;content:""}section[data-route="dc.show.serverstatus"] .redundancy-zones section header dt::after{content:":";display:inline-block;vertical-align:revert;background-color:transparent}section[data-route="dc.show.license"] .validity p{color:var(--token-color-foreground-faint)}section[data-route="dc.show.license"] .validity dl dt::before{content:"";margin-right:.25rem}section[data-route="dc.show.license"] .validity dl .expired::before{--icon-name:icon-x-circle;--icon-color:var(--token-color-foreground-critical)}section[data-route="dc.show.license"] .validity dl .warning::before{--icon-name:icon-alert-circle;--icon-color:var(--token-color-foreground-warning)}section[data-route="dc.show.license"] .validity dl .valid:not(.warning)::before{--icon-name:icon-check-circle;--icon-color:var(--token-color-foreground-success)}section[data-route="dc.show.license"] aside{box-shadow:none;padding:var(--padding-y) var(--padding-x);width:40%;min-width:413px;margin-top:1rem}section[data-route="dc.show.license"] aside header{margin-bottom:1rem}.prefers-reduced-motion{--icon-loading:icon-loading}@media (prefers-reduced-motion){:root{--hds-app-sidenav-animation-duration:0;--icon-loading:icon-loading}}.consul-intention-fieldsets .value->:last-child::before,.consul-intention-fieldsets .value-allow>:last-child::before,.consul-intention-fieldsets .value-deny>:last-child::before{--icon-size:icon-500;--icon-resolution:0.5}.consul-intention-fieldsets .value-allow>:last-child::before,.consul-intention-list td.intent-allow strong::before,.consul-intention-permission-list .intent-allow::before,.consul-intention-search-bar .value-allow span::before{--icon-name:icon-arrow-right;--icon-color:var(--token-color-foreground-success-on-surface)}.consul-intention-fieldsets .value-deny>:last-child::before,.consul-intention-list td.intent-deny strong::before,.consul-intention-permission-list .intent-deny::before,.consul-intention-search-bar .value-deny span::before{--icon-name:icon-skip;--icon-color:var(--token-color-foreground-critical-on-surface)}.consul-intention-fieldsets .value->:last-child::before,.consul-intention-list td.intent- strong::before,.consul-intention-search-bar .value- span::before{--icon-name:icon-layers}*{border-width:0}.animatable.tab-nav ul::after,.consul-auth-method-type,.consul-external-source,.consul-intention-action-warn-modal button.dangerous,.consul-intention-action-warn-modal button.dangerous:disabled,.consul-intention-action-warn-modal button.dangerous:focus,.consul-intention-action-warn-modal button.dangerous:hover:active,.consul-intention-action-warn-modal button.dangerous:hover:not(:disabled):not(:active),.consul-intention-list td.intent- strong,.consul-intention-permission-form button.type-submit,.consul-intention-permission-form button.type-submit:disabled,.consul-intention-permission-form button.type-submit:focus:not(:disabled),.consul-intention-permission-form button.type-submit:hover:not(:disabled),.consul-intention-search-bar .value- span,.consul-kind,.consul-source,.consul-transparent-proxy,.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:first-child,.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:focus:first-child,.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:hover:first-child,.discovery-chain .route-card>header ul li,.informed-action>ul>.dangerous>*,.informed-action>ul>.dangerous>:focus,.informed-action>ul>.dangerous>:hover,.leader,.menu-panel>ul>li.dangerous>:first-child,.menu-panel>ul>li.dangerous>:focus:first-child,.menu-panel>ul>li.dangerous>:hover:first-child,.modal-dialog [role=document]>footer,.modal-dialog [role=document]>header,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:first-child,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child,.tab-nav .selected>*,.topology-metrics-source-type,html[data-route^="dc.acls.index"] main td strong,span.policy-node-identity,span.policy-service-identity,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:first-child,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child{border-style:solid}.consul-auth-method-type,.consul-external-source,.consul-kind,.consul-source,.consul-transparent-proxy,.leader,.topology-metrics-source-type,span.policy-node-identity,span.policy-service-identity{background-color:var(--token-color-surface-strong);border-color:var(--token-color-foreground-faint);color:var(--token-color-foreground-primary)}.consul-intention-list td.intent- strong,.consul-intention-search-bar .value- span,.discovery-chain .route-card>header ul li,.modal-dialog [role=document]>footer,.modal-dialog [role=document]>header,html[data-route^="dc.acls.index"] main td strong{background-color:var(--token-color-surface-strong);border-color:var(--token-color-palette-neutral-300);color:var(--token-color-foreground-strong)}.animatable.tab-nav ul::after,.consul-intention-permission-form button.type-submit,.consul-intention-permission-form button.type-submit:disabled,.tab-nav .selected>*{background-color:var(--token-color-surface-primary);border-color:var(--token-color-foreground-action);color:var(--token-color-foreground-action)}.consul-intention-permission-form button.type-submit:focus:not(:disabled),.consul-intention-permission-form button.type-submit:hover:not(:disabled){background-color:var(--token-color-surface-action);border-color:var(--token-color-foreground-action);color:var(--token-color-palette-blue-500)}.consul-intention-action-warn-modal button.dangerous,.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:first-child,.informed-action>ul>.dangerous>*,.menu-panel>ul>li.dangerous>:first-child,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:first-child,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:first-child{background-color:transparent;border-color:var(--token-color-foreground-critical);color:var(--token-color-foreground-critical)}.consul-intention-action-warn-modal button.dangerous:disabled{background-color:var(--token-color-border-critical);border-color:var(--token-color-foreground-disabled);color:var(--token-color-surface-primary)}.consul-intention-action-warn-modal button.dangerous:focus,.consul-intention-action-warn-modal button.dangerous:hover:not(:disabled):not(:active),.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:focus:first-child,.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:hover:first-child,.informed-action>ul>.dangerous>:focus,.informed-action>ul>.dangerous>:hover,.menu-panel>ul>li.dangerous>:focus:first-child,.menu-panel>ul>li.dangerous>:hover:first-child,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child{background-color:var(--token-color-foreground-critical);border-color:var(--token-color-foreground-critical-high-contrast);color:var(--token-color-surface-primary)}.consul-intention-action-warn-modal button.dangerous:hover:active{background-color:var(--token-color-palette-red-400);border-color:var(--token-color-foreground-critical-high-contrast);color:var(--token-color-surface-primary)}.hover\:scale-125:hover{--tw-scale-x:1.25;--tw-scale-y:1.25}.group:hover .group-hover\:opacity-100{opacity:1} \ No newline at end of file diff --git a/agent/uiserver/dist/assets/consul-ui-88f8ddeb1f6a751c68c9f1de8978befa.js b/agent/uiserver/dist/assets/consul-ui-a3d723b2486613aa25201645e617bbf2.js similarity index 93% rename from agent/uiserver/dist/assets/consul-ui-88f8ddeb1f6a751c68c9f1de8978befa.js rename to agent/uiserver/dist/assets/consul-ui-a3d723b2486613aa25201645e617bbf2.js index 15e96ebf2e..0a6ee6ad76 100644 --- a/agent/uiserver/dist/assets/consul-ui-88f8ddeb1f6a751c68c9f1de8978befa.js +++ b/agent/uiserver/dist/assets/consul-ui-a3d723b2486613aa25201645e617bbf2.js @@ -771,7 +771,7 @@ var s,c,d,p,f,m e.default=u,(0,t.setComponentTemplate)(a,u)})),define("consul-ui/components/consul/datacenter/selector/index",["exports","@ember/component","@ember/template-factory","@glimmer/component","@ember/object","@glimmer/tracking"],(function(e,t,n,l,r,i){var o,a function u(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -const s=(0,n.createTemplateFactory)({id:"5vaQzlVR",block:'[[[1,"\\n"],[44,[[30,1]],[[[41,[28,[37,2],[[30,3,["length"]],1],null],[[[1," "],[8,[30,2,["Title"]],[[24,0,"consul-side-nav__selector-title"]],null,[["default"],[[[[1,[28,[35,3],["components.hashicorp-consul.side-nav.datacenters.title"],null]]],[]]]]],[1,"\\n "],[8,[39,4],[[24,0,"consul-datacenter-selector"]],[["@list","@items","@item","@key","@icon","@placeholder","@description"],[[30,2],[28,[37,5],["Primary:desc","Local:desc","Name:asc",[30,3]],null],[30,4],"Name","server-cluster",[28,[37,3],["components.hashicorp-consul.side-nav.datacenters.placeholder"],null],[28,[37,3],["components.hashicorp-consul.side-nav.datacenters.description"],null]]],[["default"],[[[[1,"\\n "],[8,[30,5,["Checkmark"]],[[24,0,"consul-datacenter-selector__item"]],[["@selected","@href","@isHrefExternal"],[[28,[37,6],[[30,4,["Name"]],[30,6,["Name"]]],null],[28,[37,7],["."],[["params"],[[28,[37,8],null,[["dc","partition","nspace"],[[30,6,["Name"]],[27],[52,[28,[37,2],[[30,7,["length"]],0],null],[30,7],[27]]]]]]]],false]],[["default"],[[[[1,"\\n "],[10,1],[14,0,"consul-datacenter-selector__dc-name"],[12],[1,"\\n "],[1,[30,6,["Name"]]],[1,"\\n\\n"],[41,[28,[37,9],[[30,6,["Local"]],[30,6,["Primary"]]],null],[[[1," "],[10,1],[14,0,"consul-datacenter-selector__badges"],[12],[1,"\\n"],[41,[30,6,["Primary"]],[[[1," "],[8,[39,10],null,[["@text"],["Primary"]],null],[1,"\\n"]],[]],null],[41,[30,6,["Local"]],[[[1," "],[8,[39,10],null,[["@text"],["Local"]],null],[1,"\\n"]],[]],null],[1," "],[13],[1,"\\n"]],[]],null],[1," "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[5,6]]]]],[1,"\\n"]],[]],[[[1," "],[8,[30,2,["Item"]],[[24,0,"consul-side-nav__datacenter"]],null,[["default"],[[[[1,"\\n "],[8,[39,11],null,[["@name","@color"],["server-cluster","var(--token-form-control-disabled-foreground-color)"]],null],[1,"\\n "],[8,[39,12],null,[["@size","@color"],["200","disabled"]],[["default"],[[[[1,[30,4,["Name"]]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]]]],[2]]]],["@list","SNL","@dcs","@dc","Dropdown","item","@nspace"],false,["let","if","gt","t","nav-selector","sort-by","eq","href-to","hash","or","hds/badge","flight-icon","hds/text/display"]]',moduleName:"consul-ui/components/consul/datacenter/selector/index.hbs",isStrictMode:!1}) +const s=(0,n.createTemplateFactory)({id:"xS/PrPr6",block:'[[[1,"\\n"],[44,[[30,1]],[[[41,[28,[37,2],[[30,3,["length"]],1],null],[[[1," "],[8,[30,2,["Title"]],[[24,0,"consul-side-nav__selector-title"]],null,[["default"],[[[[1,[28,[35,3],["components.hashicorp-consul.side-nav.datacenters.title"],null]]],[]]]]],[1,"\\n "],[8,[39,4],[[24,0,"consul-datacenter-selector"]],[["@list","@items","@item","@key","@icon","@placeholder","@description"],[[30,2],[28,[37,5],["Primary:desc","Local:desc","Name:asc",[30,3]],null],[30,4],"Name","server-cluster",[28,[37,3],["components.hashicorp-consul.side-nav.datacenters.placeholder"],null],[28,[37,3],["components.hashicorp-consul.side-nav.datacenters.description"],null]]],[["default"],[[[[1,"\\n "],[8,[30,5,["Dropdown","Checkmark"]],[[24,0,"consul-datacenter-selector__item"]],[["@selected","@href","@isHrefExternal"],[[28,[37,6],[[30,4,["Name"]],[30,5,["item","Name"]]],null],[28,[37,7],["."],[["params"],[[28,[37,8],null,[["dc","partition","nspace"],[[30,5,["item","Name"]],[27],[52,[28,[37,2],[[30,6,["length"]],0],null],[30,6],[27]]]]]]]],false]],[["default"],[[[[1,"\\n "],[10,1],[14,0,"consul-datacenter-selector__dc-name"],[12],[1,"\\n "],[1,[30,5,["item","Name"]]],[1,"\\n\\n"],[41,[28,[37,9],[[30,5,["item","Local"]],[30,5,["item","Primary"]]],null],[[[1," "],[10,1],[14,0,"consul-datacenter-selector__badges"],[12],[1,"\\n"],[41,[30,5,["item","Primary"]],[[[1," "],[8,[39,10],null,[["@text"],["Primary"]],null],[1,"\\n"]],[]],null],[41,[30,5,["item","Local"]],[[[1," "],[8,[39,10],null,[["@text"],["Local"]],null],[1,"\\n"]],[]],null],[1," "],[13],[1,"\\n"]],[]],null],[1," "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[5]]]]],[1,"\\n"]],[]],[[[1," "],[8,[30,2,["Item"]],[[24,0,"consul-side-nav__datacenter"]],null,[["default"],[[[[1,"\\n "],[8,[39,11],null,[["@name","@color"],["server-cluster","var(--token-form-control-disabled-foreground-color)"]],null],[1,"\\n "],[8,[39,12],null,[["@size","@color"],["200","disabled"]],[["default"],[[[[1,[30,4,["Name"]]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]]]],[2]]]],["@list","SNL","@dcs","@dc","Selector","@nspace"],false,["let","if","gt","t","nav-selector","sort-by","eq","href-to","hash","or","hds/badge","flight-icon","hds/text/display"]]',moduleName:"consul-ui/components/consul/datacenter/selector/index.hbs",isStrictMode:!1}) let c=(o=class extends l.default{constructor(){var e,t,n,l super(...arguments),e=this,t="search",l=this,(n=a)&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}get filteredItems(){const e=this.search.toLowerCase() return this.args.dcs.filter((t=>t.Name.toLowerCase().includes(e)))}onSearchInput(e){this.search=e.target.value}},a=u(o.prototype,"search",[i.tracked],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return""}}),u(o.prototype,"onSearchInput",[r.action],Object.getOwnPropertyDescriptor(o.prototype,"onSearchInput"),o.prototype),o) @@ -836,14 +836,11 @@ var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/consul/external-source/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"uG5lBIhe",block:'[[[1,"\\n"],[41,[30,1],[[[44,[[28,[37,2],[[30,1]],null]],[[[41,[28,[37,3],[[30,3],[28,[37,4],[[30,2],"consul-api-gateway"],null]],null],[[[1," "],[10,"dl"],[14,0,"tooltip-panel"],[12],[1,"\\n "],[10,"dt"],[12],[1,"\\n "],[11,1],[24,0,"consul-external-source"],[17,4],[12],[1,"\\n "],[8,[39,5],[[24,0,"mr-1.5 w-4 h-4"]],[["@name"],[[28,[37,6],[[30,2]],null]]],null],[1,"\\n Registered via "],[1,[28,[35,7],[[28,[37,8],["common.brand.",[30,2]],null]],null]],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "],[10,"dd"],[12],[1,"\\n "],[8,[39,9],null,[["@position","@menu"],["left",false]],[["default"],[[[[1,"\\n "],[8,[39,10],null,[["@name"],["header"]],[["default"],[[[[1,"\\n API Gateways manage north-south traffic from external services to services in the Datacenter. For more information, read our documentation.\\n "]],[]]]]],[1,"\\n "],[8,[39,10],null,[["@name"],["menu"]],[["default"],[[[[1,"\\n "],[10,"li"],[14,"role","separator"],[12],[1,"\\n About "],[1,[28,[35,7],[[28,[37,8],["common.brand.",[30,2]],null]],null]],[1,"\\n "],[13],[1,"\\n "],[10,"li"],[14,"role","none"],[14,0,"learn-link"],[12],[1,"\\n "],[10,3],[14,"tabindex","-1"],[14,"role","menuitem"],[15,6,[28,[37,8],[[28,[37,11],["CONSUL_DOCS_LEARN_URL"],null]],null]],[14,"rel","noopener noreferrer"],[14,"target","_blank"],[12],[1,"\\n Learn guides\\n "],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n"]],[]],[[[41,[30,2],[[[1," "],[11,1],[24,0,"consul-external-source"],[17,4],[12],[1,"\\n "],[8,[39,5],[[24,0,"mr-1.5 h-4 w-4"]],[["@name","@color"],[[28,[37,6],[[30,2]],null],"var(--token-color-hashicorp-brand)"]],null],[1,"\\n"],[41,[30,5],[[[1," "],[1,[30,5]],[1,"\\n"]],[]],[[[1," Registered via "],[1,[28,[35,7],[[28,[37,8],["common.brand.",[30,2]],null]],null]],[1,"\\n"]],[]]],[1," "],[13],[1,"\\n "]],[]],null]],[]]]],[2]]]],[]],null]],["@item","externalSource","@withInfo","&attrs","@label"],false,["if","let","service/external-source","and","eq","flight-icon","icon-mapping","t","concat","menu-panel","block-slot","env"]]',moduleName:"consul-ui/components/consul/external-source/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) -e.default=i})),define("consul-ui/components/consul/hcp/home/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -const r=(0,n.createTemplateFactory)({id:"HbjP3hiy",block:'[[[1,"\\n"],[44,[[30,1],[28,[37,1],["CONSUL_HCP_URL"],null]],[[[1," "],[1,[54,[[30,3]]]],[1,"\\n"],[41,[28,[37,4],[[30,2],[30,3]],null],[[[1," "],[8,[30,2,["BackLink"]],null,[["@text","@href","@isHrefExternal"],[[28,[37,5],["components.hashicorp-consul.side-nav.hcp"],null],[30,3],true]],null],[1,"\\n"]],[]],null]],[2,3]]]],["@list","SNL","hcpUrl"],false,["let","env","log","if","and","t"]]',moduleName:"consul-ui/components/consul/hcp/home/index.hbs",isStrictMode:!1}) -var i=(0,t.setComponentTemplate)(r,(0,l.default)()) -e.default=i})) -define("consul-ui/components/consul/health-check/list/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=i})),define("consul-ui/components/consul/health-check/list/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"1WWZf50M",block:'[[[1,"\\n"],[11,0],[24,0,"consul-health-check-list"],[17,1],[12],[1,"\\n "],[10,"ul"],[12],[1,"\\n"],[42,[28,[37,1],[[28,[37,1],[[30,2]],null]],null],null,[[[1," "],[10,"li"],[15,0,[28,[37,2],["health-check-output ",[30,3,["Status"]]],null]],[12],[1,"\\n "],[10,0],[12],[1,"\\n "],[10,"header"],[12],[1,"\\n "],[10,"h2"],[12],[1,[30,3,["Name"]]],[13],[1,"\\n "],[13],[1,"\\n "],[10,"dl"],[12],[1,"\\n"],[41,[28,[37,4],[[30,3,["Kind"]],"node"],null],[[[1," "],[10,"dt"],[12],[1,"NodeName"],[13],[1,"\\n "],[10,"dd"],[12],[1,[30,3,["Node"]]],[13],[1,"\\n"]],[]],[[[1," "],[10,"dt"],[12],[1,"ServiceName"],[13],[1,"\\n "],[10,"dd"],[12],[1,[30,3,["ServiceName"]]],[13],[1,"\\n"]],[]]],[1," "],[13],[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,"CheckID"],[13],[1,"\\n "],[10,"dd"],[12],[1,[28,[35,5],[[30,3,["CheckID"]],"-"],null]],[13],[1,"\\n "],[13],[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,"Type"],[13],[1,"\\n "],[10,"dd"],[14,"data-health-check-type",""],[12],[1,"\\n "],[1,[30,3,["Type"]]],[1,"\\n"],[41,[30,3,["Exposed"]],[[[1," "],[11,"em"],[4,[38,6],["Expose.checks is set to true, so all registered HTTP and gRPC check paths are exposed through Envoy for the Consul agent."],null],[12],[1,"Exposed"],[13],[1,"\\n"]],[]],null],[1," "],[13],[1,"\\n "],[13],[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,"Notes"],[13],[1,"\\n "],[10,"dd"],[12],[1,[28,[35,5],[[30,3,["Notes"]],"-"],null]],[13],[1,"\\n "],[13],[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,"Output"],[13],[1,"\\n "],[10,"dd"],[12],[1,"\\n "],[10,"pre"],[12],[10,"code"],[12],[1,[30,3,["Output"]]],[13],[13],[1,"\\n "],[8,[39,7],null,[["@value","@name"],[[30,3,["Output"]],"output"]],null],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n"]],[3]],null],[1," "],[13],[1,"\\n"],[13],[1,"\\n"]],["&attrs","@items","item"],false,["each","-track-array","concat","if","eq","or","tooltip","consul-copy-button"]]',moduleName:"consul-ui/components/consul/health-check/list/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) -e.default=i})),define("consul-ui/components/consul/health-check/search-bar/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=i})) +define("consul-ui/components/consul/health-check/search-bar/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"vj3AOTz9",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"consul-healthcheck-search-bar"],[17,1]],[["@filter"],[[30,2]]],[["status","search","filter","sort"],[[[[1,"\\n\\n"],[44,[[28,[37,2],[[28,[37,3],["components.consul.health-check.search-bar.",[30,3,["status","key"]],".name"],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","key"]]],null],[28,[37,3],["common.consul.",[30,3,["status","key"]]],null]],null]]]],[28,[37,2],[[28,[37,3],["components.consul.health-check.search-bar.",[30,3,["status","key"]],".options.",[30,3,["status","value"]]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","value"]]],null],[28,[37,3],["common.consul.",[30,3,["status","value"]]],null]],null]]]]],[[[1," "],[8,[30,3,["RemoveFilter"]],[[16,"aria-label",[28,[37,2],["common.ui.remove"],[["item"],[[28,[37,3],[[30,4]," ",[30,5]],null]]]]]],null,[["default"],[[[[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,[30,4]],[13],[1,"\\n "],[10,"dd"],[12],[1,[30,5]],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n"]],[4,5]]],[1,"\\n "]],[3]],[[[1,"\\n "],[8,[30,6,["Search"]],null,[["@onsearch","@value","@placeholder"],[[28,[37,5],[[30,0],[30,7]],null],[30,8],[28,[37,2],["common.search.search"],null]]],[["default"],[[[[1,"\\n"],[41,[30,2,["searchproperty"]],[[[1," "],[8,[30,6,["Select"]],[[24,0,"type-search-properties"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,5],[[30,0],[30,2,["searchproperty","change"]]],null],true,true]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.search.searchproperty"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,9,["Optgroup"]],[30,9,["Option"]]],[[[42,[28,[37,9],[[28,[37,9],[[30,2,["searchproperty","default"]]],null]],null],null,[[[1," "],[8,[30,11],null,[["@value","@selected"],[[30,12],[28,[37,10],[[30,12],[30,2,["searchproperty","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.consul.",[28,[37,11],[[30,12]],null]],null]],null]],[1,"\\n "]],[]]]]],[1,"\\n"]],[12]],null]],[10,11]]],[1," "]],[]]]]],[1,"\\n "]],[9]]]]],[1,"\\n"]],[]],null],[1," "]],[]]]]],[1,"\\n "]],[6]],[[[1,"\\n "],[8,[30,13,["Select"]],[[24,0,"type-status"]],[["@position","@onchange","@multiple"],["left",[28,[37,5],[[30,0],[30,2,["status","change"]]],null],true]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.consul.status"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,14,["Optgroup"]],[30,14,["Option"]]],[[[42,[28,[37,9],[[28,[37,9],[[28,[37,4],["passing","warning","critical","empty"],null]],null]],null],null,[[[1," "],[8,[30,16],[[16,0,[29,["value-",[30,17]]]]],[["@value","@selected"],[[30,17],[28,[37,10],[[30,17],[30,2,["status","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.consul.",[30,17]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,17]],null]],null]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[17]],null]],[15,16]]],[1," "]],[]]]]],[1,"\\n "]],[14]]]]],[1,"\\n"],[41,[30,2,["kind"]],[[[1," "],[8,[30,13,["Select"]],[[24,0,"type-kind"]],[["@position","@onchange","@multiple"],["left",[28,[37,5],[[30,0],[30,2,["kind","change"]]],null],true]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["components.consul.health-check.search-bar.kind.name"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,18,["Optgroup"]],[30,18,["Option"]]],[[[42,[28,[37,9],[[28,[37,9],[[28,[37,4],["service","node"],null]],null]],null],null,[[[1," "],[8,[30,20],null,[["@value","@selected"],[[30,21],[28,[37,10],[[30,21],[30,2,["kind","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["components.consul.health-check.search-bar.kind.options.",[30,21]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,21]],null]],null]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[21]],null]],[19,20]]],[1," "]],[]]]]],[1,"\\n "]],[18]]]]],[1,"\\n"]],[]],null],[1," "],[8,[30,13,["Select"]],[[24,0,"type-check"]],[["@position","@onchange","@multiple"],["left",[28,[37,5],[[30,0],[30,2,["check","change"]]],null],true]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["components.consul.health-check.search-bar.check.name"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,22,["Optgroup"]],[30,22,["Option"]]],[[[42,[28,[37,9],[[28,[37,9],[[28,[37,4],["alias","docker","grpc","http","script","serf","tcp","ttl"],null]],null]],null],null,[[[1," "],[8,[30,24],null,[["@value","@selected"],[[30,25],[28,[37,10],[[30,25],[30,2,["check","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["components.consul.health-check.search-bar.check.options.",[30,25]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,25]],null]],null]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[25]],null]],[23,24]]],[1," "]],[]]]]],[1,"\\n "]],[22]]]]],[1,"\\n "]],[13]],[[[1,"\\n "],[8,[30,26,["Select"]],[[24,0,"type-sort"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,5],[[30,0],[30,27,["change"]]],null],false,true]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n"],[44,[[28,[37,12],[[28,[37,4],[[28,[37,4],["Name:asc",[28,[37,2],["common.sort.alpha.asc"],null]],null],[28,[37,4],["Name:desc",[28,[37,2],["common.sort.alpha.desc"],null]],null],[28,[37,4],["Status:asc",[28,[37,2],["common.sort.status.asc"],null]],null],[28,[37,4],["Status:desc",[28,[37,2],["common.sort.status.desc"],null]],null],[28,[37,4],["Kind:asc",[28,[37,2],["components.consul.health-check.search-bar.sort.kind.asc"],null]],null],[28,[37,4],["Kind:desc",[28,[37,2],["components.consul.health-check.search-bar.sort.kind.desc"],null]],null]],null]],null]],[[[1," "],[1,[28,[35,13],[[30,29],[30,27,["value"]]],null]],[1,"\\n"]],[29]]],[1," "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,28,["Optgroup"]],[30,28,["Option"]]],[[[1," "],[8,[30,30],null,[["@label"],[[28,[37,2],["common.consul.status"],null]]],[["default"],[[[[1,"\\n "],[8,[30,31],null,[["@value","@selected"],["Status:asc",[28,[37,14],["Status:asc",[30,27,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.status.asc"],null]]],[]]]]],[1,"\\n "],[8,[30,31],null,[["@value","@selected"],["Status:desc",[28,[37,14],["Status:desc",[30,27,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.status.desc"],null]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,30],null,[["@label"],[[28,[37,2],["components.consul.health-check.search-bar.sort.name.name"],null]]],[["default"],[[[[1,"\\n "],[8,[30,31],null,[["@value","@selected"],["Name:asc",[28,[37,14],["Name:asc",[30,27,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.asc"],null]]],[]]]]],[1,"\\n "],[8,[30,31],null,[["@value","@selected"],["Name:desc",[28,[37,14],["Name:desc",[30,27,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.desc"],null]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,30],null,[["@label"],[[28,[37,2],["components.consul.health-check.search-bar.sort.kind.name"],null]]],[["default"],[[[[1,"\\n "],[8,[30,31],null,[["@value","@selected"],["Kind:asc",[28,[37,14],["Kind:asc",[30,27]],null]]],[["default"],[[[[1,"Service to Node"]],[]]]]],[1,"\\n "],[8,[30,31],null,[["@value","@selected"],["Kind:desc",[28,[37,14],["Kind:desc",[30,27]],null]]],[["default"],[[[[1,"Node to Service"]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[30,31]]],[1," "]],[]]]]],[1,"\\n "]],[28]]]]],[1,"\\n "]],[26]]]]]],["&attrs","@filter","search","key","value","search","@onsearch","@search","components","Optgroup","Option","prop","search","components","Optgroup","Option","state","components","Optgroup","Option","item","components","Optgroup","Option","item","search","@sort","components","selectable","Optgroup","Option"],false,["search-bar","let","t","concat","array","action","if","block-slot","each","-track-array","includes","lowercase","from-entries","get","eq"]]',moduleName:"consul-ui/components/consul/health-check/search-bar/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/consul/instance-checks/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 @@ -988,11 +985,11 @@ var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/consul/node/peer-info/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"ufFBkLZx",block:'[[[1,"\\n"],[41,[30,1,["PeerName"]],[[[1," "],[10,1],[14,0,"consul-node-peer-info"],[12],[1,"\\n "],[11,"svg"],[24,"width","16"],[24,"height","16"],[24,"viewBox","0 0 16 16"],[24,"fill","none"],[24,"xmlns","http://www.w3.org/2000/svg","http://www.w3.org/2000/xmlns/"],[4,[38,1],["Peer"],null],[12],[1,"\\n "],[10,"path"],[14,"d","M16 8C16 7.80109 15.921 7.61032 15.7803 7.46967L12.2803 3.96967C11.9874 3.67678 11.5126 3.67678 11.2197 3.96967C10.9268 4.26256 10.9268 4.73744 11.2197 5.03033L14.1893 8L11.2197 10.9697C10.9268 11.2626 10.9268 11.7374 11.2197 12.0303C11.5126 12.3232 11.9874 12.3232 12.2803 12.0303L15.7803 8.53033C15.921 8.38968 16 8.19891 16 8Z"],[14,"fill","#77838A"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M0.21967 8.53033C-0.0732233 8.23744 -0.0732233 7.76256 0.21967 7.46967L3.71967 3.96967C4.01256 3.67678 4.48744 3.67678 4.78033 3.96967C5.07322 4.26256 5.07322 4.73744 4.78033 5.03033L1.81066 8L4.78033 10.9697C5.07322 11.2626 5.07322 11.7374 4.78033 12.0303C4.48744 12.3232 4.01256 12.3232 3.71967 12.0303L0.21967 8.53033Z"],[14,"fill","#77838A"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M5 7C4.44772 7 4 7.44772 4 8C4 8.55229 4.44772 9 5 9H5.01C5.56228 9 6.01 8.55229 6.01 8C6.01 7.44772 5.56228 7 5.01 7H5Z"],[14,"fill","#77838A"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M7 8C7 7.44772 7.44772 7 8 7H8.01C8.56228 7 9.01 7.44772 9.01 8C9.01 8.55229 8.56228 9 8.01 9H8C7.44772 9 7 8.55229 7 8Z"],[14,"fill","#77838A"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M11 7C10.4477 7 10 7.44772 10 8C10 8.55229 10.4477 9 11 9H11.01C11.5623 9 12.01 8.55229 12.01 8C12.01 7.44772 11.5623 7 11.01 7H11Z"],[14,"fill","#77838A"],[12],[13],[1,"\\n "],[13],[1,"\\n "],[10,1],[14,0,"consul-node-peer-info__name"],[12],[1,[30,1,["PeerName"]]],[13],[1,"\\n "],[13],[1,"\\n"]],[]],null]],["@item"],false,["if","tooltip"]]',moduleName:"consul-ui/components/consul/node/peer-info/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) -e.default=i})) -define("consul-ui/components/consul/node/search-bar/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=i})),define("consul-ui/components/consul/node/search-bar/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"XAC5AQJw",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"consul-node-search-bar"],[17,1]],[["@filter"],[[30,2]]],[["status","search","filter","sort"],[[[[1,"\\n\\n"],[44,[[28,[37,2],[[28,[37,3],["components.consul.node.search-bar.",[30,3,["status","key"]]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","key"]]],null],[28,[37,3],["common.consul.",[30,3,["status","key"]]],null]],null]]]],[52,[30,3,["status","value"]],[30,3,["status","value"]],[28,[37,2],[[28,[37,3],["components.consul.node.search-bar.",[30,3,["status","value"]]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","value"]]],null],[28,[37,3],["common.consul.",[30,3,["status","value"]]],null],[28,[37,3],["common.brand.",[30,3,["status","value"]]],null]],null]]]]]],[[[1," "],[8,[30,3,["RemoveFilter"]],[[16,"aria-label",[28,[37,2],["common.ui.remove"],[["item"],[[28,[37,3],[[30,4]," ",[30,5]],null]]]]]],null,[["default"],[[[[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,[30,4]],[13],[1,"\\n "],[10,"dd"],[12],[1,[30,5]],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n"]],[4,5]]],[1,"\\n "]],[3]],[[[1,"\\n "],[8,[30,6,["Search"]],null,[["@onsearch","@value","@placeholder"],[[28,[37,6],[[30,0],[30,7]],null],[30,8],[28,[37,2],["common.search.search"],null]]],[["default"],[[[[1,"\\n "],[8,[30,6,["Select"]],[[24,0,"type-search-properties"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,6],[[30,0],[30,2,["searchproperty","change"]]],null],true,true]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.search.searchproperty"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,9,["Optgroup"]],[30,9,["Option"]]],[[[42,[28,[37,9],[[28,[37,9],[[30,2,["searchproperty","default"]]],null]],null],null,[[[1," "],[8,[30,11],null,[["@value","@selected"],[[30,12],[28,[37,10],[[30,12],[30,2,["searchproperty","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.consul.",[28,[37,11],[[30,12]],null]],null]],null]],[1,"\\n "]],[]]]]],[1,"\\n"]],[12]],null]],[10,11]]],[1," "]],[]]]]],[1,"\\n "]],[9]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[6]],[[[1,"\\n "],[8,[30,13,["Select"]],[[24,0,"type-status"]],[["@position","@onchange","@multiple"],["left",[28,[37,6],[[30,0],[30,2,["status","change"]]],null],true]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.consul.status"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,14,["Optgroup"]],[30,14,["Option"]]],[[[42,[28,[37,9],[[28,[37,9],[[28,[37,4],["passing","warning","critical"],null]],null]],null],null,[[[1," "],[8,[30,16],[[16,0,[29,["value-",[30,17]]]]],[["@value","@selected"],[[30,17],[28,[37,10],[[30,17],[30,2,["status","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.consul.",[30,17]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,17]],null]],null]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[17]],null]],[15,16]]],[1," "]],[]]]]],[1,"\\n "]],[14]]]]],[1,"\\n "],[8,[30,13,["Select"]],[[24,0,"type-version"]],[["@position","@onchange","@multiple"],["left",[28,[37,6],[[30,0],[30,2,["version","change"]]],null],true]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.consul.version"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,18,["Optgroup"]],[30,18,["Option"]]],[[[42,[28,[37,9],[[28,[37,9],[[30,21]],null]],null],null,[[[1," "],[8,[30,20],null,[["@value","@selected"],[[30,22],[28,[37,10],[[30,22],[30,2,["version","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,3],[[30,22],".x"],null]],[1,"\\n "]],[]]]]],[1,"\\n"]],[22]],null]],[19,20]]],[1," "]],[]]]]],[1,"\\n "]],[18]]]]],[1,"\\n "]],[13]],[[[1,"\\n "],[8,[30,23,["Select"]],[[24,0,"type-sort"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,6],[[30,0],[30,24,["change"]]],null],false,true]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n"],[44,[[28,[37,12],[[28,[37,4],[[28,[37,4],["Node:asc",[28,[37,2],["common.sort.alpha.asc"],null]],null],[28,[37,4],["Node:desc",[28,[37,2],["common.sort.alpha.desc"],null]],null],[28,[37,4],["Status:asc",[28,[37,2],["common.sort.status.asc"],null]],null],[28,[37,4],["Status:desc",[28,[37,2],["common.sort.status.desc"],null]],null],[28,[37,4],["Version:asc",[28,[37,2],["common.sort.version.asc"],null]],null],[28,[37,4],["Version:desc",[28,[37,2],["common.sort.version.desc"],null]],null]],null]],null]],[[[1," "],[1,[28,[35,13],[[30,26],[30,24,["value"]]],null]],[1,"\\n"]],[26]]],[1," "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,25,["Optgroup"]],[30,25,["Option"]]],[[[1," "],[8,[30,27],null,[["@label"],[[28,[37,2],["common.consul.status"],null]]],[["default"],[[[[1,"\\n "],[8,[30,28],null,[["@value","@selected"],["Status:asc",[28,[37,14],["Status:asc",[30,24,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.status.asc"],null]]],[]]]]],[1,"\\n "],[8,[30,28],null,[["@value","@selected"],["Status:desc",[28,[37,14],["Status:desc",[30,24,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.status.desc"],null]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,27],null,[["@label"],[[28,[37,2],["common.consul.node-name"],null]]],[["default"],[[[[1,"\\n "],[8,[30,28],null,[["@value","@selected"],["Node:asc",[28,[37,14],["Node:asc",[30,24,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.asc"],null]]],[]]]]],[1,"\\n "],[8,[30,28],null,[["@value","@selected"],["Node:desc",[28,[37,14],["Node:desc",[30,24,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.desc"],null]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,27],null,[["@label"],[[28,[37,2],["common.consul.version"],null]]],[["default"],[[[[1,"\\n "],[8,[30,28],null,[["@value","@selected"],["Version:asc",[28,[37,14],["Version:asc",[30,24,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.version.asc"],null]]],[]]]]],[1,"\\n "],[8,[30,28],null,[["@value","@selected"],["Version:desc",[28,[37,14],["Version:desc",[30,24,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.version.desc"],null]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[27,28]]],[1," "]],[]]]]],[1,"\\n "]],[25]]]]],[1,"\\n "]],[23]]]]]],["&attrs","@filter","search","key","value","search","@onsearch","@search","components","Optgroup","Option","prop","search","components","Optgroup","Option","state","components","Optgroup","Option","@versions","version","search","@sort","components","selectable","Optgroup","Option"],false,["search-bar","let","t","concat","array","if","action","block-slot","each","-track-array","includes","lowercase","from-entries","get","eq"]]',moduleName:"consul-ui/components/consul/node/search-bar/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) -e.default=i})),define("consul-ui/components/consul/nspace/form/index",["exports","@ember/component","@ember/template-factory","@glimmer/component","@ember/object"],(function(e,t,n,l,r){var i +e.default=i})) +define("consul-ui/components/consul/nspace/form/index",["exports","@ember/component","@ember/template-factory","@glimmer/component","@ember/object"],(function(e,t,n,l,r){var i function o(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const a=(0,n.createTemplateFactory)({id:"0A+rYSOU",block:'[[[1,"\\n"],[11,0],[24,0,"consul-nspace-form"],[17,1],[12],[1,"\\n "],[8,[39,0],null,[["@sink","@type","@label","@ondelete","@onchange"],[[28,[37,1],["/${partition}/${nspace}/${dc}/nspace",[28,[37,2],null,[["partition","nspace","dc"],["","",[30,2,["Datacenter"]]]]]],null],"nspace","Namespace",[28,[37,3],[[30,0,["onDelete"]],[30,2]],null],[28,[37,3],[[30,0,["onSubmit"]],[30,2]],null]]],[["default"],[[[[1,"\\n "],[8,[39,4],null,[["@name"],["removed"]],[["default"],[[[[1,"\\n "],[8,[39,5],[[4,[38,6],null,[["after"],[[28,[37,7],[[30,0],[30,4]],null]]]]],[["@type"],["remove"]],null],[1,"\\n "]],[4]]]]],[1,"\\n\\n "],[8,[39,4],null,[["@name"],["content"]],[["default"],[[[[1,"\\n\\n"],[44,[[28,[37,9],[[28,[37,10],["write nspaces"],null]],null],[30,2],[28,[37,2],null,[["help","Name"],["Must be a valid DNS hostname. Must contain 1-64 characters (numbers, letters, and hyphens), and must begin with a letter. Once created, this cannot be changed.",[28,[37,11],[[28,[37,2],null,[["test","error"],["^[a-zA-Z0-9]([a-zA-Z0-9-]{0,62}[a-zA-Z0-9])?$","Name must be a valid DNS hostname."]]]],null]]]],[28,[37,2],null,[["Description"],[[28,[37,11],null,null]]]]],[[[1," "],[11,"form"],[4,[38,12],["submit",[28,[37,3],[[30,3,["persist"]],[30,6]],null]],null],[4,[38,13],[[30,5]],null],[12],[1,"\\n\\n "],[8,[39,14],null,[["@src"],[[28,[37,14],["validate"],null]]],[["default"],[[[[1,"\\n\\n "],[10,"fieldset"],[12],[1,"\\n"],[41,[28,[37,16],["new nspace"],[["item"],[[30,6]]]],[[[1," "],[8,[39,17],null,[["@name","@placeholder","@item","@validations","@chart"],["Name","Name",[30,6],[30,7],[28,[37,2],null,[["state","dispatch"],[[30,13],[30,12]]]]]],null],[1,"\\n"]],[]],null],[1," "],[8,[39,17],null,[["@expanded","@name","@label","@item","@validations","@chart"],[true,"Description","Description (Optional)",[30,6],[30,8],[28,[37,2],null,[["state","dispatch"],[[30,13],[30,12]]]]]],null],[1,"\\n "],[13],[1,"\\n"],[41,[28,[37,10],["use acls"],null],[[[1," "],[10,"fieldset"],[14,1,"roles"],[12],[1,"\\n "],[10,"h2"],[12],[1,"Roles"],[13],[1,"\\n "],[10,2],[12],[1,"\\n"],[41,[28,[37,10],["write nspace"],[["item"],[[30,6]]]],[[[1," By adding roles to this namespaces, you will apply them to\\n all tokens created within this namespace.\\n"]],[]],[[[1," The following roles are applied to all tokens created within\\n this namespace.\\n"]],[]]],[1," "],[13],[1,"\\n "],[8,[39,18],null,[["@dc","@nspace","@partition","@disabled","@items"],[[30,14],"default",[30,15],[30,5],[30,6,["ACLs","RoleDefaults"]]]],null],[1,"\\n "],[13],[1,"\\n "],[10,"fieldset"],[14,1,"policies"],[12],[1,"\\n "],[10,"h2"],[12],[1,"Policies"],[13],[1,"\\n "],[10,2],[12],[1,"\\n"],[41,[28,[37,9],[[30,5]],null],[[[1," By adding policies to this namespace, you will apply them to\\n all tokens created within this namespace.\\n"]],[]],[[[1," The following policies are applied to all tokens created\\n within this namespace.\\n"]],[]]],[1," "],[13],[1,"\\n "],[8,[39,19],null,[["@dc","@nspace","@partition","@disabled","@allowIdentity","@items"],[[30,14],"default",[30,15],[30,5],false,[30,6,["ACLs","PolicyDefaults"]]]],null],[1,"\\n "],[13],[1,"\\n"]],[]],null],[1," "],[10,0],[12],[1,"\\n "],[8,[39,20],null,null,[["default"],[[[[1,"\\n"],[41,[28,[37,21],[[28,[37,16],["new nspace"],[["item"],[[30,6]]]],[28,[37,10],["create nspaces"],null]],null],[[[1," "],[8,[39,22],[[16,"disabled",[28,[37,23],[[28,[37,16],["pristine nspace"],[["item"],[[30,6]]]],[28,[37,24],[[30,13],"error"],null]],null]],[24,4,"submit"]],[["@text"],["Save"]],null],[1,"\\n"]],[]],[[[41,[28,[37,10],["write nspace"],[["item"],[[30,6]]]],[[[1," "],[8,[39,22],[[24,4,"submit"]],[["@text"],["Save"]],null],[1,"\\n "]],[]],null]],[]]],[1,"\\n "],[8,[39,22],[[24,4,"reset"],[4,[38,12],["click",[28,[37,3],[[30,0,["onCancel"]],[30,6]],null]],null]],[["@color","@text"],["secondary","Cancel"]],null],[1,"\\n\\n"],[41,[28,[37,21],[[28,[37,9],[[28,[37,16],["new nspace"],[["item"],[[30,6]]]]],null],[28,[37,10],["delete nspace"],[["item"],[[30,6]]]]],null],[[[1," "],[8,[39,25],null,[["@message"],["Are you sure you want to delete this Namespace?"]],[["default"],[[[[1,"\\n "],[8,[39,4],null,[["@name"],["action"]],[["default"],[[[[1,"\\n "],[8,[39,22],[[4,[38,12],["click",[28,[37,3],[[30,16],[28,[37,3],[[30,3,["delete"]],[30,6]],null]],null]],null]],[["@color","@text"],["critical","Delete"]],null],[1,"\\n "]],[16]]]]],[1,"\\n "],[8,[39,4],null,[["@name"],["dialog"]],[["default"],[[[[1,"\\n "],[8,[39,26],null,[["@message","@execute","@cancel"],[[30,19],[30,17],[30,18]]],null],[1,"\\n "]],[17,18,19]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],null],[1," "]],[]]]]],[1,"\\n "],[13],[1,"\\n "]],[9,10,11,12,13]]]]],[1,"\\n "],[13],[1,"\\n"]],[5,6,7,8]]],[1," "]],[]]]]],[1,"\\n "]],[3]]]]],[1,"\\n"],[13]],["&attrs","@item","writer","after","readOnly","item","Name","Description","State","Guard","ChartAction","dispatch","state","@dc","@partition","confirm","execute","cancel","message"],false,["data-writer","uri","hash","fn","block-slot","consul/nspace/notifications","notification","action","let","not","can","array","on","disabled","state-chart","if","is","text-input","role-selector","policy-selector","hds/button-set","and","hds/button","or","state-matches","confirmation-dialog","delete-confirmation"]]',moduleName:"consul-ui/components/consul/nspace/form/index.hbs",isStrictMode:!1}) @@ -1010,7 +1007,7 @@ e.default=i})),define("consul-ui/components/consul/nspace/search-bar/index",["ex const r=(0,n.createTemplateFactory)({id:"DelkwCZ4",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"consul-nspace-search-bar"],[17,1]],[["@filter"],[[30,2]]],[["status","search","sort"],[[[[1,"\\n\\n"],[44,[[28,[37,2],[[28,[37,3],["components.consul.nspace.search-bar.",[30,3,["status","key"]]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","key"]]],null],[28,[37,3],["common.consul.",[30,3,["status","key"]]],null]],null]]]],[28,[37,2],[[28,[37,3],["components.consul.nspace.search-bar.",[30,3,["status","value"]]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","value"]]],null],[28,[37,3],["common.consul.",[30,3,["status","value"]]],null],[28,[37,3],["common.brand.",[30,3,["status","value"]]],null]],null]]]]],[[[1," "],[8,[30,3,["RemoveFilter"]],[[16,"aria-label",[28,[37,2],["common.ui.remove"],[["item"],[[28,[37,3],[[30,4]," ",[30,5]],null]]]]]],null,[["default"],[[[[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,[30,4]],[13],[1,"\\n "],[10,"dd"],[12],[1,[30,5]],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n"]],[4,5]]],[1,"\\n "]],[3]],[[[1,"\\n "],[8,[30,6,["Search"]],null,[["@onsearch","@value","@placeholder"],[[28,[37,5],[[30,0],[30,7]],null],[30,8],[28,[37,2],["common.search.search"],null]]],[["default"],[[[[1,"\\n "],[8,[30,6,["Select"]],[[24,0,"type-search-properties"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,5],[[30,0],[30,2,["searchproperty","change"]]],null],true,true]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.search.searchproperty"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,9,["Optgroup"]],[30,9,["Option"]]],[[[42,[28,[37,8],[[28,[37,8],[[30,2,["searchproperty","default"]]],null]],null],null,[[[1," "],[8,[30,11],null,[["@value","@selected"],[[30,12],[28,[37,9],[[30,12],[30,2,["searchproperty","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.consul.",[28,[37,10],[[30,12]],null]],null]],null]],[1,"\\n "]],[]]]]],[1,"\\n"]],[12]],null]],[10,11]]],[1," "]],[]]]]],[1,"\\n "]],[9]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[6]],[[[1,"\\n "],[8,[30,13,["Select"]],[[24,0,"type-sort"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,5],[[30,0],[30,14,["change"]]],null],false,true]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n"],[44,[[28,[37,11],[[28,[37,4],[[28,[37,4],["Name:asc",[28,[37,2],["common.sort.alpha.asc"],null]],null],[28,[37,4],["Name:desc",[28,[37,2],["common.sort.alpha.desc"],null]],null]],null]],null]],[[[1," "],[1,[28,[35,12],[[30,16],[30,14,["value"]]],null]],[1,"\\n"]],[16]]],[1," "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,15,["Optgroup"]],[30,15,["Option"]]],[[[1," "],[8,[30,17],null,[["@label"],[[28,[37,2],["common.consul.name"],null]]],[["default"],[[[[1,"\\n "],[8,[30,18],null,[["@value","@selected"],["Name:asc",[28,[37,13],["Name:asc",[30,14,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.asc"],null]]],[]]]]],[1,"\\n "],[8,[30,18],null,[["@value","@selected"],["Name:desc",[28,[37,13],["Name:desc",[30,14,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.desc"],null]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[17,18]]],[1," "]],[]]]]],[1,"\\n "]],[15]]]]],[1,"\\n "]],[13]]]]]],["&attrs","@filter","search","key","value","search","@onsearch","@search","components","Optgroup","Option","prop","search","@sort","components","selectable","Optgroup","Option"],false,["search-bar","let","t","concat","array","action","block-slot","each","-track-array","includes","lowercase","from-entries","get","eq"]]',moduleName:"consul-ui/components/consul/nspace/search-bar/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/consul/nspace/selector/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -const r=(0,n.createTemplateFactory)({id:"5UTTzYK/",block:'[[[1,"\\n"],[41,[28,[37,1],[[28,[37,2],["use nspaces"],null],[28,[37,2],["choose nspaces"],null]],null],[[[1," "],[8,[39,3],null,[["@src","@onchange"],[[28,[37,4],["/${partition}/*/${dc}/namespaces",[28,[37,5],null,[["partition","dc"],[[30,1],[30,2,["Name"]]]]]],null],[28,[37,6],[[28,[37,7],[[30,3]],null]],null]]],null],[1,"\\n"],[44,[[30,4],[52,[30,5],[28,[37,5],null,[["Name"],[[30,5]]]],[28,[37,5],null,[["Name"],["default"]]]],[28,[37,9],["dc.nspaces",[30,2,["Name"]]],null]],[[[1," "],[8,[30,6,["Title"]],[[24,0,"consul-side-nav__selector-title"]],null,[["default"],[[[[1,[28,[35,10],["components.hashicorp-consul.side-nav.nspaces.title"],null]]],[]]]]],[1,"\\n "],[8,[39,11],null,[["@list","@items","@item","@key","@icon","@placeholder","@footerLink","@footerLinkText"],[[30,4],[28,[37,12],["Name:asc",[28,[37,13],["DeletedAt",[30,9]],null]],null],[30,7],"Name","folder",[28,[37,10],["components.hashicorp-consul.side-nav.nspaces.placeholder"],null],[28,[37,14],["dc.nspaces",[30,2,["Name"]]],null],[28,[37,10],["components.hashicorp-consul.side-nav.nspaces.footer"],null]]],[["default"],[[[[1,"\\n "],[8,[30,10,["Checkmark"]],null,[["@selected","@href","@isHrefExternal"],[[28,[37,15],[[30,7,["Name"]],[30,11,["Name"]]],null],[52,[30,8],[28,[37,14],["dc.services.index"],[["params"],[[28,[37,5],null,[["partition","nspace","dc"],[[52,[28,[37,16],[[30,1,["length"]],0],null],[30,1],[27]],[30,11,["Name"]],[30,2,["Name"]]]]]]]],[28,[37,14],["."],[["params"],[[28,[37,5],null,[["partition","nspace"],[[52,[28,[37,16],[[30,1,["length"]],0],null],[30,1],[27]],[30,11,["Name"]]]]]]]]],false]],[["default"],[[[[1,"\\n "],[1,[30,11,["Name"]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[10,11]]]]],[1,"\\n"]],[6,7,8]]]],[]],null]],["@partition","@dc","@onchange","@list","@nspace","SNL","nspace","isManaging","@nspaces","Dropdown","item"],false,["if","and","can","data-source","uri","hash","fn","optional","let","is-href","t","nav-selector","sort-by","reject-by","href-to","eq","gt"]]',moduleName:"consul-ui/components/consul/nspace/selector/index.hbs",isStrictMode:!1}) +const r=(0,n.createTemplateFactory)({id:"ZHMEj55d",block:'[[[1,"\\n"],[41,[28,[37,1],[[28,[37,2],["use nspaces"],null],[28,[37,2],["choose nspaces"],null]],null],[[[44,[[30,1],[52,[30,2],[28,[37,4],null,[["Name"],[[30,2]]]],[28,[37,4],null,[["Name"],["default"]]]],[28,[37,5],["dc.nspaces",[30,3,["Name"]]],null]],[[[1," "],[8,[30,4,["Title"]],[[24,0,"consul-side-nav__selector-title"]],null,[["default"],[[[[1,[28,[35,6],["components.hashicorp-consul.side-nav.nspaces.title"],null]]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@list","@items","@item","@key","@icon","@placeholder","@footerLink","@footerLinkText"],[[30,1],[28,[37,8],["Name:asc",[28,[37,9],["DeletedAt",[30,7]],null]],null],[30,5],"Name","folder",[28,[37,6],["components.hashicorp-consul.side-nav.nspaces.placeholder"],null],[28,[37,10],["dc.nspaces",[30,3,["Name"]]],null],[28,[37,6],["components.hashicorp-consul.side-nav.nspaces.footer"],null]]],[["default"],[[[[1,"\\n "],[8,[30,8,["Data"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,11],null,[["@src","@loading","@onchange"],[[28,[37,12],["/${partition}/*/${dc}/namespaces",[28,[37,4],null,[["partition","dc"],[[30,9],[30,3,["Name"]]]]]],null],"lazy",[28,[37,13],[[28,[37,14],[[30,10]],null]],null]]],null],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,8,["Dropdown","Checkmark"]],null,[["@selected","@href","@isHrefExternal"],[[28,[37,15],[[30,5,["Name"]],[30,8,["item","Name"]]],null],[28,[37,10],["dc.services.index"],[["params"],[[28,[37,4],null,[["partition","nspace","peer","dc"],[[52,[28,[37,16],[[30,9,["length"]],0],null],[30,9],[27]],[30,8,["item","Name"]],[27],[30,3,["Name"]]]]]]]],false]],[["default"],[[[[1,"\\n "],[1,[30,8,["item","Name"]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[8]]]]],[1,"\\n"]],[4,5,6]]]],[]],null]],["@list","@nspace","@dc","SNL","nspace","isManaging","@nspaces","Selector","@partition","@onchange"],false,["if","and","can","let","hash","is-href","t","nav-selector","sort-by","reject-by","href-to","data-source","uri","fn","optional","eq","gt"]]',moduleName:"consul-ui/components/consul/nspace/selector/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/consul/partition/form/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"IFfwt6JG",block:'[[[1,"\\n"],[11,0],[24,0,"consul-partition-form"],[17,1],[12],[1,"\\n "],[8,[39,0],null,[["@sink","@type","@label","@ondelete","@onchange"],[[28,[37,1],["/${partition}/${nspace}/${dc}/partition",[28,[37,2],null,[["partition","nspace","dc"],["","",[30,2,["Datacenter"]]]]]],null],"partition","Partition",[28,[37,3],[[52,[30,3],[30,3],[30,4]],[30,2]],null],[28,[37,3],[[28,[37,5],[[30,4]],null],[30,2]],null]]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["removed"]],[["default"],[[[[1,"\\n "],[8,[39,7],[[4,[38,8],null,[["after"],[[28,[37,9],[[30,0],[30,6]],null]]]]],[["@type"],["remove"]],null],[1,"\\n "]],[6]]]]],[1,"\\n\\n "],[8,[39,6],null,[["@name"],["content"]],[["default"],[[[[1,"\\n\\n"],[44,[[28,[37,11],[[28,[37,12],["write partition"],null]],null],[30,2],[28,[37,2],null,[["help","Name"],["Must be a valid DNS hostname. Must contain 1-64 characters (numbers, letters, and hyphens), and must begin with a letter. Once created, this cannot be changed.",[28,[37,13],[[28,[37,2],null,[["test","error"],["^[a-zA-Z0-9]([a-zA-Z0-9-]{0,62}[a-zA-Z0-9])?$","Name must be a valid DNS hostname."]]]],null]]]],[28,[37,2],null,[["Description"],[[28,[37,13],null,null]]]]],[[[11,"form"],[4,[38,14],["submit",[28,[37,3],[[30,5,["persist"]],[30,8]],null]],null],[4,[38,15],[[30,7]],null],[12],[1,"\\n\\n"],[8,[39,16],null,[["@src"],[[28,[37,16],["validate"],null]]],[["default"],[[[[1,"\\n\\n "],[10,"fieldset"],[12],[1,"\\n"],[41,[28,[37,17],["new partition"],[["item"],[[30,8]]]],[[[1," "],[8,[39,18],null,[["@name","@placeholder","@item","@validations","@chart"],["Name","Name",[30,8],[30,9],[28,[37,2],null,[["state","dispatch"],[[30,15],[30,14]]]]]],null],[1,"\\n"]],[]],null],[1," "],[8,[39,18],null,[["@expanded","@name","@label","@item","@validations","@chart"],[true,"Description","Description (Optional)",[30,8],[30,10],[28,[37,2],null,[["state","dispatch"],[[30,15],[30,14]]]]]],null],[1,"\\n "],[13],[1,"\\n\\n "],[10,0],[12],[1,"\\n "],[8,[39,19],null,null,[["default"],[[[[1,"\\n\\n\\n"],[41,[28,[37,20],[[28,[37,17],["new partition"],[["item"],[[30,8]]]],[28,[37,12],["create partitions"],null]],null],[[[1," "],[8,[39,21],[[16,"disabled",[28,[37,22],[[28,[37,17],["pristine partition"],[["item"],[[30,8]]]],[28,[37,23],[[30,15],"error"],null]],null]],[24,4,"submit"]],[["@text"],["Save"]],null],[1,"\\n"]],[]],[[[41,[28,[37,11],[[30,7]],null],[[[1," "],[8,[39,21],[[24,4,"submit"]],[["@text"],["Save"]],null],[1,"\\n"]],[]],null]],[]]],[1," "],[8,[39,21],[[24,4,"reset"],[4,[38,14],["click",[52,[30,16],[28,[37,3],[[28,[37,5],[[30,16],[30,8]],null]],null],[28,[37,3],[[28,[37,5],[[30,4],[30,8]],null]],null]]],null]],[["@text","@color"],["Cancel","secondary"]],null],[1,"\\n\\n"],[41,[28,[37,20],[[28,[37,11],[[28,[37,17],["new partition"],[["item"],[[30,8]]]]],null],[28,[37,12],["delete partition"],[["item"],[[30,8]]]]],null],[[[1," "],[8,[39,24],null,[["@message"],["Are you sure you want to delete this Partition?"]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["action"]],[["default"],[[[[1,"\\n "],[8,[39,21],[[4,[38,14],["click",[28,[37,3],[[30,17],[28,[37,3],[[30,5,["delete"]],[30,8]],null]],null]],null]],[["@text","@color"],["Delete","critical"]],null],[1,"\\n "]],[17]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["dialog"]],[["default"],[[[[1,"\\n "],[8,[39,25],null,[["@message","@execute","@cancel"],[[30,20],[30,18],[30,19]]],null],[1,"\\n "]],[18,19,20]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],null],[1," "]],[]]]]],[1,"\\n "],[13],[1,"\\n\\n"]],[11,12,13,14,15]]]]],[1,"\\n"],[13],[1,"\\n\\n"]],[7,8,9,10]]],[1," "]],[]]]]],[1,"\\n"]],[5]]]]],[1,"\\n"],[13],[1,"\\n"]],["&attrs","@item","@ondelete","@onsubmit","writer","after","readOnly","item","Name","Description","State","Guard","ChartAction","dispatch","state","@oncancel","confirm","execute","cancel","message"],false,["data-writer","uri","hash","fn","if","optional","block-slot","consul/partition/notifications","notification","action","let","not","can","array","on","disabled","state-chart","is","text-input","hds/button-set","and","hds/button","or","state-matches","confirmation-dialog","delete-confirmation"]]',moduleName:"consul-ui/components/consul/partition/form/index.hbs",isStrictMode:!1}) @@ -1025,7 +1022,7 @@ e.default=i})),define("consul-ui/components/consul/partition/search-bar/index",[ const r=(0,n.createTemplateFactory)({id:"IuHkFeus",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"consul-partition-search-bar"],[17,1]],[["@filter"],[[30,2]]],[["status","search","sort"],[[[[1,"\\n\\n"],[44,[[28,[37,2],[[28,[37,3],["components.consul.nspace.search-bar.",[30,3,["status","key"]]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","key"]]],null],[28,[37,3],["common.consul.",[30,3,["status","key"]]],null]],null]]]],[28,[37,2],[[28,[37,3],["components.consul.nspace.search-bar.",[30,3,["status","value"]]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","value"]]],null],[28,[37,3],["common.consul.",[30,3,["status","value"]]],null],[28,[37,3],["common.brand.",[30,3,["status","value"]]],null]],null]]]]],[[[1," "],[8,[30,3,["RemoveFilter"]],[[16,"aria-label",[28,[37,2],["common.ui.remove"],[["item"],[[28,[37,3],[[30,4]," ",[30,5]],null]]]]]],null,[["default"],[[[[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,[30,4]],[13],[1,"\\n "],[10,"dd"],[12],[1,[30,5]],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n"]],[4,5]]],[1,"\\n "]],[3]],[[[1,"\\n "],[8,[30,6,["Search"]],null,[["@onsearch","@value","@placeholder"],[[28,[37,5],[[30,0],[30,7]],null],[30,8],[28,[37,2],["common.search.search"],null]]],[["default"],[[[[1,"\\n "],[8,[30,6,["Select"]],[[24,0,"type-search-properties"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,5],[[30,0],[30,2,["searchproperty","change"]]],null],true,true]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.search.searchproperty"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,9,["Optgroup"]],[30,9,["Option"]]],[[[42,[28,[37,8],[[28,[37,8],[[30,2,["searchproperty","default"]]],null]],null],null,[[[1," "],[8,[30,11],null,[["@value","@selected"],[[30,12],[28,[37,9],[[30,12],[30,2,["searchproperty","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.consul.",[28,[37,10],[[30,12]],null]],null]],null]],[1,"\\n "]],[]]]]],[1,"\\n"]],[12]],null]],[10,11]]],[1," "]],[]]]]],[1,"\\n "]],[9]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[6]],[[[1,"\\n "],[8,[30,13,["Select"]],[[24,0,"type-sort"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,5],[[30,0],[30,14,["change"]]],null],false,true]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n"],[44,[[28,[37,11],[[28,[37,4],[[28,[37,4],["Name:asc",[28,[37,2],["common.sort.alpha.asc"],null]],null],[28,[37,4],["Name:desc",[28,[37,2],["common.sort.alpha.desc"],null]],null]],null]],null]],[[[1," "],[1,[28,[35,12],[[30,16],[30,14,["value"]]],null]],[1,"\\n"]],[16]]],[1," "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,15,["Optgroup"]],[30,15,["Option"]]],[[[1," "],[8,[30,17],null,[["@label"],[[28,[37,2],["common.consul.name"],null]]],[["default"],[[[[1,"\\n "],[8,[30,18],null,[["@value","@selected"],["Name:asc",[28,[37,13],["Name:asc",[30,14,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.asc"],null]]],[]]]]],[1,"\\n "],[8,[30,18],null,[["@value","@selected"],["Name:desc",[28,[37,13],["Name:desc",[30,14,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.desc"],null]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[17,18]]],[1," "]],[]]]]],[1,"\\n "]],[15]]]]],[1,"\\n "]],[13]]]]]],["&attrs","@filter","search","key","value","search","@onsearch","@search","components","Optgroup","Option","prop","search","@sort","components","selectable","Optgroup","Option"],false,["search-bar","let","t","concat","array","action","block-slot","each","-track-array","includes","lowercase","from-entries","get","eq"]]',moduleName:"consul-ui/components/consul/partition/search-bar/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/consul/partition/selector/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -const r=(0,n.createTemplateFactory)({id:"2skwmygl",block:'[[[1,"\\n"],[44,[[30,1],[52,[30,2],[28,[37,2],null,[["Name"],[[30,2]]]],[28,[37,2],null,[["Name"],["default"]]]],[28,[37,3],["dc.partitions",[30,3,["Name"]]],null],[28,[37,4],["choose partitions"],[["dc"],[[30,3]]]]],[[[1," "],[8,[39,5],null,[["@src","@onchange"],[[28,[37,6],["/*/*/${dc}/partitions",[28,[37,2],null,[["dc"],[[30,3,["Name"]]]]]],null],[28,[37,7],[[28,[37,8],[[30,8]],null]],null]]],null],[1,"\\n "],[8,[30,4,["Title"]],[[24,0,"consul-side-nav__selector-title"]],null,[["default"],[[[[1,[28,[35,9],["components.hashicorp-consul.side-nav.partitions.title"],null]]],[]]]]],[1,"\\n "],[8,[39,10],null,[["@list","@items","@item","@key","@icon","@placeholder","@footerLink","@footerLinkText","@disabled"],[[30,1],[28,[37,11],["Name:asc",[28,[37,12],["DeletedAt",[30,9]],null]],null],[30,5],"Name","users",[28,[37,9],["components.hashicorp-consul.side-nav.partitions.placeholder"],null],[28,[37,13],["dc.partitions",[30,3,["Name"]]],null],[28,[37,9],["components.hashicorp-consul.side-nav.partitions.footer"],null],[28,[37,14],[[30,7]],null]]],[["default"],[[[[1,"\\n"],[41,[30,7],[[[1," "],[8,[30,10,["Checkmark"]],null,[["@selected","@href","@isHrefExternal"],[[28,[37,15],[[30,5,["Name"]],[30,11,["Name"]]],null],[52,[30,11,["href"]],[30,11,["href"]],[52,[30,6],[28,[37,13],["dc.services.index"],[["params"],[[28,[37,2],null,[["partition","nspace","dc"],[[30,11,["Name"]],[27],[30,3,["Name"]]]]]]]],[28,[37,13],["."],[["params"],[[28,[37,2],null,[["partition","nspace"],[[30,11,["Name"]],[27]]]]]]]]],false]],[["default"],[[[[1,"\\n "],[1,[30,11,["Name"]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],null],[1," "]],[10,11]]]]],[1,"\\n"]],[4,5,6,7]]]],["@list","@partition","@dc","SNL","partition","isManaging","canChoose","@onchange","@partitions","Dropdown","item"],false,["let","if","hash","is-href","can","data-source","uri","fn","optional","t","nav-selector","sort-by","reject-by","href-to","not","eq"]]',moduleName:"consul-ui/components/consul/partition/selector/index.hbs",isStrictMode:!1}) +const r=(0,n.createTemplateFactory)({id:"kdZOlgGd",block:'[[[1,"\\n"],[44,[[30,1],[52,[30,2],[28,[37,2],null,[["Name"],[[30,2]]]],[28,[37,2],null,[["Name"],["default"]]]],[28,[37,3],["dc.partitions",[30,3,["Name"]]],null],[28,[37,4],["choose partitions"],[["dc"],[[30,3]]]]],[[[1," "],[8,[30,4,["Title"]],[[24,0,"consul-side-nav__selector-title"]],null,[["default"],[[[[1,[28,[35,5],["components.hashicorp-consul.side-nav.partitions.title"],null]]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@list","@items","@item","@key","@icon","@placeholder","@footerLink","@footerLinkText","@disabled"],[[30,1],[28,[37,7],["Name:asc",[28,[37,8],["DeletedAt",[30,8]],null]],null],[30,5],"Name","users",[28,[37,5],["components.hashicorp-consul.side-nav.partitions.placeholder"],null],[28,[37,9],["dc.partitions",[30,3,["Name"]]],null],[28,[37,5],["components.hashicorp-consul.side-nav.partitions.footer"],null],[28,[37,10],[[30,7]],null]]],[["default"],[[[[1,"\\n "],[8,[30,9,["Data"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,11],null,[["@src","@loading","@onchange"],[[28,[37,12],["/*/*/${dc}/partitions",[28,[37,2],null,[["dc"],[[30,3,["Name"]]]]]],null],"lazy",[28,[37,13],[[28,[37,14],[[30,10]],null]],null]]],null],[1,"\\n "]],[]]]]],[1,"\\n"],[41,[30,7],[[[1," "],[8,[30,9,["Dropdown","Checkmark"]],null,[["@selected","@href","@isHrefExternal"],[[28,[37,15],[[30,5,["Name"]],[30,9,["item","Name"]]],null],[52,[30,9,["item","href"]],[30,9,["item","href"]],[28,[37,9],["dc.services.index"],[["params"],[[28,[37,2],null,[["partition","nspace","peer","dc"],[[30,9,["item","Name"]],[27],[27],[30,3,["Name"]]]]]]]]],false]],[["default"],[[[[1,"\\n "],[1,[30,9,["item","Name"]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],null],[1," "]],[9]]]]],[1,"\\n"]],[4,5,6,7]]]],["@list","@partition","@dc","SNL","partition","isManaging","canChoose","@partitions","Selector","@onchange"],false,["let","if","hash","is-href","can","t","nav-selector","sort-by","reject-by","href-to","not","data-source","uri","fn","optional","eq"]]',moduleName:"consul-ui/components/consul/partition/selector/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/consul/peer/address/list/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"TNoxeoLi",block:'[[[1,"\\n"],[8,[39,0],null,null,[["default"],[[[[1,"\\n"],[41,[30,1,["data","height"]],[[[1," "],[10,0],[15,5,[30,1,["data","fillRemainingHeightStyle"]]],[14,0,"overflow-y-scroll"],[12],[1,"\\n "],[8,[39,2],null,[["@tagName","@estimateHeight","@items"],["ul",[30,1,["data","height"]],[30,2]]],[["default"],[[[[1,"\\n "],[10,"li"],[14,0,"px-3 h-12 border-bottom-primary flex items-center justify-between group"],[12],[1,"\\n "],[10,0],[14,0,"hds-typography-display-300 text-hds-foreground-strong hds-font-weight-semibold"],[12],[1,[30,3]],[13],[1,"\\n "],[8,[39,3],[[24,0,"opacity-0 group-hover:opacity-100"]],[["@value","@name"],[[30,3],"Address"]],null],[1,"\\n "],[13],[1,"\\n "]],[3,4]]]]],[1,"\\n "],[13],[1,"\\n"]],[]],null]],[1]]]]]],["p","@items","address","index"],false,["providers/dimension","if","vertical-collection","consul-copy-button"]]',moduleName:"consul-ui/components/consul/peer/address/list/index.hbs",isStrictMode:!1}) @@ -1082,11 +1079,11 @@ var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/consul/policy/list/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"62bUfTFp",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"consul-policy-list"]],[["@items"],[[30,1]]],[["default"],[[[[1,"\\n "],[8,[39,1],null,[["@name"],["header"]],[["default"],[[[[1,"\\n"],[41,[28,[37,3],[[28,[37,4],[[28,[37,5],[[30,2]],null],"policy-management"],null],[28,[37,4],[[28,[37,5],[[30,2]],null],"read-only"],null]],null],[[[1," "],[10,"dl"],[14,0,"policy-management"],[12],[1,"\\n "],[10,"dt"],[12],[1,"Type"],[13],[1,"\\n "],[10,"dd"],[12],[1,"\\n"],[41,[28,[37,4],[[28,[37,5],[[30,2]],null],"policy-management"],null],[[[1," "],[8,[39,6],null,null,[["default"],[[[[1,"\\n Global Management Policy\\n "]],[]]]]],[1,"\\n"]],[]],[[[1," "],[8,[39,6],null,null,[["default"],[[[[1,"\\n Global Read-only Policy\\n "]],[]]]]],[1,"\\n"]],[]]],[1," "],[13],[1,"\\n "],[13],[1,"\\n"]],[]],null],[1," "],[10,3],[15,6,[28,[37,7],["dc.acls.policies.edit",[30,2,["ID"]]],null]],[15,0,[52,[28,[37,4],[[28,[37,5],[[30,2]],null],"policy-management"],null],"is-management"]],[12],[1,[30,2,["Name"]]],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,1],null,[["@name"],["details"]],[["default"],[[[[1,"\\n "],[10,"dl"],[14,0,"datacenter"],[12],[1,"\\n "],[10,"dt"],[12],[1,"\\n "],[8,[39,6],null,null,[["default"],[[[[1,"Datacenters"]],[]]]]],[1,"\\n "],[13],[1,"\\n "],[10,"dd"],[12],[1,"\\n "],[1,[28,[35,8],[", ",[28,[37,9],[[30,2]],null]],null]],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "],[10,"dl"],[14,0,"description"],[12],[1,"\\n "],[10,"dt"],[12],[1,"Description"],[13],[1,"\\n "],[10,"dd"],[12],[1,"\\n "],[1,[30,2,["Description"]]],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,1],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[8,[30,3],null,null,[["default"],[[[[1,"\\n "],[8,[30,4],null,[["@href"],[[28,[37,7],["dc.acls.policies.edit",[30,2,["ID"]]],null]]],[["default"],[[[[1,"\\n "],[8,[39,1],null,[["@name"],["label"]],[["default"],[[[[1,"\\n"],[41,[28,[37,10],["write policy"],[["item"],[[30,2]]]],[[[1," Edit\\n"]],[]],[[[1," View\\n"]],[]]],[1," "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"],[41,[28,[37,10],["delete policy"],[["item"],[[30,2]]]],[[[1," "],[8,[30,4],[[24,0,"dangerous"]],[["@onclick"],[[28,[37,11],[[30,0],[30,5],[30,2]],null]]],[["default"],[[[[1,"\\n "],[8,[39,1],null,[["@name"],["label"]],[["default"],[[[[1,"\\n Delete\\n "]],[]]]]],[1,"\\n "],[8,[39,1],null,[["@name"],["confirmation"]],[["default"],[[[[1,"\\n "],[8,[30,6],[[24,0,"warning"]],null,[["default"],[[[[1,"\\n "],[8,[39,1],null,[["@name"],["header"]],[["default"],[[[[1,"\\n Confirm delete\\n "]],[]]]]],[1,"\\n "],[8,[39,1],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[10,2],[12],[1,"\\n Are you sure you want to delete this policy?\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,1],null,[["@name"],["confirm"]],[["default"],[[[[1,"\\n "],[8,[30,7],null,null,[["default"],[[[[1,"Delete"]],[]]]]],[1,"\\n "]],[7]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[6]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],null],[1," "]],[4]]]]],[1,"\\n "]],[3]]]]],[1,"\\n"]],[2]]]]]],["@items","item","Actions","Action","@ondelete","Confirmation","Confirm"],false,["list-collection","block-slot","if","or","eq","policy/typeof","tooltip","href-to","join","policy/datacenters","can","action"]]',moduleName:"consul-ui/components/consul/policy/list/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) -e.default=i})) -define("consul-ui/components/consul/policy/notifications/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=i})),define("consul-ui/components/consul/policy/notifications/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"sUAOpVES",block:'[[[1,"\\n"],[41,[28,[37,1],[[30,1],"create"],null],[[[41,[28,[37,1],[[30,2],"success"],null],[[[1," Your policy has been added.\\n"]],[]],[[[1," There was an error adding your policy.\\n"]],[]]]],[]],[[[41,[28,[37,1],[[30,1],"update"],null],[[[41,[28,[37,1],[[30,2],"success"],null],[[[1," Your policy has been saved.\\n"]],[]],[[[1," There was an error saving your policy.\\n"]],[]]]],[]],[[[41,[28,[37,1],[[30,1],"delete"],null],[[[41,[28,[37,1],[[30,2],"success"],null],[[[1," Your policy was deleted.\\n"]],[]],[[[1," There was an error deleting your policy.\\n"]],[]]]],[]],null]],[]]]],[]]],[44,[[30,3,["errors","firstObject"]]],[[[41,[30,4,["detail"]],[[[1," "],[10,"br"],[12],[13],[1,[28,[35,3],["(",[52,[30,4,["status"]],[28,[37,3],[[30,4,["status"]],": "],null]],[30,4,["detail"]],")"],null]],[1,"\\n"]],[]],null]],[4]]]],["@type","@status","@error","error"],false,["if","eq","let","concat"]]',moduleName:"consul-ui/components/consul/policy/notifications/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) -e.default=i})),define("consul-ui/components/consul/policy/search-bar/index",["exports","@ember/component","@ember/template-factory","@glimmer/component"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=i})) +define("consul-ui/components/consul/policy/search-bar/index",["exports","@ember/component","@ember/template-factory","@glimmer/component"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"Idci/62E",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"consul-policy-search-bar"],[17,1]],[["@filter"],[[30,2]]],[["status","search","filter","sort"],[[[[1,"\\n\\n"],[44,[[28,[37,2],[[28,[37,3],["components.consul.policy.search-bar.",[30,3,["status","key"]],".name"],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","key"]]],null],[28,[37,3],["common.consul.",[30,3,["status","key"]]],null]],null]]]],[28,[37,2],[[28,[37,3],["components.consul.policy.search-bar.",[30,3,["status","key"]],".options.",[30,3,["status","value"]]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","value"]]],null],[28,[37,3],["common.consul.",[30,3,["status","value"]]],null],[28,[37,3],["common.brand.",[30,3,["status","value"]]],null]],null]]]]],[[[1," "],[8,[30,3,["RemoveFilter"]],[[16,"aria-label",[28,[37,2],["common.ui.remove"],[["item"],[[28,[37,3],[[30,4]," ",[30,5]],null]]]]]],null,[["default"],[[[[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,[30,4]],[13],[1,"\\n "],[10,"dd"],[12],[1,[52,[28,[37,6],[[30,3,["status","key"]],"datacenter"],null],[30,3,["status","value"]],[30,5]]],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n"]],[4,5]]],[1,"\\n "]],[3]],[[[1,"\\n "],[8,[30,6,["Search"]],null,[["@onsearch","@value","@placeholder"],[[28,[37,7],[[30,0],[30,7]],null],[30,8],[28,[37,2],["common.search.search"],null]]],[["default"],[[[[1,"\\n "],[8,[30,6,["Select"]],[[24,0,"type-search-properties"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,7],[[30,0],[30,2,["searchproperty","change"]]],null],true,true]],[["default"],[[[[1,"\\n "],[8,[39,8],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.search.searchproperty"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,8],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,9,["Optgroup"]],[30,9,["Option"]]],[[[42,[28,[37,10],[[28,[37,10],[[30,2,["searchproperty","default"]]],null]],null],null,[[[1," "],[8,[30,11],null,[["@value","@selected"],[[30,12],[28,[37,11],[[30,12],[30,2,["searchproperty","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.consul.",[28,[37,12],[[30,12]],null]],null]],null]],[1,"\\n "]],[]]]]],[1,"\\n"]],[12]],null]],[10,11]]],[1," "]],[]]]]],[1,"\\n "]],[9]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[6]],[[[1,"\\n "],[8,[30,13,["Select"]],[[24,0,"type-datacenter"]],[["@position","@onchange","@multiple"],["left",[28,[37,7],[[30,0],[30,2,["datacenter","change"]]],null],true]],[["default"],[[[[1,"\\n "],[8,[39,8],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.consul.datacenter"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,8],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,14,["Optgroup"]],[30,14,["Option"]]],[[[42,[28,[37,10],[[28,[37,10],[[33,13]],null]],null],null,[[[1," "],[8,[30,16],null,[["@value","@selected"],[[30,17,["Name"]],[28,[37,11],[[30,17,["Name"]],[30,2,["datacenter","value"]]],null]]],[["default"],[[[[1,[30,17,["Name"]]]],[]]]]],[1,"\\n"]],[17]],null],[1," "],[8,[39,14],null,[["@src","@loading","@onchange"],[[28,[37,15],["/${partition}/*/*/datacenters",[28,[37,16],null,[["partition"],[[30,18]]]]],null],"lazy",[28,[37,7],[[30,0],[28,[37,17],[[30,0,["dcs"]]],null]],[["value"],["data"]]]]],null],[1,"\\n"]],[15,16]]],[1," "]],[]]]]],[1,"\\n "]],[14]]]]],[1,"\\n "],[8,[30,13,["Select"]],[[24,0,"type-kind"]],[["@position","@onchange","@multiple"],["left",[28,[37,7],[[30,0],[30,2,["kind","change"]]],null],true]],[["default"],[[[[1,"\\n "],[8,[39,8],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["components.consul.policy.search-bar.kind.name"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,8],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,19,["Optgroup"]],[30,19,["Option"]]],[[[42,[28,[37,10],[[28,[37,10],[[28,[37,4],["global-management","standard"],null]],null]],null],null,[[[1," "],[8,[30,21],[[16,0,[29,["value-",[30,22]]]]],[["@value","@selected"],[[30,22],[28,[37,11],[[30,22],[30,2,["kind","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["components.consul.policy.search-bar.kind.options.",[30,22]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,22]],null]],null]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[22]],null]],[20,21]]],[1," "]],[]]]]],[1,"\\n "]],[19]]]]],[1,"\\n "]],[13]],[[[1,"\\n "],[8,[30,23,["Select"]],[[24,0,"type-sort"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,7],[[30,0],[30,24,["change"]]],null],false,true]],[["default"],[[[[1,"\\n "],[8,[39,8],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n"],[44,[[28,[37,18],[[28,[37,4],[[28,[37,4],["Name:asc",[28,[37,2],["common.sort.alpha.asc"],null]],null],[28,[37,4],["Name:desc",[28,[37,2],["common.sort.alpha.desc"],null]],null]],null]],null]],[[[1," "],[1,[28,[35,19],[[30,26],[30,24,["value"]]],null]],[1,"\\n"]],[26]]],[1," "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,8],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,25,["Optgroup"]],[30,25,["Option"]]],[[[1," "],[8,[30,27],null,[["@label"],[[28,[37,2],["common.ui.name"],null]]],[["default"],[[[[1,"\\n "],[8,[30,28],null,[["@value","@selected"],["Name:asc",[28,[37,6],["Name:asc",[30,24,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.asc"],null]]],[]]]]],[1,"\\n "],[8,[30,28],null,[["@value","@selected"],["Name:desc",[28,[37,6],["Name:desc",[30,24,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.desc"],null]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[27,28]]],[1," "]],[]]]]],[1,"\\n "]],[25]]]]],[1,"\\n "]],[23]]]]],[1,"\\n"]],["&attrs","@filter","search","key","value","search","@onsearch","@search","components","Optgroup","Option","prop","search","components","Optgroup","Option","dc","@partition","components","Optgroup","Option","state","search","@sort","components","selectable","Optgroup","Option"],false,["search-bar","let","t","concat","array","if","eq","action","block-slot","each","-track-array","includes","lowercase","dcs","data-source","uri","hash","mut","from-entries","get"]]',moduleName:"consul-ui/components/consul/policy/search-bar/index.hbs",isStrictMode:!1}) class i extends l.default{}e.default=i,(0,t.setComponentTemplate)(r,i)})),define("consul-ui/components/consul/role/list/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"B3WXmBeq",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"consul-role-list"],[17,1]],[["@items"],[[30,2]]],[["default"],[[[[1,"\\n "],[8,[39,1],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,3],[15,6,[28,[37,2],["dc.acls.roles.edit",[30,3,["ID"]]],null]],[12],[1,[30,3,["Name"]]],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,1],null,[["@name"],["details"]],[["default"],[[[[1,"\\n "],[8,[39,3],null,[["@item"],[[30,3]]],null],[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,"Description"],[13],[1,"\\n "],[10,"dd"],[12],[1,"\\n "],[1,[30,3,["Description"]]],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,1],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[8,[30,4],null,null,[["default"],[[[[1,"\\n "],[8,[30,5],null,[["@href"],[[28,[37,2],["dc.acls.roles.edit",[30,3,["ID"]]],null]]],[["default"],[[[[1,"\\n "],[8,[39,1],null,[["@name"],["label"]],[["default"],[[[[1,"\\n"],[41,[28,[37,5],["write role"],[["item"],[[30,3]]]],[[[1," Edit\\n"]],[]],[[[1," View\\n"]],[]]],[1," "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"],[41,[28,[37,5],["delete role"],[["item"],[[30,3]]]],[[[1," "],[8,[30,5],[[24,0,"dangerous"]],[["@onclick"],[[28,[37,6],[[30,0],[30,6],[30,3]],null]]],[["default"],[[[[1,"\\n "],[8,[39,1],null,[["@name"],["label"]],[["default"],[[[[1,"\\n Delete\\n "]],[]]]]],[1,"\\n "],[8,[39,1],null,[["@name"],["confirmation"]],[["default"],[[[[1,"\\n "],[8,[30,7],[[24,0,"warning"]],null,[["default"],[[[[1,"\\n "],[8,[39,1],null,[["@name"],["header"]],[["default"],[[[[1,"\\n Confirm delete\\n "]],[]]]]],[1,"\\n "],[8,[39,1],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[10,2],[12],[1,"\\n Are you sure you want to delete this role?\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,1],null,[["@name"],["confirm"]],[["default"],[[[[1,"\\n "],[8,[30,8],null,null,[["default"],[[[[1,"Delete"]],[]]]]],[1,"\\n "]],[8]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[7]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],null],[1," "]],[5]]]]],[1,"\\n "]],[4]]]]],[1,"\\n"]],[3]]]]]],["&attrs","@items","item","Actions","Action","@ondelete","Confirmation","Confirm"],false,["list-collection","block-slot","href-to","consul/token/ruleset/list","if","can","action"]]',moduleName:"consul-ui/components/consul/role/list/index.hbs",isStrictMode:!1}) @@ -1115,9 +1112,12 @@ return(null===(t=e.Service)||void 0===t||null===(n=t.Meta)||void 0===n?void 0:n[ const r=(0,n.createTemplateFactory)({id:"VjdzBiZi",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"consul-service-instance-search-bar"],[17,1]],[["@filter"],[[30,2]]],[["status","search","filter","sort"],[[[[1,"\\n\\n"],[44,[[28,[37,2],[[28,[37,3],["components.consul.service-instance.search-bar.",[30,3,["status","key"]],".name"],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","key"]]],null],[28,[37,3],["common.consul.",[30,3,["status","key"]]],null]],null]]]],[28,[37,2],[[28,[37,3],["components.consul.service-instance.search-bar.",[30,3,["status","key"]],".options.",[30,3,["status","value"]]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","value"]]],null],[28,[37,3],["common.consul.",[30,3,["status","value"]]],null],[28,[37,3],["common.brand.",[30,3,["status","value"]]],null]],null]]]]],[[[1," "],[8,[30,3,["RemoveFilter"]],[[16,"aria-label",[28,[37,2],["common.ui.remove"],[["item"],[[28,[37,3],[[30,4]," ",[30,5]],null]]]]]],null,[["default"],[[[[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,[30,4]],[13],[1,"\\n "],[10,"dd"],[12],[1,[30,5]],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n"]],[4,5]]],[1,"\\n "]],[3]],[[[1,"\\n "],[8,[30,6,["Search"]],null,[["@onsearch","@value","@placeholder"],[[28,[37,5],[[30,0],[30,7]],null],[30,8],[28,[37,2],["common.search.search"],null]]],[["default"],[[[[1,"\\n"],[41,[30,2,["searchproperty"]],[[[1," "],[8,[30,6,["Select"]],[[24,0,"type-search-properties"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,5],[[30,0],[30,2,["searchproperty","change"]]],null],true,true]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.search.searchproperty"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,9,["Option"]]],[[[42,[28,[37,9],[[28,[37,9],[[30,2,["searchproperty","default"]]],null]],null],null,[[[1," "],[8,[30,10],null,[["@value","@selected"],[[30,11],[28,[37,10],[[30,11],[30,2,["searchproperty","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.consul.",[28,[37,11],[[30,11]],null]],null]],null]],[1,"\\n "]],[]]]]],[1,"\\n"]],[11]],null]],[10]]],[1," "]],[]]]]],[1,"\\n "]],[9]]]]],[1,"\\n"]],[]],null],[1," "]],[]]]]],[1,"\\n "]],[6]],[[[1,"\\n "],[8,[30,12,["Select"]],[[24,0,"type-status"]],[["@position","@onchange","@multiple"],["left",[28,[37,5],[[30,0],[30,2,["status","change"]]],null],true]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.consul.status"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,13,["Optgroup"]],[30,13,["Option"]]],[[[42,[28,[37,9],[[28,[37,9],[[28,[37,4],["passing","warning","critical","empty"],null]],null]],null],null,[[[1," "],[8,[30,15],[[16,0,[29,["value-",[30,16]]]]],[["@value","@selected"],[[30,16],[28,[37,10],[[30,16],[30,2,["status","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.consul.",[30,16]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,16]],null]],null]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[16]],null]],[14,15]]],[1," "]],[]]]]],[1,"\\n "]],[13]]]]],[1,"\\n"],[41,[28,[37,12],[[30,17,["length"]],0],null],[[[1," "],[8,[30,12,["Select"]],[[24,0,"type-source"]],[["@position","@onchange","@multiple"],["left",[28,[37,5],[[30,0],[30,2,["source","change"]]],null],true]],[["default"],[[[[1,"\\n "],[8,[39,13],null,[["@components","@filter","@sources"],[[30,18],[30,2],[30,17]]],null],[1,"\\n "]],[18]]]]],[1,"\\n"]],[]],null],[1," "]],[12]],[[[1,"\\n "],[8,[30,19,["Select"]],[[24,0,"type-sort"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,5],[[30,0],[30,20,["change"]]],null],false,true]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n"],[44,[[28,[37,14],[[28,[37,4],[[28,[37,4],["Name:asc",[28,[37,2],["common.sort.alpha.asc"],null]],null],[28,[37,4],["Name:desc",[28,[37,2],["common.sort.alpha.desc"],null]],null],[28,[37,4],["Status:asc",[28,[37,2],["common.sort.status.asc"],null]],null],[28,[37,4],["Status:desc",[28,[37,2],["common.sort.status.desc"],null]],null]],null]],null]],[[[1," "],[1,[28,[35,15],[[30,22],[30,20,["value"]]],null]],[1,"\\n"]],[22]]],[1," "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,21,["Optgroup"]],[30,21,["Option"]]],[[[1," "],[8,[30,23],null,[["@label"],[[28,[37,2],["common.consul.status"],null]]],[["default"],[[[[1,"\\n "],[8,[30,24],null,[["@value","@selected"],["Status:asc",[28,[37,16],["Status:asc",[30,20,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.status.asc"],null]]],[]]]]],[1,"\\n "],[8,[30,24],null,[["@value","@selected"],["Status:desc",[28,[37,16],["Status:desc",[30,20,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.status.desc"],null]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,23],null,[["@label"],[[28,[37,2],["components.consul.service-instance.search-bar.sort.name.name"],null]]],[["default"],[[[[1,"\\n "],[8,[30,24],null,[["@value","@selected"],["Name:asc",[28,[37,16],["Name:asc",[30,20,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.asc"],null]]],[]]]]],[1,"\\n "],[8,[30,24],null,[["@value","@selected"],["Name:desc",[28,[37,16],["Name:desc",[30,20,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.desc"],null]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[23,24]]],[1," "]],[]]]]],[1,"\\n "]],[21]]]]],[1,"\\n "]],[19]]]]],[1,"\\n"]],["&attrs","@filter","search","key","value","search","@onsearch","@search","components","Option","prop","search","components","Optgroup","Option","state","@sources","components","search","@sort","components","selectable","Optgroup","Option"],false,["search-bar","let","t","concat","array","action","if","block-slot","each","-track-array","includes","lowercase","gt","consul/sources-select","from-entries","get","eq"]]',moduleName:"consul-ui/components/consul/service-instance/search-bar/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/consul/service/list/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -const r=(0,n.createTemplateFactory)({id:"DY2ljV0F",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"consul-service-list"],[17,1]],[["@items","@linkable"],[[30,2],"linkable service"]],[["default"],[[[[1,"\\n "],[8,[39,1],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"dl"],[15,0,[30,3,["MeshStatus"]]],[12],[1,"\\n "],[10,"dt"],[12],[1,"\\n Health\\n "],[13],[1,"\\n "],[11,"dd"],[4,[38,2],[[30,3,["healthTooltipText"]]],null],[12],[13],[1,"\\n "],[13],[1,"\\n"],[41,[28,[37,4],[[30,3,["InstanceCount"]],0],null],[[[1," "],[10,3],[15,6,[28,[37,5],["dc.services.show.index",[30,3,["Name"]]],[["params"],[[52,[28,[37,6],[[30,3,["Partition"]],[30,5]],null],[28,[37,7],null,[["partition","nspace","peer"],[[30,3,["Partition"]],[30,3,["Namespace"]],[30,3,["PeerName"]]]]],[28,[37,7],null,[["peer"],[[30,3,["PeerName"]]]]]]]]]],[12],[1,"\\n "],[1,[30,3,["Name"]]],[1,"\\n "],[13],[1,"\\n"]],[]],[[[1," "],[10,2],[12],[1,"\\n "],[1,[30,3,["Name"]]],[1,"\\n "],[13],[1,"\\n"]],[]]],[1," "]],[]]]]],[1,"\\n "],[8,[39,1],null,[["@name"],["details"]],[["default"],[[[[1,"\\n "],[8,[39,8],null,[["@item"],[[30,3]]],null],[1,"\\n "],[8,[39,9],null,[["@item"],[[30,3]]],null],[1,"\\n"],[41,[28,[37,10],[[28,[37,6],[[30,3,["InstanceCount"]],0],null],[28,[37,10],[[28,[37,6],[[30,3,["Kind"]],"terminating-gateway"],null],[28,[37,6],[[30,3,["Kind"]],"ingress-gateway"],null]],null]],null],[[[1," "],[10,1],[12],[1,"\\n "],[1,[28,[35,11],[[30,3,["InstanceCount"]]],null]],[1,"\\n "],[1,[28,[35,12],[[30,3,["InstanceCount"]],"instance"],[["without-count"],[true]]]],[1,"\\n "],[13],[1,"\\n"]],[]],null],[41,[51,[30,6]],[[[1," "],[8,[39,14],null,[["@item","@nspace","@partition"],[[30,3],[30,7],[30,5]]],null],[1,"\\n"]],[]],null],[41,[28,[37,15],[[30,3,["Kind"]],"terminating-gateway"],null],[[[1," "],[10,1],[12],[1,"\\n "],[1,[28,[35,11],[[30,3,["GatewayConfig","AssociatedServiceCount"]]],null]],[1,"\\n "],[1,[28,[35,12],[[30,3,["GatewayConfig","AssociatedServiceCount"]],"linked service"],[["without-count"],[true]]]],[1,"\\n "],[13],[1,"\\n"]],[]],[[[41,[28,[37,15],[[30,3,["Kind"]],"ingress-gateway"],null],[[[1," "],[10,1],[12],[1,"\\n "],[1,[28,[35,11],[[30,3,["GatewayConfig","AssociatedServiceCount"]]],null]],[1,"\\n "],[1,[28,[35,12],[[30,3,["GatewayConfig","AssociatedServiceCount"]],"upstream"],[["without-count"],[true]]]],[1,"\\n "],[13],[1,"\\n "]],[]],null]],[]]],[41,[28,[37,16],[[30,3,["ConnectedWithGateway"]],[30,3,["ConnectedWithProxy"]]],null],[[[1," "],[10,"dl"],[14,0,"mesh"],[12],[1,"\\n "],[10,"dt"],[12],[1,"\\n "],[8,[39,2],null,null,[["default"],[[[[1,"\\n This service uses a proxy for the Consul service mesh\\n "]],[]]]]],[1,"\\n "],[13],[1,"\\n"],[41,[28,[37,10],[[30,3,["ConnectedWithGateway"]],[30,3,["ConnectedWithProxy"]]],null],[[[1," "],[10,"dd"],[12],[1,"\\n in service mesh with proxy and gateway\\n "],[13],[1,"\\n"]],[]],[[[41,[30,3,["ConnectedWithProxy"]],[[[1," "],[10,"dd"],[12],[1,"\\n in service mesh with proxy\\n "],[13],[1,"\\n"]],[]],[[[41,[30,3,["ConnectedWithGateway"]],[[[1," "],[10,"dd"],[12],[1,"\\n in service mesh with gateway\\n "],[13],[1,"\\n "]],[]],null]],[]]]],[]]],[1," "],[13],[1,"\\n"]],[]],null],[1," "],[8,[39,17],null,[["@item"],[[30,3]]],null],[1,"\\n "]],[]]]]],[1,"\\n"]],[3,4]]]]],[1,"\\n"]],["&attrs","@items","item","index","@partition","@isPeerDetail","@nspace"],false,["list-collection","block-slot","tooltip","if","gt","href-to","not-eq","hash","consul/kind","consul/external-source","and","format-number","pluralize","unless","consul/bucket/list","eq","or","tag-list"]]',moduleName:"consul-ui/components/consul/service/list/index.hbs",isStrictMode:!1}) +const r=(0,n.createTemplateFactory)({id:"xPCwneUJ",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"consul-service-list"],[17,1]],[["@items","@linkable"],[[30,2],"linkable service"]],[["default"],[[[[1,"\\n "],[8,[39,1],null,[["@item","@partition","@nspace"],[[30,3],[30,5],[30,6]]],null],[1,"\\n"]],[3,4]]]]],[1,"\\n"]],["&attrs","@items","item","index","@partition","@nspace"],false,["list-collection","consul/service/list/item"]]',moduleName:"consul-ui/components/consul/service/list/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) -e.default=i})),define("consul-ui/components/consul/service/search-bar/index",["exports","@ember/component","@ember/template-factory","@glimmer/component"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=i})),define("consul-ui/components/consul/service/list/item/index",["exports","@ember/component","@ember/template-factory","@glimmer/component"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +const r=(0,n.createTemplateFactory)({id:"cAe7geUl",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"dl"],[15,0,[30,1,["MeshStatus"]]],[12],[1,"\\n "],[10,"dt"],[12],[1,"\\n Health\\n "],[13],[1,"\\n "],[11,"dd"],[4,[38,1],[[30,1,["healthTooltipText"]]],null],[12],[13],[1,"\\n "],[13],[1,"\\n"],[41,[28,[37,3],[[30,1,["InstanceCount"]],0],null],[[[1," "],[10,3],[15,6,[28,[37,4],["dc.services.show.index",[30,1,["Name"]]],[["params"],[[30,0,["linkParams"]]]]]],[12],[1,"\\n "],[1,[30,1,["Name"]]],[1,"\\n "],[13],[1,"\\n"]],[]],[[[1," "],[10,2],[12],[1,"\\n "],[1,[30,1,["Name"]]],[1,"\\n "],[13],[1,"\\n"]],[]]]],[]]]]],[1,"\\n"],[8,[39,0],null,[["@name"],["details"]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@item"],[[30,1]]],null],[1,"\\n "],[8,[39,6],null,[["@item"],[[30,1]]],null],[1,"\\n"],[41,[28,[37,7],[[28,[37,8],[[30,1,["InstanceCount"]],0],null],[28,[37,7],[[28,[37,8],[[30,1,["Kind"]],"terminating-gateway"],null],[28,[37,8],[[30,1,["Kind"]],"ingress-gateway"],null]],null]],null],[[[1," "],[10,1],[12],[1,"\\n "],[1,[28,[35,9],[[30,1,["InstanceCount"]]],null]],[1,"\\n "],[1,[28,[35,10],[[30,1,["InstanceCount"]],"instance"],[["without-count"],[true]]]],[1,"\\n "],[13],[1,"\\n"]],[]],null],[41,[51,[30,2]],[[[1," "],[8,[39,12],null,[["@item","@nspace","@partition"],[[30,1],[30,3],[30,4]]],null],[1,"\\n"]],[]],null],[41,[28,[37,13],[[30,1,["Kind"]],"terminating-gateway"],null],[[[1," "],[10,1],[12],[1,"\\n "],[1,[28,[35,9],[[30,1,["GatewayConfig","AssociatedServiceCount"]]],null]],[1,"\\n "],[1,[28,[35,10],[[30,1,["GatewayConfig","AssociatedServiceCount"]],"linked service"],[["without-count"],[true]]]],[1,"\\n "],[13],[1,"\\n"]],[]],[[[41,[28,[37,13],[[30,1,["Kind"]],"ingress-gateway"],null],[[[1," "],[10,1],[12],[1,"\\n "],[1,[28,[35,9],[[30,1,["GatewayConfig","AssociatedServiceCount"]]],null]],[1,"\\n "],[1,[28,[35,10],[[30,1,["GatewayConfig","AssociatedServiceCount"]],"upstream"],[["without-count"],[true]]]],[1,"\\n "],[13],[1,"\\n "]],[]],null]],[]]],[41,[28,[37,14],[[30,1,["ConnectedWithGateway"]],[30,1,["ConnectedWithProxy"]]],null],[[[1," "],[10,"dl"],[14,0,"mesh"],[12],[1,"\\n "],[10,"dt"],[12],[1,"\\n "],[8,[39,1],null,null,[["default"],[[[[1,"\\n This service uses a proxy for the Consul service mesh\\n "]],[]]]]],[1,"\\n "],[13],[1,"\\n"],[41,[28,[37,7],[[30,1,["ConnectedWithGateway"]],[30,1,["ConnectedWithProxy"]]],null],[[[1," "],[10,"dd"],[12],[1,"\\n in service mesh with proxy and gateway\\n "],[13],[1,"\\n"]],[]],[[[41,[30,1,["ConnectedWithProxy"]],[[[1," "],[10,"dd"],[12],[1,"\\n in service mesh with proxy\\n "],[13],[1,"\\n"]],[]],[[[41,[30,1,["ConnectedWithGateway"]],[[[1," "],[10,"dd"],[12],[1,"\\n in service mesh with gateway\\n "],[13],[1,"\\n "]],[]],null]],[]]]],[]]],[1," "],[13],[1,"\\n"]],[]],null],[1," "],[8,[39,15],null,[["@item"],[[30,1]]],null],[1,"\\n"]],[]]]]]],["@item","@isPeerDetail","@nspace","@partition"],false,["block-slot","tooltip","if","gt","href-to","consul/kind","consul/external-source","and","not-eq","format-number","pluralize","unless","consul/bucket/list","eq","or","tag-list"]]',moduleName:"consul-ui/components/consul/service/list/item/index.hbs",isStrictMode:!1}) +class i extends l.default{get linkParams(){const e={} +return this.args.item.Partition&&this.args.partition!==this.args.item.Partition?(e.partition=this.args.item.Partition,e.nspace=this.args.Namespace):this.args.item.Namespace&&this.args.nspace!==this.args.item.Namespace&&(e.nspace=this.args.item.Namespace),this.args.item.PeerName&&(e.peer=this.args.item.PeerName),e}}e.default=i,(0,t.setComponentTemplate)(r,i)})),define("consul-ui/components/consul/service/search-bar/index",["exports","@ember/component","@ember/template-factory","@glimmer/component"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"4sJ6oMs1",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"consul-service-search-bar"],[17,1]],[["@filter"],[[30,2]]],[["status","search","filter","sort"],[[[[1,"\\n\\n"],[44,[[28,[37,2],[[28,[37,3],["components.consul.service.search-bar.",[30,3,["status","key"]]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","key"]]],null],[28,[37,3],["common.consul.",[30,3,["status","key"]]],null]],null]]]],[28,[37,2],[[28,[37,3],["components.consul.service.search-bar.",[30,3,["status","value"]]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,3,["status","value"]]],null],[28,[37,3],["common.consul.",[30,3,["status","value"]]],null],[28,[37,3],["common.brand.",[30,3,["status","value"]]],null]],null]]]]],[[[1," "],[8,[30,3,["RemoveFilter"]],[[16,"aria-label",[28,[37,2],["common.ui.remove"],[["item"],[[28,[37,3],[[30,4]," ",[30,5]],null]]]]]],null,[["default"],[[[[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,[30,4]],[13],[1,"\\n "],[10,"dd"],[12],[1,[30,5]],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n"]],[4,5]]],[1,"\\n "]],[3]],[[[1,"\\n "],[8,[30,6,["Search"]],null,[["@onsearch","@value","@placeholder"],[[28,[37,5],[[30,0],[30,7]],null],[30,8],[28,[37,2],["common.search.search"],null]]],[["default"],[[[[1,"\\n "],[8,[30,6,["Select"]],[[24,0,"type-search-properties"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,5],[[30,0],[30,2,["searchproperty","change"]]],null],true,true]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.search.searchproperty"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,9,["Optgroup"]],[30,9,["Option"]]],[[[42,[28,[37,8],[[28,[37,8],[[30,2,["searchproperty","default"]]],null]],null],null,[[[1," "],[8,[30,11],null,[["@value","@selected"],[[30,12],[28,[37,9],[[30,12],[30,2,["searchproperty","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.consul.",[28,[37,10],[[30,12]],null]],null]],null]],[1,"\\n "]],[]]]]],[1,"\\n"]],[12]],null]],[10,11]]],[1," "]],[]]]]],[1,"\\n "]],[9]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[6]],[[[1,"\\n "],[8,[30,13,["Select"]],[[24,0,"type-status"]],[["@position","@onchange","@multiple"],["left",[28,[37,5],[[30,0],[30,2,["status","change"]]],null],true]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["common.consul.status"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,14,["Optgroup"]],[30,14,["Option"]]],[[[42,[28,[37,8],[[28,[37,8],[[30,0,["healthStates"]]],null]],null],null,[[[1," "],[8,[30,16],[[16,0,[29,["value-",[30,17]]]]],[["@value","@selected"],[[30,17],[28,[37,9],[[30,17],[30,2,["status","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.consul.",[30,17]],null]],[["default"],[[28,[37,4],[[28,[37,3],["common.search.",[30,17]],null]],null]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[17]],null]],[15,16]]],[1," "]],[]]]]],[1,"\\n "]],[14]]]]],[1,"\\n "],[8,[30,13,["Select"]],null,[["@position","@onchange","@multiple"],["left",[28,[37,5],[[30,0],[30,2,["kind","change"]]],null],true]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n "],[1,[28,[35,2],["components.consul.service.search-bar.kind"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,18,["Optgroup"]],[30,18,["Option"]]],[[[1," "],[8,[30,20],null,[["@value","@selected"],["service",[28,[37,9],["service",[30,2,["kind","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],["common.consul.service"],null]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,19],null,[["@label"],[[28,[37,2],["common.consul.gateway"],null]]],[["default"],[[[[1,"\\n"],[42,[28,[37,8],[[28,[37,8],[[28,[37,4],["api-gateway","ingress-gateway","terminating-gateway","mesh-gateway"],null]],null]],null],null,[[[1," "],[8,[30,20],null,[["@value","@selected"],[[30,21],[28,[37,9],[[30,21],[30,2,["kind","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.consul.",[30,21]],null]],null]],[1,"\\n "]],[]]]]],[1,"\\n"]],[21]],null],[1," "]],[]]]]],[1,"\\n "],[8,[30,19],null,[["@label"],[[28,[37,2],["common.consul.mesh"],null]]],[["default"],[[[[1,"\\n"],[42,[28,[37,8],[[28,[37,8],[[28,[37,4],["in-mesh","not-in-mesh"],null]],null]],null],null,[[[1," "],[8,[30,20],null,[["@value","@selected"],[[30,22],[28,[37,9],[[30,22],[30,2,["kind","value"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["common.search.",[30,22]],null]],null]],[1,"\\n "]],[]]]]],[1,"\\n"]],[22]],null],[1," "]],[]]]]],[1,"\\n"]],[19,20]]],[1," "]],[]]]]],[1,"\\n "]],[18]]]]],[1,"\\n"],[41,[28,[37,12],[[30,23,["length"]],0],null],[[[1," "],[8,[30,13,["Select"]],[[24,0,"type-source"]],[["@position","@onchange","@multiple"],["left",[28,[37,5],[[30,0],[30,2,["source","change"]]],null],true]],[["default"],[[[[1,"\\n "],[8,[39,13],null,[["@components","@filter","@sources"],[[30,24],[30,2],[30,0,["sortedSources"]]]],null],[1,"\\n "]],[24]]]]],[1,"\\n"]],[]],null],[1," "]],[13]],[[[1,"\\n "],[8,[30,25,["Select"]],[[24,0,"type-sort"]],[["@position","@onchange","@multiple","@required"],["right",[28,[37,5],[[30,0],[30,26,["change"]]],null],false,true]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["selected"]],[["default"],[[[[1,"\\n "],[10,1],[12],[1,"\\n"],[44,[[28,[37,14],[[28,[37,4],[[28,[37,4],["Name:asc",[28,[37,2],["common.sort.alpha.asc"],null]],null],[28,[37,4],["Name:desc",[28,[37,2],["common.sort.alpha.desc"],null]],null],[28,[37,4],["Status:asc",[28,[37,2],["common.sort.status.asc"],null]],null],[28,[37,4],["Status:desc",[28,[37,2],["common.sort.status.desc"],null]],null]],null]],null]],[[[1," "],[1,[28,[35,15],[[30,28],[30,26,["value"]]],null]],[1,"\\n"]],[28]]],[1," "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["options"]],[["default"],[[[[1,"\\n"],[44,[[30,27,["Optgroup"]],[30,27,["Option"]]],[[[1," "],[8,[30,29],null,[["@label"],[[28,[37,2],["common.consul.status"],null]]],[["default"],[[[[1,"\\n "],[8,[30,30],null,[["@value","@selected"],["Status:asc",[28,[37,16],["Status:asc",[30,26,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.status.asc"],null]]],[]]]]],[1,"\\n "],[8,[30,30],null,[["@value","@selected"],["Status:desc",[28,[37,16],["Status:desc",[30,26,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.status.desc"],null]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,29],null,[["@label"],[[28,[37,2],["common.consul.service-name"],null]]],[["default"],[[[[1,"\\n "],[8,[30,30],null,[["@value","@selected"],["Name:asc",[28,[37,16],["Name:asc",[30,26,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.asc"],null]]],[]]]]],[1,"\\n "],[8,[30,30],null,[["@value","@selected"],["Name:desc",[28,[37,16],["Name:desc",[30,26,["value"]]],null]]],[["default"],[[[[1,[28,[35,2],["common.sort.alpha.desc"],null]]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[29,30]]],[1," "]],[]]]]],[1,"\\n "]],[27]]]]],[1,"\\n "]],[25]]]]],[1,"\\n"]],["&attrs","@filter","search","key","value","search","@onsearch","@search","components","Optgroup","Option","prop","search","components","Optgroup","Option","state","components","Optgroup","Option","kind","state","@sources","components","search","@sort","components","selectable","Optgroup","Option"],false,["search-bar","let","t","concat","array","action","block-slot","each","-track-array","includes","lowercase","if","gt","consul/sources-select","from-entries","get","eq"]]',moduleName:"consul-ui/components/consul/service/search-bar/index.hbs",isStrictMode:!1}) class i extends l.default{get healthStates(){return this.args.peer?["passing","warning","critical","unknown","empty"]:["passing","warning","critical","empty"]}get sortedSources(){const e=this.args.sources||[] return e.unshift(["consul"]),e.includes("consul-api-gateway")?[...e.filter((e=>"consul-api-gateway"!==e)),"consul-api-gateway"]:e}}e.default=i,(0,t.setComponentTemplate)(r,i)})),define("consul-ui/components/consul/source/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 @@ -1311,18 +1311,25 @@ function o(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const a=(0,n.createTemplateFactory)({id:"QsBvWU14",block:'[[[1,"\\n"],[11,0],[24,0,"freetext-filter"],[17,1],[12],[1,"\\n "],[10,"label"],[14,0,"type-search"],[12],[1,"\\n "],[10,1],[14,0,"freetext-filter_label"],[12],[1,"Search"],[13],[1,"\\n "],[10,"input"],[14,0,"freetext-filter_input"],[15,"onsearch",[28,[37,0],[[30,0],[30,0,["change"]]],null]],[15,"oninput",[28,[37,0],[[30,0],[30,0,["change"]]],null]],[15,"onkeydown",[28,[37,0],[[30,0],[30,0,["keydown"]]],null]],[14,3,"s"],[15,2,[30,2]],[15,"placeholder",[30,0,["placeholder"]]],[14,"autofocus","autofocus"],[14,4,"search"],[12],[13],[1,"\\n "],[13],[1,"\\n "],[18,3,null],[1,"\\n"],[13]],["&attrs","@value","&default"],false,["action","yield"]]',moduleName:"consul-ui/components/freetext-filter/index.hbs",isStrictMode:!1}) let u=(o((i=class extends l.default{get placeholder(){return this.args.placeholder||"Search"}get onsearch(){return this.args.onsearch||(()=>{})}change(e){this.onsearch(e)}keydown(e){13===e.keyCode&&e.preventDefault()}}).prototype,"change",[r.action],Object.getOwnPropertyDescriptor(i.prototype,"change"),i.prototype),o(i.prototype,"keydown",[r.action],Object.getOwnPropertyDescriptor(i.prototype,"keydown"),i.prototype),i) -e.default=u,(0,t.setComponentTemplate)(a,u)})),define("consul-ui/components/hashicorp-consul/index",["exports","@ember/component","@ember/template-factory","@glimmer/component","@ember/service"],(function(e,t,n,l,r){var i,o,a +e.default=u,(0,t.setComponentTemplate)(a,u)})),define("consul-ui/components/hashicorp-consul/index",["exports","@ember/component","@ember/template-factory","@glimmer/component","@ember/service"],(function(e,t,n,l,r){var i,o,a,u,s +function c(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function d(e,t,n,l,r){var i={} +return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +const p=(0,n.createTemplateFactory)({id:"dpUxCs9Y",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"hashicorp-consul"],[17,1]],null,[["notifications","side-nav","main"],[[[[1,"\\n"],[42,[28,[37,2],[[28,[37,2],[[33,3,["queue"]]],null]],null],null,[[[1," "],[8,[30,2,["Notification"]],null,[["@delay","@sticky"],[[28,[37,4],[[30,3,["timeout"]],[30,3,["extendedTimeout"]]],null],[30,3,["sticky"]]]],[["default"],[[[[1,"\\n"],[41,[30,3,["dom"]],[[[1," "],[2,[30,3,["dom"]]],[1,"\\n"]],[]],[[[44,[[28,[37,7],[[30,3,["type"]]],null],[28,[37,7],[[30,3,["action"]]],null]],[[[1," "],[8,[39,8],[[24,"data-notification",""]],[["@color"],[[52,[28,[37,9],[[30,4],"error"],null],"critical",[30,4]]]],[["default"],[[[[1,"\\n "],[8,[30,6,["Title"]],null,null,[["default"],[[[[1,[28,[35,10],[[30,4]],null]],[1,"!"]],[]]]]],[1,"\\n "],[8,[30,6,["Description"]],null,null,[["default"],[[[[1,"\\n"],[41,[28,[37,9],[[30,5],"logout"],null],[[[41,[28,[37,9],[[30,4],"success"],null],[[[1," "],[1,[28,[35,11],["components.hashicorp-consul.notifications.logged-out"],null]],[1,"\\n"]],[]],[[[1," "],[1,[28,[35,11],["components.hashicorp-consul.notifications.logged-out-error"],null]],[1,"\\n"]],[]]]],[]],[[[41,[28,[37,9],[[30,5],"authorize"],null],[[[41,[28,[37,9],[[30,4],"success"],null],[[[1," "],[1,[28,[35,11],["components.hashicorp-consul.notifications.logged-in"],null]],[1,"\\n"]],[]],[[[1," "],[1,[28,[35,11],["components.hashicorp-consul.notifications.logged-in-error"],null]],[1,"\\n"]],[]]]],[]],[[[41,[28,[37,12],[[28,[37,9],[[30,5],"use"],null],[28,[37,9],[[30,3,["model"]],"token"],null]],null],[[[1," "],[8,[39,13],null,[["@type","@status","@item","@error"],[[30,5],[30,4],[30,3,["item"]],[30,3,["error"]]]],null],[1,"\\n"]],[]],[[[41,[28,[37,9],[[30,3,["model"]],"intention"],null],[[[1," "],[8,[39,14],null,[["@type","@status","@item","@error"],[[30,5],[30,4],[30,3,["item"]],[30,3,["error"]]]],null],[1,"\\n"]],[]],[[[41,[28,[37,9],[[30,3,["model"]],"role"],null],[[[1," "],[8,[39,15],null,[["@type","@status","@item","@error"],[[30,5],[30,4],[30,3,["item"]],[30,3,["error"]]]],null],[1,"\\n"]],[]],[[[41,[28,[37,9],[[30,3,["model"]],"policy"],null],[[[1," "],[8,[39,16],null,[["@type","@status","@item","@error"],[[30,5],[30,4],[30,3,["item"]],[30,3,["error"]]]],null],[1,"\\n "]],[]],null]],[]]]],[]]]],[]]],[1," "]],[]]]],[]]],[1," "]],[]]]]],[1,"\\n "]],[6]]]]],[1,"\\n\\n"]],[4,5]]]],[]]],[1," "]],[]]]]],[1,"\\n"]],[3]],null],[1,"\\n "]],[2]],[[[1,"\\n "],[8,[39,17],[[24,0,"consul-side-nav"]],[["@hasA11yRefocus","@isResponsive"],[false,false]],[["header","body","footer"],[[[[1,"\\n "],[8,[39,18],null,null,[["logo","actions"],[[[[1,"\\n "],[8,[39,19],null,[["@icon","@ariaLabel","@href","@isHrefExternal"],["consul-color","Consul",[28,[37,20],["index"],[["params"],[[28,[37,21],null,[["peer"],[[27]]]]]]],false]],null],[1,"\\n "]],[]],[[[1,"\\n "],[8,[39,22],[[24,0,"hds-side-nav__dropdown"]],[["@listPosition"],["bottom-left"]],[["default"],[[[[1,"\\n "],[8,[30,7,["ToggleIcon"]],null,[["@icon","@text"],["help","Help & Support menu"]],null],[1,"\\n "],[8,[39,23],null,[["@dropdown"],[[30,7]]],null],[1,"\\n "],[8,[30,7,["Interactive"]],null,[["@href","@isHrefExternal","@text"],[[28,[37,24],["CONSUL_DOCS_URL"],null],true,[28,[37,11],["components.hashicorp-consul.side-nav.support-menu.docs"],null]]],null],[1,"\\n "],[8,[30,7,["Interactive"]],null,[["@href","@isHrefExternal","@text"],[[28,[37,25],[[28,[37,24],["CONSUL_DOCS_LEARN_URL"],null],"/consul"],null],true,[28,[37,11],["components.hashicorp-consul.side-nav.support-menu.tutorials"],null]]],null],[1,"\\n "],[8,[30,7,["Interactive"]],null,[["@href","@isHrefExternal","@text"],[[28,[37,24],["CONSUL_REPO_ISSUES_URL"],null],true,[28,[37,11],["components.hashicorp-consul.side-nav.support-menu.feedback"],null]]],null],[1,"\\n "]],[7]]]]],[1,"\\n\\n "],[8,[39,26],null,[["@dc","@partition","@nspace","@onchange"],[[30,8],[30,9],[30,10],[30,11]]],[["default"],[[[[1,"\\n "],[8,[39,27],null,[["@target","@name","@value"],[[30,0],"tokenSelector",[30,12]]],null],[1,"\\n "]],[12]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]],[[[1,"\\n "],[8,[39,28],[[24,0,"hds-side-nav-hide-when-minimized consul-side-nav__selector-group"]],null,[["default"],[[[[1,"\\n "],[8,[39,29],null,[["@list"],[[30,13]]],null],[1,"\\n "],[8,[39,30],null,[["@list","@dc","@partition","@nspace","@dcs"],[[30,13],[30,8],[30,9],[30,10],[30,14]]],null],[1,"\\n\\n "],[8,[39,31],null,[["@dc","@partition","@nspace","@partitions","@list","@onchange"],[[30,8],[30,9],[30,10],[30,0,["partitions"]],[30,13],[28,[37,32],[[30,0],[28,[37,33],[[30,0,["partitions"]]],null]],[["value"],["data"]]]]],null],[1,"\\n "],[8,[39,34],null,[["@list","@dc","@partition","@nspace","@nspaces","@onchange"],[[30,13],[30,8],[30,9],[30,10],[30,0,["nspaces"]],[28,[37,32],[[30,0],[28,[37,33],[[30,0,["nspaces"]]],null]],[["value"],["data"]]]]],null],[1,"\\n "]],[13]]]]],[1,"\\n "],[8,[39,28],[[24,0,"hds-side-nav-hide-when-minimized"]],null,[["default"],[[[[1,"\\n"],[41,[28,[37,35],["access overview"],null],[[[1," "],[8,[30,15,["Link"]],null,[["@text","@route","@models","@query","@isActive"],[[28,[37,11],["components.hashicorp-consul.side-nav.overview"],null],"dc.show",[28,[37,36],[[30,8,["Name"]]],null],[28,[37,21],null,[["peer"],[[27]]]],[28,[37,37],["dc.show",[30,8,["Name"]]],null]]],null],[1,"\\n"]],[]],null],[41,[28,[37,35],["read services"],null],[[[1," "],[8,[30,15,["Link"]],null,[["@text","@href","@isHrefExternal","@isActive"],[[28,[37,11],["components.hashicorp-consul.side-nav.services"],null],[28,[37,20],["dc.services",[30,8,["Name"]]],[["params"],[[28,[37,21],null,[["peer"],[[27]]]]]]],false,[28,[37,37],["dc.services",[30,8,["Name"]]],null]]],null],[1,"\\n"]],[]],null],[41,[28,[37,35],["read nodes"],null],[[[1," "],[8,[30,15,["Link"]],null,[["@text","@href","@isHrefExternal","@isActive"],[[28,[37,11],["components.hashicorp-consul.side-nav.nodes"],null],[28,[37,20],["dc.nodes",[30,8,["Name"]]],[["params"],[[28,[37,21],null,[["peer"],[[27]]]]]]],false,[28,[37,37],["dc.nodes",[30,8,["Name"]]],null]]],null],[1,"\\n"]],[]],null],[41,[28,[37,35],["read kv"],null],[[[1," "],[8,[30,15,["Link"]],null,[["@text","@href","@isHrefExternal","@isActive"],[[28,[37,11],["components.hashicorp-consul.side-nav.kv"],null],[28,[37,20],["dc.kv",[30,8,["Name"]]],[["params"],[[28,[37,21],null,[["peer"],[[27]]]]]]],false,[28,[37,37],["dc.kv",[30,8,["Name"]]],null]]],null],[1,"\\n"]],[]],null],[41,[28,[37,35],["read intentions"],null],[[[1," "],[8,[30,15,["Link"]],null,[["@text","@href","@isHrefExternal","@isActive"],[[28,[37,11],["components.hashicorp-consul.side-nav.intentions"],null],[28,[37,20],["dc.intentions",[30,8,["Name"]]],[["params"],[[28,[37,21],null,[["peer"],[[27]]]]]]],false,[28,[37,37],["dc.intentions",[30,8,["Name"]]],null]]],null],[1,"\\n"]],[]],null],[1,"\\n "],[8,[39,38],null,[["@dc","@partition","@nspace","@list"],[[30,8],[30,9],[30,10],[30,15]]],null],[1,"\\n "],[8,[39,39],null,[["@dc","@partition","@nspace","@list"],[[30,8],[30,9],[30,10],[30,15]]],null],[1,"\\n "]],[15]]]]],[1,"\\n "]],[]],[[[1,"\\n "],[10,"footer"],[14,"role","contentinfo"],[12],[1,"\\n "],[8,[39,40],[[24,0,"hds-side-nav-hide-when-minimized"]],[["@size","@color"],["100","disabled"]],[["default"],[[[[1,"\\n "],[1,[28,[35,11],["components.hashicorp-consul.side-nav.footer"],[["version"],[[30,0,["consulVersion"]]]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[2,[28,[37,25],["\x3c!-- ",[28,[37,24],["CONSUL_GIT_SHA"],null],"--\x3e"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]],[[[1,"\\n "],[18,16,[[28,[37,21],null,[["login"],[[52,[30,0,["tokenSelector"]],[30,0,["tokenSelector"]],[28,[37,21],null,[["open","close"],[[27],[27]]]]]]]]]],[1,"\\n "]],[]]]]]],["&attrs","app","flash","status","type","T","dd","@dc","@partition","@nspace","@onchange","selector","SNL","@dcs","SNL","&default"],false,["app","each","-track-array","flashMessages","sub","if","let","lowercase","hds/toast","eq","capitalize","t","or","consul/token/notifications","consul/intention/notifications","consul/role/notifications","consul/policy/notifications","hds/side-nav","hds/side-nav/header","hds/side-nav/header/home-link","href-to","hash","hds/dropdown","debug/navigation","env","concat","consul/token/selector","ref","hds/side-nav/list","hcp-nav-item","consul/datacenter/selector","consul/partition/selector","action","mut","consul/nspace/selector","can","array","is-href","consul/acl/selector","consul/peer/selector","hds/text/display","yield"]]',moduleName:"consul-ui/components/hashicorp-consul/index.hbs",isStrictMode:!1}) +let f=(i=(0,r.inject)("flashMessages"),o=(0,r.inject)("env"),a=class extends l.default{constructor(){super(...arguments),c(this,"flashMessages",u,this),c(this,"env",s,this)}get consulVersion(){const e=["","oss"].includes(this.env.var("CONSUL_BINARY_TYPE"))?"":"+ent" +return`${this.env.var("CONSUL_VERSION")}${e}`}},u=d(a.prototype,"flashMessages",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),s=d(a.prototype,"env",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),a) +e.default=f,(0,t.setComponentTemplate)(p,f)})) +define("consul-ui/components/hcp-nav-item/index",["exports","@ember/component","@ember/template-factory","@glimmer/component","@ember/service"],(function(e,t,n,l,r){var i,o Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -const u=(0,n.createTemplateFactory)({id:"xnhilElo",block:'[[[1,"\\n"],[8,[39,0],[[24,0,"hashicorp-consul"],[17,1]],null,[["notifications","side-nav","main"],[[[[1,"\\n"],[42,[28,[37,2],[[28,[37,2],[[33,3,["queue"]]],null]],null],null,[[[1," "],[8,[30,2,["Notification"]],null,[["@delay","@sticky"],[[28,[37,4],[[30,3,["timeout"]],[30,3,["extendedTimeout"]]],null],[30,3,["sticky"]]]],[["default"],[[[[1,"\\n"],[41,[30,3,["dom"]],[[[1," "],[2,[30,3,["dom"]]],[1,"\\n"]],[]],[[[44,[[28,[37,7],[[30,3,["type"]]],null],[28,[37,7],[[30,3,["action"]]],null]],[[[1," "],[8,[39,8],[[24,"data-notification",""]],[["@color"],[[52,[28,[37,9],[[30,4],"error"],null],"critical",[30,4]]]],[["default"],[[[[1,"\\n "],[8,[30,6,["Title"]],null,null,[["default"],[[[[1,[28,[35,10],[[30,4]],null]],[1,"!"]],[]]]]],[1,"\\n "],[8,[30,6,["Description"]],null,null,[["default"],[[[[1,"\\n"],[41,[28,[37,9],[[30,5],"logout"],null],[[[41,[28,[37,9],[[30,4],"success"],null],[[[1," "],[1,[28,[35,11],["components.hashicorp-consul.notifications.logged-out"],null]],[1,"\\n"]],[]],[[[1," "],[1,[28,[35,11],["components.hashicorp-consul.notifications.logged-out-error"],null]],[1,"\\n"]],[]]]],[]],[[[41,[28,[37,9],[[30,5],"authorize"],null],[[[41,[28,[37,9],[[30,4],"success"],null],[[[1," "],[1,[28,[35,11],["components.hashicorp-consul.notifications.logged-in"],null]],[1,"\\n"]],[]],[[[1," "],[1,[28,[35,11],["components.hashicorp-consul.notifications.logged-in-error"],null]],[1,"\\n"]],[]]]],[]],[[[41,[28,[37,12],[[28,[37,9],[[30,5],"use"],null],[28,[37,9],[[30,3,["model"]],"token"],null]],null],[[[1," "],[8,[39,13],null,[["@type","@status","@item","@error"],[[30,5],[30,4],[30,3,["item"]],[30,3,["error"]]]],null],[1,"\\n"]],[]],[[[41,[28,[37,9],[[30,3,["model"]],"intention"],null],[[[1," "],[8,[39,14],null,[["@type","@status","@item","@error"],[[30,5],[30,4],[30,3,["item"]],[30,3,["error"]]]],null],[1,"\\n"]],[]],[[[41,[28,[37,9],[[30,3,["model"]],"role"],null],[[[1," "],[8,[39,15],null,[["@type","@status","@item","@error"],[[30,5],[30,4],[30,3,["item"]],[30,3,["error"]]]],null],[1,"\\n"]],[]],[[[41,[28,[37,9],[[30,3,["model"]],"policy"],null],[[[1," "],[8,[39,16],null,[["@type","@status","@item","@error"],[[30,5],[30,4],[30,3,["item"]],[30,3,["error"]]]],null],[1,"\\n "]],[]],null]],[]]]],[]]]],[]]],[1," "]],[]]]],[]]],[1," "]],[]]]]],[1,"\\n "]],[6]]]]],[1,"\\n\\n"]],[4,5]]]],[]]],[1," "]],[]]]]],[1,"\\n"]],[3]],null],[1,"\\n "]],[2]],[[[1,"\\n "],[8,[39,17],[[24,0,"consul-side-nav"]],[["@isResponsive"],[false]],[["header","body","footer"],[[[[1,"\\n "],[8,[39,18],null,null,[["logo","actions"],[[[[1,"\\n "],[8,[39,19],null,[["@icon","@ariaLabel","@href","@isHrefExternal"],["consul-color","Consul",[28,[37,20],["index"],[["params"],[[28,[37,21],null,[["peer"],[[27]]]]]]],false]],null],[1,"\\n "]],[]],[[[1,"\\n "],[8,[39,22],[[24,0,"hds-side-nav__dropdown"]],[["@listPosition"],["bottom-left"]],[["default"],[[[[1,"\\n "],[8,[30,7,["ToggleIcon"]],null,[["@icon","@text"],["help","Help & Support menu"]],null],[1,"\\n "],[8,[39,23],null,[["@dropdown"],[[30,7]]],null],[1,"\\n "],[8,[30,7,["Interactive"]],null,[["@href","@isHrefExternal","@text"],[[28,[37,24],["CONSUL_DOCS_URL"],null],true,[28,[37,11],["components.hashicorp-consul.side-nav.support-menu.docs"],null]]],null],[1,"\\n "],[8,[30,7,["Interactive"]],null,[["@href","@isHrefExternal","@text"],[[28,[37,25],[[28,[37,24],["CONSUL_DOCS_LEARN_URL"],null],"/consul"],null],true,[28,[37,11],["components.hashicorp-consul.side-nav.support-menu.tutorials"],null]]],null],[1,"\\n "],[8,[30,7,["Interactive"]],null,[["@href","@isHrefExternal","@text"],[[28,[37,24],["CONSUL_REPO_ISSUES_URL"],null],true,[28,[37,11],["components.hashicorp-consul.side-nav.support-menu.feedback"],null]]],null],[1,"\\n "]],[7]]]]],[1,"\\n\\n "],[8,[39,26],null,[["@dc","@partition","@nspace","@onchange"],[[30,8],[30,9],[30,10],[30,11]]],[["default"],[[[[1,"\\n "],[8,[39,27],null,[["@target","@name","@value"],[[30,0],"tokenSelector",[30,12]]],null],[1,"\\n "]],[12]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]],[[[1,"\\n "],[8,[39,28],[[24,0,"hds-side-nav-hide-when-minimized consul-side-nav__selector-group"]],null,[["default"],[[[[1,"\\n "],[8,[39,29],null,[["@list"],[[30,13]]],null],[1,"\\n "],[8,[39,30],null,[["@list","@dc","@partition","@nspace","@dcs"],[[30,13],[30,8],[30,9],[30,10],[30,14]]],null],[1,"\\n\\n "],[8,[39,31],null,[["@dc","@partition","@nspace","@partitions","@list","@onchange"],[[30,8],[30,9],[30,10],[30,0,["partitions"]],[30,13],[28,[37,32],[[30,0],[28,[37,33],[[30,0,["partitions"]]],null]],[["value"],["data"]]]]],null],[1,"\\n "],[8,[39,34],null,[["@list","@dc","@partition","@nspace","@nspaces","@onchange"],[[30,13],[30,8],[30,9],[30,10],[30,0,["nspaces"]],[28,[37,32],[[30,0],[28,[37,33],[[30,0,["nspaces"]]],null]],[["value"],["data"]]]]],null],[1,"\\n "]],[13]]]]],[1,"\\n "],[8,[39,28],[[24,0,"hds-side-nav-hide-when-minimized"]],null,[["default"],[[[[1,"\\n"],[41,[28,[37,35],["access overview"],null],[[[1," "],[8,[30,15,["Link"]],null,[["@text","@route","@models","@query","@isActive"],[[28,[37,11],["components.hashicorp-consul.side-nav.overview"],null],"dc.show",[28,[37,36],[[30,8,["Name"]]],null],[28,[37,21],null,[["peer"],[[27]]]],[28,[37,37],["dc.show",[30,8,["Name"]]],null]]],null],[1,"\\n"]],[]],null],[41,[28,[37,35],["read services"],null],[[[1," "],[8,[30,15,["Link"]],null,[["@text","@href","@isHrefExternal","@isActive"],[[28,[37,11],["components.hashicorp-consul.side-nav.services"],null],[28,[37,20],["dc.services",[30,8,["Name"]]],[["params"],[[28,[37,21],null,[["peer"],[[27]]]]]]],false,[28,[37,37],["dc.services",[30,8,["Name"]]],null]]],null],[1,"\\n"]],[]],null],[41,[28,[37,35],["read nodes"],null],[[[1," "],[8,[30,15,["Link"]],null,[["@text","@href","@isHrefExternal","@isActive"],[[28,[37,11],["components.hashicorp-consul.side-nav.nodes"],null],[28,[37,20],["dc.nodes",[30,8,["Name"]]],[["params"],[[28,[37,21],null,[["peer"],[[27]]]]]]],false,[28,[37,37],["dc.nodes",[30,8,["Name"]]],null]]],null],[1,"\\n"]],[]],null],[41,[28,[37,35],["read kv"],null],[[[1," "],[8,[30,15,["Link"]],null,[["@text","@href","@isHrefExternal","@isActive"],[[28,[37,11],["components.hashicorp-consul.side-nav.kv"],null],[28,[37,20],["dc.kv",[30,8,["Name"]]],[["params"],[[28,[37,21],null,[["peer"],[[27]]]]]]],false,[28,[37,37],["dc.kv",[30,8,["Name"]]],null]]],null],[1,"\\n"]],[]],null],[41,[28,[37,35],["read intentions"],null],[[[1," "],[8,[30,15,["Link"]],null,[["@text","@href","@isHrefExternal","@isActive"],[[28,[37,11],["components.hashicorp-consul.side-nav.intentions"],null],[28,[37,20],["dc.intentions",[30,8,["Name"]]],[["params"],[[28,[37,21],null,[["peer"],[[27]]]]]]],false,[28,[37,37],["dc.intentions",[30,8,["Name"]]],null]]],null],[1,"\\n"]],[]],null],[1,"\\n "],[8,[39,38],null,[["@dc","@partition","@nspace","@list"],[[30,8],[30,9],[30,10],[30,15]]],null],[1,"\\n "],[8,[39,39],null,[["@dc","@partition","@nspace","@list"],[[30,8],[30,9],[30,10],[30,15]]],null],[1,"\\n "]],[15]]]]],[1,"\\n "]],[]],[[[1,"\\n "],[10,"footer"],[14,"role","contentinfo"],[12],[1,"\\n "],[8,[39,40],[[24,0,"hds-side-nav-hide-when-minimized"]],[["@size","@color"],["100","disabled"]],[["default"],[[[[1,"\\n "],[1,[28,[35,11],["components.hashicorp-consul.side-nav.footer"],[["version"],[[28,[37,24],["CONSUL_VERSION"],null]]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[2,[28,[37,25],["\x3c!-- ",[28,[37,24],["CONSUL_GIT_SHA"],null],"--\x3e"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]],[[[1,"\\n "],[18,16,[[28,[37,21],null,[["login"],[[52,[30,0,["tokenSelector"]],[30,0,["tokenSelector"]],[28,[37,21],null,[["open","close"],[[27],[27]]]]]]]]]],[1,"\\n "]],[]]]]]],["&attrs","app","flash","status","type","T","dd","@dc","@partition","@nspace","@onchange","selector","SNL","@dcs","SNL","&default"],false,["app","each","-track-array","flashMessages","sub","if","let","lowercase","hds/toast","eq","capitalize","t","or","consul/token/notifications","consul/intention/notifications","consul/role/notifications","consul/policy/notifications","hds/side-nav","hds/side-nav/header","hds/side-nav/header/home-link","href-to","hash","hds/dropdown","debug/navigation","env","concat","consul/token/selector","ref","hds/side-nav/list","consul/hcp/home","consul/datacenter/selector","consul/partition/selector","action","mut","consul/nspace/selector","can","array","is-href","consul/acl/selector","consul/peer/selector","hds/text/display","yield"]]',moduleName:"consul-ui/components/hashicorp-consul/index.hbs",isStrictMode:!1}) -let s=(i=(0,r.inject)("flashMessages"),o=class extends l.default{constructor(){var e,t,n,l -super(...arguments),e=this,t="flashMessages",l=this,(n=a)&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}},c=o.prototype,d="flashMessages",p=[i],f={configurable:!0,enumerable:!0,writable:!0,initializer:null},h={},Object.keys(f).forEach((function(e){h[e]=f[e]})),h.enumerable=!!h.enumerable,h.configurable=!!h.configurable,("value"in h||h.initializer)&&(h.writable=!0),h=p.slice().reverse().reduce((function(e,t){return t(c,d,e)||e}),h),m&&void 0!==h.initializer&&(h.value=h.initializer?h.initializer.call(m):void 0,h.initializer=void 0),void 0===h.initializer&&(Object.defineProperty(c,d,h),h=null),a=h,o) -var c,d,p,f,m,h -e.default=s,(0,t.setComponentTemplate)(u,s)})) -define("consul-ui/components/hds/accordion/index",["exports","@hashicorp/design-system-components/components/hds/accordion/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/accordion/item/button",["exports","@hashicorp/design-system-components/components/hds/accordion/item/button"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/accordion/item/index",["exports","@hashicorp/design-system-components/components/hds/accordion/item/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/alert/description",["exports","@hashicorp/design-system-components/components/hds/alert/description"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/alert/index",["exports","@hashicorp/design-system-components/components/hds/alert/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/alert/title",["exports","@hashicorp/design-system-components/components/hds/alert/title"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-footer/copyright",["exports","@hashicorp/design-system-components/components/hds/app-footer/copyright"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-footer/index",["exports","@hashicorp/design-system-components/components/hds/app-footer/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-footer/item",["exports","@hashicorp/design-system-components/components/hds/app-footer/item"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-footer/legal-links",["exports","@hashicorp/design-system-components/components/hds/app-footer/legal-links"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-footer/link",["exports","@hashicorp/design-system-components/components/hds/app-footer/link"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-footer/status-link",["exports","@hashicorp/design-system-components/components/hds/app-footer/status-link"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-frame/index",["exports","@hashicorp/design-system-components/components/hds/app-frame/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-frame/parts/footer",["exports","@hashicorp/design-system-components/components/hds/app-frame/parts/footer"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-frame/parts/header",["exports","@hashicorp/design-system-components/components/hds/app-frame/parts/header"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-frame/parts/main",["exports","@hashicorp/design-system-components/components/hds/app-frame/parts/main"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-frame/parts/modals",["exports","@hashicorp/design-system-components/components/hds/app-frame/parts/modals"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-frame/parts/sidebar",["exports","@hashicorp/design-system-components/components/hds/app-frame/parts/sidebar"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/application-state/body",["exports","@hashicorp/design-system-components/components/hds/application-state/body"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/application-state/footer",["exports","@hashicorp/design-system-components/components/hds/application-state/footer"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/application-state/header",["exports","@hashicorp/design-system-components/components/hds/application-state/header"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/application-state/index",["exports","@hashicorp/design-system-components/components/hds/application-state/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/avatar/index",["exports","@hashicorp/design-system-components/components/hds/avatar/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/badge-count/index",["exports","@hashicorp/design-system-components/components/hds/badge-count/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/badge/index",["exports","@hashicorp/design-system-components/components/hds/badge/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/breadcrumb/index",["exports","@hashicorp/design-system-components/components/hds/breadcrumb/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/breadcrumb/item",["exports","@hashicorp/design-system-components/components/hds/breadcrumb/item"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/breadcrumb/truncation",["exports","@hashicorp/design-system-components/components/hds/breadcrumb/truncation"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/button-set/index",["exports","@hashicorp/design-system-components/components/hds/button-set/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/button/index",["exports","@hashicorp/design-system-components/components/hds/button/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) -define("consul-ui/components/hds/card/container",["exports","@hashicorp/design-system-components/components/hds/card/container"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/copy/button/index",["exports","@hashicorp/design-system-components/components/hds/copy/button/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/copy/snippet/index",["exports","@hashicorp/design-system-components/components/hds/copy/snippet/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/disclosure-primitive/index",["exports","@hashicorp/design-system-components/components/hds/disclosure-primitive/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dismiss-button/index",["exports","@hashicorp/design-system-components/components/hds/dismiss-button/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/footer",["exports","@hashicorp/design-system-components/components/hds/dropdown/footer"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/header",["exports","@hashicorp/design-system-components/components/hds/dropdown/header"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/index",["exports","@hashicorp/design-system-components/components/hds/dropdown/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/checkbox",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/checkbox"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/checkmark",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/checkmark"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/copy-item",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/copy-item"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/description",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/description"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/generic",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/generic"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/interactive",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/interactive"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/radio",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/radio"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/separator",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/separator"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/title",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/title"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/toggle/button",["exports","@hashicorp/design-system-components/components/hds/dropdown/toggle/button"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/toggle/chevron",["exports","@hashicorp/design-system-components/components/hds/dropdown/toggle/chevron"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/toggle/icon",["exports","@hashicorp/design-system-components/components/hds/dropdown/toggle/icon"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/flyout/body",["exports","@hashicorp/design-system-components/components/hds/flyout/body"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/flyout/description",["exports","@hashicorp/design-system-components/components/hds/flyout/description"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/flyout/footer",["exports","@hashicorp/design-system-components/components/hds/flyout/footer"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/flyout/header",["exports","@hashicorp/design-system-components/components/hds/flyout/header"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/flyout/index",["exports","@hashicorp/design-system-components/components/hds/flyout/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/checkbox/base",["exports","@hashicorp/design-system-components/components/hds/form/checkbox/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/checkbox/field",["exports","@hashicorp/design-system-components/components/hds/form/checkbox/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/checkbox/group",["exports","@hashicorp/design-system-components/components/hds/form/checkbox/group"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/error/index",["exports","@hashicorp/design-system-components/components/hds/form/error/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/error/message",["exports","@hashicorp/design-system-components/components/hds/form/error/message"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) -define("consul-ui/components/hds/form/field/index",["exports","@hashicorp/design-system-components/components/hds/form/field/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/fieldset/index",["exports","@hashicorp/design-system-components/components/hds/form/fieldset/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/file-input/base",["exports","@hashicorp/design-system-components/components/hds/form/file-input/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/file-input/field",["exports","@hashicorp/design-system-components/components/hds/form/file-input/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/helper-text/index",["exports","@hashicorp/design-system-components/components/hds/form/helper-text/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/indicator/index",["exports","@hashicorp/design-system-components/components/hds/form/indicator/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/label/index",["exports","@hashicorp/design-system-components/components/hds/form/label/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/legend/index",["exports","@hashicorp/design-system-components/components/hds/form/legend/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/masked-input/base",["exports","@hashicorp/design-system-components/components/hds/form/masked-input/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/masked-input/field",["exports","@hashicorp/design-system-components/components/hds/form/masked-input/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio-card/description",["exports","@hashicorp/design-system-components/components/hds/form/radio-card/description"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio-card/group",["exports","@hashicorp/design-system-components/components/hds/form/radio-card/group"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio-card/index",["exports","@hashicorp/design-system-components/components/hds/form/radio-card/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio-card/label",["exports","@hashicorp/design-system-components/components/hds/form/radio-card/label"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio/base",["exports","@hashicorp/design-system-components/components/hds/form/radio/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio/field",["exports","@hashicorp/design-system-components/components/hds/form/radio/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio/group",["exports","@hashicorp/design-system-components/components/hds/form/radio/group"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/select/base",["exports","@hashicorp/design-system-components/components/hds/form/select/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/select/field",["exports","@hashicorp/design-system-components/components/hds/form/select/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/text-input/base",["exports","@hashicorp/design-system-components/components/hds/form/text-input/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/text-input/field",["exports","@hashicorp/design-system-components/components/hds/form/text-input/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/textarea/base",["exports","@hashicorp/design-system-components/components/hds/form/textarea/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/textarea/field",["exports","@hashicorp/design-system-components/components/hds/form/textarea/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/toggle/base",["exports","@hashicorp/design-system-components/components/hds/form/toggle/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/toggle/field",["exports","@hashicorp/design-system-components/components/hds/form/toggle/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/toggle/group",["exports","@hashicorp/design-system-components/components/hds/form/toggle/group"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/visibility-toggle/index",["exports","@hashicorp/design-system-components/components/hds/form/visibility-toggle/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/icon-tile/index",["exports","@hashicorp/design-system-components/components/hds/icon-tile/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/interactive/index",["exports","@hashicorp/design-system-components/components/hds/interactive/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/link/inline",["exports","@hashicorp/design-system-components/components/hds/link/inline"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) -define("consul-ui/components/hds/link/standalone",["exports","@hashicorp/design-system-components/components/hds/link/standalone"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/menu-primitive/index",["exports","@hashicorp/design-system-components/components/hds/menu-primitive/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/modal/body",["exports","@hashicorp/design-system-components/components/hds/modal/body"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/modal/footer",["exports","@hashicorp/design-system-components/components/hds/modal/footer"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/modal/header",["exports","@hashicorp/design-system-components/components/hds/modal/header"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/modal/index",["exports","@hashicorp/design-system-components/components/hds/modal/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/page-header/actions",["exports","@hashicorp/design-system-components/components/hds/page-header/actions"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/page-header/badges",["exports","@hashicorp/design-system-components/components/hds/page-header/badges"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/page-header/description",["exports","@hashicorp/design-system-components/components/hds/page-header/description"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/page-header/index",["exports","@hashicorp/design-system-components/components/hds/page-header/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/page-header/subtitle",["exports","@hashicorp/design-system-components/components/hds/page-header/subtitle"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/page-header/title",["exports","@hashicorp/design-system-components/components/hds/page-header/title"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/compact/index",["exports","@hashicorp/design-system-components/components/hds/pagination/compact/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/info",["exports","@hashicorp/design-system-components/components/hds/pagination/info"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/nav/arrow",["exports","@hashicorp/design-system-components/components/hds/pagination/nav/arrow"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/nav/ellipsis",["exports","@hashicorp/design-system-components/components/hds/pagination/nav/ellipsis"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/nav/number",["exports","@hashicorp/design-system-components/components/hds/pagination/nav/number"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/numbered/index",["exports","@hashicorp/design-system-components/components/hds/pagination/numbered/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/size-selector",["exports","@hashicorp/design-system-components/components/hds/pagination/size-selector"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/reveal/index",["exports","@hashicorp/design-system-components/components/hds/reveal/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/reveal/toggle/button",["exports","@hashicorp/design-system-components/components/hds/reveal/toggle/button"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/segmented-group/index",["exports","@hashicorp/design-system-components/components/hds/segmented-group/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/separator/index",["exports","@hashicorp/design-system-components/components/hds/separator/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/base",["exports","@hashicorp/design-system-components/components/hds/side-nav/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/header/home-link",["exports","@hashicorp/design-system-components/components/hds/side-nav/header/home-link"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/header/icon-button",["exports","@hashicorp/design-system-components/components/hds/side-nav/header/icon-button"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/header/index",["exports","@hashicorp/design-system-components/components/hds/side-nav/header"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/index",["exports","@hashicorp/design-system-components/components/hds/side-nav/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/list/back-link",["exports","@hashicorp/design-system-components/components/hds/side-nav/list/back-link"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/list/index",["exports","@hashicorp/design-system-components/components/hds/side-nav/list/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) -define("consul-ui/components/hds/side-nav/list/item",["exports","@hashicorp/design-system-components/components/hds/side-nav/list/item"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/list/link",["exports","@hashicorp/design-system-components/components/hds/side-nav/list/link"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/list/title",["exports","@hashicorp/design-system-components/components/hds/side-nav/list/title"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/portal/index",["exports","@hashicorp/design-system-components/components/hds/side-nav/portal/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/portal/target",["exports","@hashicorp/design-system-components/components/hds/side-nav/portal/target"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/toggle-button",["exports","@hashicorp/design-system-components/components/hds/side-nav/toggle-button"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/stepper/step/indicator",["exports","@hashicorp/design-system-components/components/hds/stepper/step/indicator"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/stepper/task/indicator",["exports","@hashicorp/design-system-components/components/hds/stepper/task/indicator"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/table/index",["exports","@hashicorp/design-system-components/components/hds/table/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/table/td",["exports","@hashicorp/design-system-components/components/hds/table/td"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/table/th-sort",["exports","@hashicorp/design-system-components/components/hds/table/th-sort"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/table/th",["exports","@hashicorp/design-system-components/components/hds/table/th"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/table/tr",["exports","@hashicorp/design-system-components/components/hds/table/tr"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/tabs/index",["exports","@hashicorp/design-system-components/components/hds/tabs/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/tabs/panel",["exports","@hashicorp/design-system-components/components/hds/tabs/panel"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/tabs/tab",["exports","@hashicorp/design-system-components/components/hds/tabs/tab"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/tag/index",["exports","@hashicorp/design-system-components/components/hds/tag/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/text/body",["exports","@hashicorp/design-system-components/components/hds/text/body"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/text/code",["exports","@hashicorp/design-system-components/components/hds/text/code"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/text/display",["exports","@hashicorp/design-system-components/components/hds/text/display"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/text/index",["exports","@hashicorp/design-system-components/components/hds/text/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/toast/index",["exports","@hashicorp/design-system-components/components/hds/toast/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/tooltip-button/index",["exports","@hashicorp/design-system-components/components/hds/tooltip-button/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/yield/index",["exports","@hashicorp/design-system-components/components/hds/yield/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/informed-action/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +const a=(0,n.createTemplateFactory)({id:"l8c9YROO",block:'[[[1,"\\n"],[44,[[30,1],[28,[37,1],["CONSUL_HCP_URL"],null]],[[[41,[30,0,["shouldShowBackToHcpItem"]],[[[1," "],[8,[30,2,["BackLink"]],null,[["@text","@href","@isHrefExternal"],[[28,[37,3],["components.hashicorp-consul.side-nav.hcp"],null],[30,3],true]],null],[1,"\\n"]],[]],null]],[2,3]]]],["@list","SNL","hcpUrl"],false,["let","env","if","t"]]',moduleName:"consul-ui/components/hcp-nav-item/index.hbs",isStrictMode:!1}) +let u=(i=class extends l.default{constructor(){var e,t,n,l +super(...arguments),e=this,t="env",l=this,(n=o)&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}get shouldShowBackToHcpItem(){const e=!!this.env.var("CONSUL_HCP_URL") +return!!this.env.var("CONSUL_HCP_ENABLED")&&e}},s=i.prototype,c="env",d=[r.inject],p={configurable:!0,enumerable:!0,writable:!0,initializer:null},m={},Object.keys(p).forEach((function(e){m[e]=p[e]})),m.enumerable=!!m.enumerable,m.configurable=!!m.configurable,("value"in m||m.initializer)&&(m.writable=!0),m=d.slice().reverse().reduce((function(e,t){return t(s,c,e)||e}),m),f&&void 0!==m.initializer&&(m.value=m.initializer?m.initializer.call(f):void 0,m.initializer=void 0),void 0===m.initializer&&(Object.defineProperty(s,c,m),m=null),o=m,i) +var s,c,d,p,f,m +e.default=u,(0,t.setComponentTemplate)(a,u)})),define("consul-ui/components/hds/accordion/index",["exports","@hashicorp/design-system-components/components/hds/accordion/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/accordion/item/button",["exports","@hashicorp/design-system-components/components/hds/accordion/item/button"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/accordion/item/index",["exports","@hashicorp/design-system-components/components/hds/accordion/item/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/alert/description",["exports","@hashicorp/design-system-components/components/hds/alert/description"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/alert/index",["exports","@hashicorp/design-system-components/components/hds/alert/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/alert/title",["exports","@hashicorp/design-system-components/components/hds/alert/title"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-footer/copyright",["exports","@hashicorp/design-system-components/components/hds/app-footer/copyright"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-footer/index",["exports","@hashicorp/design-system-components/components/hds/app-footer/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-footer/item",["exports","@hashicorp/design-system-components/components/hds/app-footer/item"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-footer/legal-links",["exports","@hashicorp/design-system-components/components/hds/app-footer/legal-links"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-footer/link",["exports","@hashicorp/design-system-components/components/hds/app-footer/link"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-footer/status-link",["exports","@hashicorp/design-system-components/components/hds/app-footer/status-link"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-frame/index",["exports","@hashicorp/design-system-components/components/hds/app-frame/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-frame/parts/footer",["exports","@hashicorp/design-system-components/components/hds/app-frame/parts/footer"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-frame/parts/header",["exports","@hashicorp/design-system-components/components/hds/app-frame/parts/header"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-frame/parts/main",["exports","@hashicorp/design-system-components/components/hds/app-frame/parts/main"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-frame/parts/modals",["exports","@hashicorp/design-system-components/components/hds/app-frame/parts/modals"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/app-frame/parts/sidebar",["exports","@hashicorp/design-system-components/components/hds/app-frame/parts/sidebar"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/application-state/body",["exports","@hashicorp/design-system-components/components/hds/application-state/body"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/application-state/footer",["exports","@hashicorp/design-system-components/components/hds/application-state/footer"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/application-state/header",["exports","@hashicorp/design-system-components/components/hds/application-state/header"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/application-state/index",["exports","@hashicorp/design-system-components/components/hds/application-state/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/avatar/index",["exports","@hashicorp/design-system-components/components/hds/avatar/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/badge-count/index",["exports","@hashicorp/design-system-components/components/hds/badge-count/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/badge/index",["exports","@hashicorp/design-system-components/components/hds/badge/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/breadcrumb/index",["exports","@hashicorp/design-system-components/components/hds/breadcrumb/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/breadcrumb/item",["exports","@hashicorp/design-system-components/components/hds/breadcrumb/item"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/breadcrumb/truncation",["exports","@hashicorp/design-system-components/components/hds/breadcrumb/truncation"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/button-set/index",["exports","@hashicorp/design-system-components/components/hds/button-set/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) +define("consul-ui/components/hds/button/index",["exports","@hashicorp/design-system-components/components/hds/button/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/card/container",["exports","@hashicorp/design-system-components/components/hds/card/container"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/copy/button/index",["exports","@hashicorp/design-system-components/components/hds/copy/button/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/copy/snippet/index",["exports","@hashicorp/design-system-components/components/hds/copy/snippet/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/disclosure-primitive/index",["exports","@hashicorp/design-system-components/components/hds/disclosure-primitive/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dismiss-button/index",["exports","@hashicorp/design-system-components/components/hds/dismiss-button/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/footer",["exports","@hashicorp/design-system-components/components/hds/dropdown/footer"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/header",["exports","@hashicorp/design-system-components/components/hds/dropdown/header"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/index",["exports","@hashicorp/design-system-components/components/hds/dropdown/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/checkbox",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/checkbox"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/checkmark",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/checkmark"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/copy-item",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/copy-item"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/description",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/description"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/generic",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/generic"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/interactive",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/interactive"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/radio",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/radio"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/separator",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/separator"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/list-item/title",["exports","@hashicorp/design-system-components/components/hds/dropdown/list-item/title"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/toggle/button",["exports","@hashicorp/design-system-components/components/hds/dropdown/toggle/button"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/toggle/chevron",["exports","@hashicorp/design-system-components/components/hds/dropdown/toggle/chevron"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/dropdown/toggle/icon",["exports","@hashicorp/design-system-components/components/hds/dropdown/toggle/icon"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/flyout/body",["exports","@hashicorp/design-system-components/components/hds/flyout/body"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/flyout/description",["exports","@hashicorp/design-system-components/components/hds/flyout/description"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/flyout/footer",["exports","@hashicorp/design-system-components/components/hds/flyout/footer"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/flyout/header",["exports","@hashicorp/design-system-components/components/hds/flyout/header"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/flyout/index",["exports","@hashicorp/design-system-components/components/hds/flyout/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/checkbox/base",["exports","@hashicorp/design-system-components/components/hds/form/checkbox/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/checkbox/field",["exports","@hashicorp/design-system-components/components/hds/form/checkbox/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/checkbox/group",["exports","@hashicorp/design-system-components/components/hds/form/checkbox/group"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/error/index",["exports","@hashicorp/design-system-components/components/hds/form/error/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) +define("consul-ui/components/hds/form/error/message",["exports","@hashicorp/design-system-components/components/hds/form/error/message"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/field/index",["exports","@hashicorp/design-system-components/components/hds/form/field/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/fieldset/index",["exports","@hashicorp/design-system-components/components/hds/form/fieldset/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/file-input/base",["exports","@hashicorp/design-system-components/components/hds/form/file-input/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/file-input/field",["exports","@hashicorp/design-system-components/components/hds/form/file-input/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/helper-text/index",["exports","@hashicorp/design-system-components/components/hds/form/helper-text/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/indicator/index",["exports","@hashicorp/design-system-components/components/hds/form/indicator/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/label/index",["exports","@hashicorp/design-system-components/components/hds/form/label/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/legend/index",["exports","@hashicorp/design-system-components/components/hds/form/legend/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/masked-input/base",["exports","@hashicorp/design-system-components/components/hds/form/masked-input/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/masked-input/field",["exports","@hashicorp/design-system-components/components/hds/form/masked-input/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio-card/description",["exports","@hashicorp/design-system-components/components/hds/form/radio-card/description"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio-card/group",["exports","@hashicorp/design-system-components/components/hds/form/radio-card/group"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio-card/index",["exports","@hashicorp/design-system-components/components/hds/form/radio-card/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio-card/label",["exports","@hashicorp/design-system-components/components/hds/form/radio-card/label"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio/base",["exports","@hashicorp/design-system-components/components/hds/form/radio/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio/field",["exports","@hashicorp/design-system-components/components/hds/form/radio/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/radio/group",["exports","@hashicorp/design-system-components/components/hds/form/radio/group"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/select/base",["exports","@hashicorp/design-system-components/components/hds/form/select/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/select/field",["exports","@hashicorp/design-system-components/components/hds/form/select/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/text-input/base",["exports","@hashicorp/design-system-components/components/hds/form/text-input/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/text-input/field",["exports","@hashicorp/design-system-components/components/hds/form/text-input/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/textarea/base",["exports","@hashicorp/design-system-components/components/hds/form/textarea/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/textarea/field",["exports","@hashicorp/design-system-components/components/hds/form/textarea/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/toggle/base",["exports","@hashicorp/design-system-components/components/hds/form/toggle/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/toggle/field",["exports","@hashicorp/design-system-components/components/hds/form/toggle/field"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/toggle/group",["exports","@hashicorp/design-system-components/components/hds/form/toggle/group"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/form/visibility-toggle/index",["exports","@hashicorp/design-system-components/components/hds/form/visibility-toggle/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/icon-tile/index",["exports","@hashicorp/design-system-components/components/hds/icon-tile/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/interactive/index",["exports","@hashicorp/design-system-components/components/hds/interactive/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) +define("consul-ui/components/hds/link/inline",["exports","@hashicorp/design-system-components/components/hds/link/inline"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/link/standalone",["exports","@hashicorp/design-system-components/components/hds/link/standalone"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/menu-primitive/index",["exports","@hashicorp/design-system-components/components/hds/menu-primitive/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/modal/body",["exports","@hashicorp/design-system-components/components/hds/modal/body"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/modal/footer",["exports","@hashicorp/design-system-components/components/hds/modal/footer"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/modal/header",["exports","@hashicorp/design-system-components/components/hds/modal/header"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/modal/index",["exports","@hashicorp/design-system-components/components/hds/modal/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/page-header/actions",["exports","@hashicorp/design-system-components/components/hds/page-header/actions"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/page-header/badges",["exports","@hashicorp/design-system-components/components/hds/page-header/badges"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/page-header/description",["exports","@hashicorp/design-system-components/components/hds/page-header/description"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/page-header/index",["exports","@hashicorp/design-system-components/components/hds/page-header/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/page-header/subtitle",["exports","@hashicorp/design-system-components/components/hds/page-header/subtitle"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/page-header/title",["exports","@hashicorp/design-system-components/components/hds/page-header/title"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/compact/index",["exports","@hashicorp/design-system-components/components/hds/pagination/compact/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/info",["exports","@hashicorp/design-system-components/components/hds/pagination/info"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/nav/arrow",["exports","@hashicorp/design-system-components/components/hds/pagination/nav/arrow"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/nav/ellipsis",["exports","@hashicorp/design-system-components/components/hds/pagination/nav/ellipsis"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/nav/number",["exports","@hashicorp/design-system-components/components/hds/pagination/nav/number"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/numbered/index",["exports","@hashicorp/design-system-components/components/hds/pagination/numbered/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/pagination/size-selector",["exports","@hashicorp/design-system-components/components/hds/pagination/size-selector"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/reveal/index",["exports","@hashicorp/design-system-components/components/hds/reveal/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/reveal/toggle/button",["exports","@hashicorp/design-system-components/components/hds/reveal/toggle/button"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/segmented-group/index",["exports","@hashicorp/design-system-components/components/hds/segmented-group/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/separator/index",["exports","@hashicorp/design-system-components/components/hds/separator/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/base",["exports","@hashicorp/design-system-components/components/hds/side-nav/base"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/header/home-link",["exports","@hashicorp/design-system-components/components/hds/side-nav/header/home-link"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/header/icon-button",["exports","@hashicorp/design-system-components/components/hds/side-nav/header/icon-button"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/header/index",["exports","@hashicorp/design-system-components/components/hds/side-nav/header"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/index",["exports","@hashicorp/design-system-components/components/hds/side-nav/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/list/back-link",["exports","@hashicorp/design-system-components/components/hds/side-nav/list/back-link"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) +define("consul-ui/components/hds/side-nav/list/index",["exports","@hashicorp/design-system-components/components/hds/side-nav/list/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/list/item",["exports","@hashicorp/design-system-components/components/hds/side-nav/list/item"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/list/link",["exports","@hashicorp/design-system-components/components/hds/side-nav/list/link"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/list/title",["exports","@hashicorp/design-system-components/components/hds/side-nav/list/title"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/portal/index",["exports","@hashicorp/design-system-components/components/hds/side-nav/portal/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/portal/target",["exports","@hashicorp/design-system-components/components/hds/side-nav/portal/target"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/side-nav/toggle-button",["exports","@hashicorp/design-system-components/components/hds/side-nav/toggle-button"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/stepper/step/indicator",["exports","@hashicorp/design-system-components/components/hds/stepper/step/indicator"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/stepper/task/indicator",["exports","@hashicorp/design-system-components/components/hds/stepper/task/indicator"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/table/index",["exports","@hashicorp/design-system-components/components/hds/table/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/table/td",["exports","@hashicorp/design-system-components/components/hds/table/td"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/table/th-sort",["exports","@hashicorp/design-system-components/components/hds/table/th-sort"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/table/th",["exports","@hashicorp/design-system-components/components/hds/table/th"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/table/tr",["exports","@hashicorp/design-system-components/components/hds/table/tr"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/tabs/index",["exports","@hashicorp/design-system-components/components/hds/tabs/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/tabs/panel",["exports","@hashicorp/design-system-components/components/hds/tabs/panel"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/tabs/tab",["exports","@hashicorp/design-system-components/components/hds/tabs/tab"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/tag/index",["exports","@hashicorp/design-system-components/components/hds/tag/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/text/body",["exports","@hashicorp/design-system-components/components/hds/text/body"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/text/code",["exports","@hashicorp/design-system-components/components/hds/text/code"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/text/display",["exports","@hashicorp/design-system-components/components/hds/text/display"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/text/index",["exports","@hashicorp/design-system-components/components/hds/text/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/toast/index",["exports","@hashicorp/design-system-components/components/hds/toast/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/tooltip-button/index",["exports","@hashicorp/design-system-components/components/hds/tooltip-button/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/hds/yield/index",["exports","@hashicorp/design-system-components/components/hds/yield/index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/informed-action/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"OpUoOs2J",block:'[[[1,"\\n"],[11,0],[24,0,"informed-action"],[17,1],[12],[1,"\\n "],[10,0],[12],[1,"\\n "],[10,"header"],[12],[1,"\\n "],[18,2,null],[1,"\\n "],[13],[1,"\\n "],[18,3,null],[1,"\\n "],[13],[1,"\\n "],[10,"ul"],[12],[1,"\\n "],[18,4,[[28,[37,1],null,[["Action"],[[50,"anonymous",0,null,[["tagName"],["li"]]]]]]]],[1,"\\n "],[13],[1,"\\n"],[13]],["&attrs","&header","&body","&actions"],false,["yield","hash","component"]]',moduleName:"consul-ui/components/informed-action/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/ivy-codemirror",["exports","ivy-codemirror/components/ivy-codemirror"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/jwt-source/index",["exports","@glimmer/component","@ember/service","consul-ui/utils/dom/event-source"],(function(e,t,n,l){var r,i,o,a,u @@ -1340,14 +1347,14 @@ this.set("height",Math.max(0,r)),this.updateItems(),this.updateScrollPosition()} if(t.target.checked&&e!==this.checked){(0,r.set)(this,"checked",parseInt(e)),this.$row=this.dom.closest("li",t.target),this.$row.style.zIndex=1 const n=this.dom.sibling(t.target,"div") n.getBoundingClientRect().top+n.clientHeight>this.dom.element('footer[role="contentinfo"]').getBoundingClientRect().top?n.classList.add("above"):n.classList.remove("above")}else{this.dom.sibling(t.target,"div").classList.remove("above"),(0,r.set)(this,"checked",null),this.$row.style.zIndex=null}}}})) -e.default=c})),define("consul-ui/components/maybe-in-element",["exports","ember-maybe-in-element/components/maybe-in-element"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/menu-panel/index",["exports","@ember/component","@ember/template-factory","@ember/service","@ember/runloop","@ember/object","block-slots"],(function(e,t,n,l,r,i,o){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=c})),define("consul-ui/components/maybe-in-element",["exports","ember-maybe-in-element/components/maybe-in-element"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) +define("consul-ui/components/menu-panel/index",["exports","@ember/component","@ember/template-factory","@ember/service","@ember/runloop","@ember/object","block-slots"],(function(e,t,n,l,r,i,o){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const a=(0,n.createTemplateFactory)({id:"AUkkhvKM",block:'[[[1,"\\n"],[18,3,null],[1,"\\n"],[44,[[28,[37,2],null,[["change"],[[28,[37,3],[[30,0],"change"],null]]]]],[[[11,0],[16,0,[28,[37,4],[[28,[37,5],["menu-panel"],null],[28,[37,5],["menu-panel-deprecated"],null],[28,[37,5],[[33,6]],null],[28,[37,5],[[33,7],"confirmation"],null]],null]],[4,[38,8],[[28,[37,3],[[30,0],"connect"],null]],null],[12],[1,"\\n "],[8,[39,9],null,[["@name"],["controls"]],[["default"],[[[[1,"\\n "],[18,3,[[30,1]]],[1,"\\n "]],[]]]]],[1,"\\n"],[6,[39,9],null,[["name"],["header"]],[["default","else"],[[[[1," "],[10,0],[12],[1,"\\n "],[18,3,[[30,1]]],[1,"\\n "],[13],[1,"\\n"]],[]],[[],[]]]]],[1," "],[11,"ul"],[24,"role","menu"],[17,2],[12],[1,"\\n "],[8,[39,9],null,[["@name"],["menu"]],[["default"],[[[[1,"\\n "],[18,3,[[30,1]]],[1,"\\n "]],[]]]]],[1,"\\n "],[13],[1,"\\n"],[13],[1,"\\n"]],[1]]]],["api","&attrs","&default"],false,["yield","let","hash","action","class-map","array","position","isConfirmation","did-insert","yield-slot"]]',moduleName:"consul-ui/components/menu-panel/index.hbs",isStrictMode:!1}) var u=(0,t.setComponentTemplate)(a,t.default.extend(o.default,{tagName:"",dom:(0,l.inject)("dom"),isConfirmation:!1,actions:{connect:function(e){(0,r.next)((()=>{if(!this.isDestroyed){const t=this.dom.element('li:only-child > [role="menu"]:first-child',e);(0,i.set)(this,"isConfirmation",void 0!==t)}}))},change:function(e){const t=e.target.getAttribute("id"),n=this.dom.element(`[for='${t}']`),l=this.dom.element("[role=menu]",n.parentElement),r=this.dom.closest(".menu-panel",l) if(e.target.checked){l.style.display="block" const e=l.offsetHeight+2 r.style.maxHeight=r.style.minHeight=`${e}px`}else l.style.display=null,r.style.maxHeight=null,r.style.minHeight="0"}}})) -e.default=u})) -define("consul-ui/components/menu/action/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=u})),define("consul-ui/components/menu/action/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"JOnhu+Ze",block:'[[[1,"\\n"],[8,[39,0],[[24,"role","menuitem"],[17,1],[4,[38,1],["click",[52,[30,2],[30,4,["close"]],[28,[37,3],null,null]]],null]],[["@href","@external"],[[30,2],[30,3]]],[["default"],[[[[1,"\\n "],[18,5,null],[1,"\\n"]],[]]]]],[1,"\\n"]],["&attrs","@href","@external","@disclosure","&default"],false,["action","on","if","noop","yield"]]',moduleName:"consul-ui/components/menu/action/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/menu/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 @@ -1368,10 +1375,13 @@ var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/more-popover-menu/index",["exports","@ember/component","@ember/template-factory"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const l=(0,n.createTemplateFactory)({id:"aLK3TSIx",block:'[[[1,"\\n"],[11,0],[24,0,"more-popover-menu"],[17,1],[12],[1,"\\n "],[8,[39,0],null,[["@expanded","@onchange","@keyboardAccess"],[[99,1,["@expanded"]],[28,[37,2],[[30,0],[33,3]],null],false]],[["default"],[[[[1,"\\n "],[8,[39,4],null,[["@name"],["trigger"]],[["default"],[[[[1,"\\n More\\n "]],[]]]]],[1,"\\n "],[8,[39,4],null,[["@name"],["menu"]],[["default"],[[[[1,"\\n "],[18,4,[[30,2,["MenuItem"]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[2,3]]]]],[1,"\\n"],[13],[1,"\\n"]],["&attrs","components","api","&default"],false,["popover-menu","expanded","action","onchange","block-slot","yield"]]',moduleName:"consul-ui/components/more-popover-menu/index.hbs",isStrictMode:!1}) var r=(0,t.setComponentTemplate)(l,t.default.extend({tagName:""})) -e.default=r})),define("consul-ui/components/nav-selector/index",["exports","@ember/component","@ember/template-factory","@glimmer/component","@ember/object","@glimmer/tracking"],(function(e,t,n,l,r,i){var o,a +e.default=r})),define("consul-ui/components/nav-selector/generic",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +const r=(0,n.createTemplateFactory)({id:"J8OHquCr",block:'[[[1,"\\n"],[18,1,null]],["&default"],false,["yield"]]',moduleName:"consul-ui/components/nav-selector/generic.hbs",isStrictMode:!1}) +var i=(0,t.setComponentTemplate)(r,(0,l.default)()) +e.default=i})),define("consul-ui/components/nav-selector/index",["exports","@ember/component","@ember/template-factory","@glimmer/component","@ember/object","@glimmer/tracking"],(function(e,t,n,l,r,i){var o,a function u(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -const s=(0,n.createTemplateFactory)({id:"gV2Cd+sK",block:'[[[1,"\\n"],[44,[[30,1]],[[[1," "],[8,[30,2,["Item"]],[[24,0,"consul-side-nav__selector"]],null,[["default"],[[[[1,"\\n "],[8,[39,1],[[24,0,"hds-side-nav__dropdown"],[17,3]],[["@listPosition","@width","@isInline"],["bottom-left","15.5rem",true]],[["default"],[[[[1,"\\n "],[8,[30,4,["ToggleButton"]],[[24,0,"consul-side-nav__selector-toggle"],[16,"disabled",[28,[37,2],[[30,5],true],null]]],[["@icon","@text"],[[30,6],[28,[37,3],[[30,7],[30,8]],null]]],null],[1,"\\n "],[8,[30,4,["Header"]],null,[["@hasDivider"],[true]],[["default"],[[[[1,"\\n"],[41,[30,9],[[[1," "],[10,0],[14,0,"consul-side-nav__selector-description"],[12],[1,"\\n "],[8,[39,5],null,[["@size","@color"],["100","faint"]],[["default"],[[[[1,[30,9]]],[]]]]],[1,"\\n "],[13],[1,"\\n"]],[]],null],[1," "],[8,[39,6],[[16,"placeholder",[30,10]],[16,"aria-label",[30,10]],[4,[38,7],["input",[30,0,["onSearchInput"]]],null]],[["@type","@value"],["search",[30,0,["search"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n"],[41,[28,[37,2],[[30,0,["filteredItems","length"]],0],null],[[[1," "],[8,[30,4,["Description"]],null,[["@text"],["No results"]],null],[1,"\\n"]],[]],[[[42,[28,[37,9],[[28,[37,9],[[30,0,["filteredItems"]]],null]],null],null,[[[1," "],[18,14,[[30,4],[30,11]]],[1,"\\n"]],[11]],null]],[]]],[41,[30,12],[[[1," "],[8,[30,4,["Footer"]],null,[["@hasDivider"],[true]],[["default"],[[[[1,"\\n "],[8,[39,11],null,[["@href","@isHrefExternal","@text","@iconPosition","@icon","@color"],[[30,12],false,[30,13],"trailing","arrow-right","secondary"]],null],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],null],[1," "]],[4]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[2]]]],["@list","SNL","&attrs","DD","@disabled","@icon","@item","@key","@description","@placeholder","item","@footerLink","@footerLinkText","&default"],false,["let","hds/dropdown","eq","get","if","hds/text/body","hds/form/text-input/base","on","each","-track-array","yield","hds/link/standalone"]]',moduleName:"consul-ui/components/nav-selector/index.hbs",isStrictMode:!1}) +const s=(0,n.createTemplateFactory)({id:"JCYQiQJP",block:'[[[1,"\\n"],[44,[[30,1]],[[[1," "],[8,[30,2,["Item"]],[[24,0,"consul-side-nav__selector"]],null,[["default"],[[[[1,"\\n "],[8,[39,1],[[24,0,"hds-side-nav__dropdown"],[17,3]],[["@listPosition","@width","@isInline"],["bottom-left","15.5rem",true]],[["default"],[[[[1,"\\n "],[8,[30,4,["ToggleButton"]],[[24,0,"consul-side-nav__selector-toggle"],[16,"disabled",[28,[37,2],[[30,5],true],null]]],[["@icon","@text"],[[30,6],[28,[37,3],[[30,7],[30,8]],null]]],null],[1,"\\n "],[8,[30,4,["Header"]],null,[["@hasDivider"],[true]],[["default"],[[[[1,"\\n "],[18,14,[[28,[37,5],null,[["Data"],[[50,"nav-selector/generic",0,null,null]]]]]],[1,"\\n"],[41,[30,9],[[[1," "],[10,0],[14,0,"consul-side-nav__selector-description"],[12],[1,"\\n "],[8,[39,8],null,[["@size","@color"],["100","faint"]],[["default"],[[[[1,[30,9]]],[]]]]],[1,"\\n "],[13],[1,"\\n"]],[]],null],[1," "],[8,[39,9],[[16,"placeholder",[30,10]],[16,"aria-label",[30,10]],[4,[38,10],["input",[30,0,["onSearchInput"]]],null]],[["@type","@value"],["search",[30,0,["search"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n"],[41,[28,[37,2],[[30,0,["filteredItems","length"]],0],null],[[[1," "],[8,[30,4,["Description"]],null,[["@text"],["No results"]],null],[1,"\\n"]],[]],[[[42,[28,[37,12],[[28,[37,12],[[30,0,["filteredItems"]]],null]],null],null,[[[1," "],[18,14,[[28,[37,5],null,[["Dropdown","item"],[[30,4],[30,11]]]]]],[1,"\\n"]],[11]],null]],[]]],[41,[30,12],[[[1," "],[8,[30,4,["Footer"]],null,[["@hasDivider"],[true]],[["default"],[[[[1,"\\n "],[8,[39,13],null,[["@href","@isHrefExternal","@text","@iconPosition","@icon","@color"],[[30,12],false,[30,13],"trailing","arrow-right","secondary"]],null],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],null],[1," "]],[4]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[2]]]],["@list","SNL","&attrs","DD","@disabled","@icon","@item","@key","@description","@placeholder","item","@footerLink","@footerLinkText","&default"],false,["let","hds/dropdown","eq","get","yield","hash","component","if","hds/text/body","hds/form/text-input/base","on","each","-track-array","hds/link/standalone"]]',moduleName:"consul-ui/components/nav-selector/index.hbs",isStrictMode:!1}) let c=(o=class extends l.default{constructor(){var e,t,n,l super(...arguments),e=this,t="search",l=this,(n=a)&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}get filteredItems(){const e=this.search.toLowerCase() return e?this.args.items.filter((t=>t[this.args.key].toLowerCase().includes(e))):this.args.items}onSearchInput(e){this.search=e.target.value}},a=u(o.prototype,"search",[i.tracked],{configurable:!0,enumerable:!0,writable:!0,initializer:function(){return""}}),u(o.prototype,"onSearchInput",[r.action],Object.getOwnPropertyDescriptor(o.prototype,"onSearchInput"),o.prototype),o) @@ -1495,8 +1505,8 @@ return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumera const s=(0,n.createTemplateFactory)({id:"SC5oIKM/",block:'[[[1,"\\n"],[44,[[30,1,["MenuItem"]]],[[[1," "],[8,[30,2],[[16,0,[52,[30,0,["selected"]],"is-active"]],[17,3],[4,[38,3],[[30,0,["connect"]]],null],[4,[38,3],[[28,[37,4],[[30,0],"selected",[30,5]],null]],null],[4,[38,5],[[28,[37,4],[[30,0],"selected",[30,5]],null]],null],[4,[38,6],[[30,0,["disconnect"]]],null]],[["@onclick","@selected"],[[28,[37,2],[[30,0],[30,4],[30,0]],null],[30,0,["selected"]]]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["label"]],[["default"],[[[[1,"\\n "],[18,6,null],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[2]]]],["@components","MenuItem","&attrs","@onclick","@selected","&default"],false,["let","if","action","did-insert","set","did-update","will-destroy","block-slot","yield"]]',moduleName:"consul-ui/components/popover-select/option/index.hbs",isStrictMode:!1}) let c=(o=class extends l.default{constructor(){var e,t,n,l super(...arguments),e=this,t="selected",l=this,(n=a)&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}connect(){this.args.select.addOption(this)}disconnect(){this.args.select.removeOption(this)}},a=u(o.prototype,"selected",[r.tracked],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),u(o.prototype,"connect",[i.action],Object.getOwnPropertyDescriptor(o.prototype,"connect"),o.prototype),u(o.prototype,"disconnect",[i.action],Object.getOwnPropertyDescriptor(o.prototype,"disconnect"),o.prototype),o) -e.default=c,(0,t.setComponentTemplate)(s,c)})),define("consul-ui/components/portal-target",["exports","ember-stargate/components/portal-target"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/portal",["exports","ember-stargate/components/portal"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select-multiple-with-create",["exports","ember-power-select-with-create/components/power-select-multiple-with-create"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select-multiple",["exports","ember-power-select/components/power-select-multiple"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) -define("consul-ui/components/power-select-multiple/trigger",["exports","ember-power-select/components/power-select-multiple/trigger"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select-with-create",["exports","ember-power-select-with-create/components/power-select-with-create"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select-with-create/suggested-option",["exports","ember-power-select-with-create/components/power-select-with-create/suggested-option"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select",["exports","ember-power-select/components/power-select"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/before-options",["exports","ember-power-select/components/power-select/before-options"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/no-matches-message",["exports","ember-power-select/components/power-select/no-matches-message"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/options",["exports","ember-power-select/components/power-select/options"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/placeholder",["exports","ember-power-select/components/power-select/placeholder"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/power-select-group",["exports","ember-power-select/components/power-select/power-select-group"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/search-message",["exports","ember-power-select/components/power-select/search-message"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/trigger",["exports","ember-power-select/components/power-select/trigger"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/progress/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=c,(0,t.setComponentTemplate)(s,c)})),define("consul-ui/components/portal-target",["exports","ember-stargate/components/portal-target"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/portal",["exports","ember-stargate/components/portal"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) +define("consul-ui/components/power-select-multiple-with-create",["exports","ember-power-select-with-create/components/power-select-multiple-with-create"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select-multiple",["exports","ember-power-select/components/power-select-multiple"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select-multiple/trigger",["exports","ember-power-select/components/power-select-multiple/trigger"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select-with-create",["exports","ember-power-select-with-create/components/power-select-with-create"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select-with-create/suggested-option",["exports","ember-power-select-with-create/components/power-select-with-create/suggested-option"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select",["exports","ember-power-select/components/power-select"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/before-options",["exports","ember-power-select/components/power-select/before-options"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/no-matches-message",["exports","ember-power-select/components/power-select/no-matches-message"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/options",["exports","ember-power-select/components/power-select/options"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/placeholder",["exports","ember-power-select/components/power-select/placeholder"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/power-select-group",["exports","ember-power-select/components/power-select/power-select-group"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/search-message",["exports","ember-power-select/components/power-select/search-message"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/power-select/trigger",["exports","ember-power-select/components/power-select/trigger"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/components/progress/index",["exports","@ember/component","@ember/template-factory","@ember/component/template-only"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const r=(0,n.createTemplateFactory)({id:"v8Ti52Dh",block:'[[[1,"\\n"],[11,0],[24,0,"progress indeterminate"],[24,"role","progressbar"],[17,1],[12],[13],[1,"\\n"]],["&attrs"],false,[]]',moduleName:"consul-ui/components/progress/index.hbs",isStrictMode:!1}) var i=(0,t.setComponentTemplate)(r,(0,l.default)()) e.default=i})),define("consul-ui/components/providers/dimension/index",["exports","@ember/component","@ember/template-factory","@glimmer/component","@glimmer/tracking","@ember/object","ember-ref-bucket","@ember/template"],(function(e,t,n,l,r,i,o,a){var u,s,c,d @@ -1572,7 +1582,8 @@ var o=(0,t.setComponentTemplate)(i,t.default.extend({chart:(0,l.inject)("state") void 0!==this.machine&&this.machine.stop(),void 0!==this.initial&&(this.src.initial=this.initial),this.machine=this.chart.interpret(this.src,{onTransition:e=>{const t=new CustomEvent("transition",{detail:e}) this.ontransition(t),t.defaultPrevented||e.actions.forEach((t=>{"function"==typeof this._actions[t.type]&&this._actions[t.type](t.type,e.context,e.event)})),(0,r.set)(this,"state",e)},onGuard:function(t){for(var n=arguments.length,l=new Array(n>1?n-1:0),r=1;ro!==this.router.currentRouteName||void 0!==t.nspace?a.transitionTo(...(0,i.default)(this.router.currentRoute,t,n)):e))}),e.type,(function(e,t){return e}),{})}},c=m(s.prototype,"router",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),d=m(s.prototype,"store",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),p=m(s.prototype,"feedback",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),m(s.prototype,"reauthorize",[r.action],Object.getOwnPropertyDescriptor(s.prototype,"reauthorize"),s.prototype),s) e.default=h})),define("consul-ui/controllers/dc/acls/policies/create",["exports","consul-ui/controllers/dc/acls/policies/edit"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -class n extends t.default{}e.default=n})),define("consul-ui/controllers/dc/acls/policies/edit",["exports","@ember/service","@ember/controller"],(function(e,t,n){var l,r,i +class n extends t.default{}e.default=n})) +define("consul-ui/controllers/dc/acls/policies/edit",["exports","@ember/service","@ember/controller"],(function(e,t,n){var l,r,i Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 let o=(l=(0,t.inject)("form"),r=class extends n.default{constructor(){var e,t,n,l super(...arguments),e=this,t="builder",l=this,(n=i)&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}init(){super.init(...arguments),this.form=this.builder.form("policy")}setProperties(e){super.setProperties(Object.keys(e).reduce(((e,t,n)=>{if("item"===t)e[t]=this.form.setData(e[t]).getData() return e}),e))}},a=r.prototype,u="builder",s=[l],c={configurable:!0,enumerable:!0,writable:!0,initializer:null},p={},Object.keys(c).forEach((function(e){p[e]=c[e]})),p.enumerable=!!p.enumerable,p.configurable=!!p.configurable,("value"in p||p.initializer)&&(p.writable=!0),p=s.slice().reverse().reduce((function(e,t){return t(a,u,e)||e}),p),d&&void 0!==p.initializer&&(p.value=p.initializer?p.initializer.call(d):void 0,p.initializer=void 0),void 0===p.initializer&&(Object.defineProperty(a,u,p),p=null),i=p,r) var a,u,s,c,d,p e.default=o})),define("consul-ui/controllers/dc/acls/roles/create",["exports","consul-ui/controllers/dc/acls/roles/edit"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -class n extends t.default{}e.default=n})) -define("consul-ui/controllers/dc/acls/roles/edit",["exports","@ember/service","@ember/controller"],(function(e,t,n){var l,r,i +class n extends t.default{}e.default=n})),define("consul-ui/controllers/dc/acls/roles/edit",["exports","@ember/service","@ember/controller"],(function(e,t,n){var l,r,i Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 let o=(l=(0,t.inject)("form"),r=class extends n.default{constructor(){var e,t,n,l super(...arguments),e=this,t="builder",l=this,(n=i)&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}init(){super.init(...arguments),this.form=this.builder.form("role")}setProperties(e){super.setProperties(Object.keys(e).reduce(((e,t,n)=>{if("item"===t)e[t]=this.form.setData(e[t]).getData() @@ -1840,20 +1850,20 @@ const l=(0,n.default)()})),define("consul-ui/forms/kv",["exports","consul-ui/val return i(n,{}).setValidators(r)} const l=(0,n.default)()})),define("consul-ui/forms/policy",["exports","consul-ui/validations/policy","consul-ui/utils/form/builder"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"policy",r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:t.default,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:l return i(n,{Datacenters:{type:"array"}}).setValidators(r)} -const l=(0,n.default)()})),define("consul-ui/forms/role",["exports","consul-ui/validations/role","consul-ui/utils/form/builder"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"role",r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:t.default,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:l +const l=(0,n.default)()})) +define("consul-ui/forms/role",["exports","consul-ui/validations/role","consul-ui/utils/form/builder"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"role",r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:t.default,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:l return i(n,{}).setValidators(r).add(e.form("policy"))} const l=(0,n.default)()})),define("consul-ui/forms/token",["exports","consul-ui/validations/token","consul-ui/utils/form/builder"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e){let n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",r=arguments.length>2&&void 0!==arguments[2]?arguments[2]:t.default,i=arguments.length>3&&void 0!==arguments[3]?arguments[3]:l return i(n,{}).setValidators(r).add(e.form("policy")).add(e.form("role"))} -const l=(0,n.default)()})) -define("consul-ui/helpers/-element",["exports","ember-element-helper/helpers/-element"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/abs",["exports","ember-math-helpers/helpers/abs"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"abs",{enumerable:!0,get:function(){return t.abs}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/acos",["exports","ember-math-helpers/helpers/acos"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"acos",{enumerable:!0,get:function(){return t.acos}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/acosh",["exports","ember-math-helpers/helpers/acosh"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"acosh",{enumerable:!0,get:function(){return t.acosh}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/add",["exports","ember-math-helpers/helpers/add"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"add",{enumerable:!0,get:function(){return t.add}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/adopt-styles",["exports","@ember/component/helper","@ember/debug","@lit/reactive-element"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +const l=(0,n.default)()})),define("consul-ui/helpers/-element",["exports","ember-element-helper/helpers/-element"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/abs",["exports","ember-math-helpers/helpers/abs"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"abs",{enumerable:!0,get:function(){return t.abs}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/acos",["exports","ember-math-helpers/helpers/acos"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"acos",{enumerable:!0,get:function(){return t.acos}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/acosh",["exports","ember-math-helpers/helpers/acosh"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"acosh",{enumerable:!0,get:function(){return t.acosh}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/add",["exports","ember-math-helpers/helpers/add"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"add",{enumerable:!0,get:function(){return t.add}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/adopt-styles",["exports","@ember/component/helper","@ember/debug","@lit/reactive-element"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 class r extends t.default{compute(e,t){let[n,r]=e Array.isArray(r)||(r=[r]),(0,l.adoptStyles)(n,r)}}e.default=r})),define("consul-ui/helpers/and",["exports","ember-truth-helpers/helpers/and"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"and",{enumerable:!0,get:function(){return t.and}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/app-version",["exports","@ember/component/helper","consul-ui/config/environment","ember-cli-app-version/utils/regexp"],(function(e,t,n,l){function r(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{} const r=n.default.APP.version let i=t.versionOnly||t.hideSha,o=t.shaOnly||t.hideVersion,a=null return i&&(t.showExtended&&(a=r.match(l.versionExtendedRegExp)),a||(a=r.match(l.versionRegExp))),o&&(a=r.match(l.shaRegExp)),a?a[0]:r}Object.defineProperty(e,"__esModule",{value:!0}),e.appVersion=r,e.default=void 0 var i=(0,t.helper)(r) -e.default=i})),define("consul-ui/helpers/append",["exports","ember-composable-helpers/helpers/append"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"append",{enumerable:!0,get:function(){return t.append}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-concat",["exports","ember-array-fns/helpers/array-concat"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayConcat",{enumerable:!0,get:function(){return t.arrayConcat}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-every",["exports","ember-array-fns/helpers/array-every"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayEvery",{enumerable:!0,get:function(){return t.arrayEvery}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-filter",["exports","ember-array-fns/helpers/array-filter"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayFilter",{enumerable:!0,get:function(){return t.arrayFilter}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-find-index",["exports","ember-array-fns/helpers/array-find-index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayFindIndex",{enumerable:!0,get:function(){return t.arrayFindIndex}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-find",["exports","ember-array-fns/helpers/array-find"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayFind",{enumerable:!0,get:function(){return t.arrayFind}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-includes",["exports","ember-array-fns/helpers/array-includes"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayIncludes",{enumerable:!0,get:function(){return t.arrayIncludes}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-index-of",["exports","ember-array-fns/helpers/array-index-of"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayIndexOf",{enumerable:!0,get:function(){return t.arrayIndexOf}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-is-array",["exports","ember-array-fns/helpers/array-is-array"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayIsArray",{enumerable:!0,get:function(){return t.arrayIsArray}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-is-first-element",["exports","ember-array-fns/helpers/array-is-first-element"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayIsFirstElement",{enumerable:!0,get:function(){return t.arrayIsFirstElement}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-is-last-element",["exports","ember-array-fns/helpers/array-is-last-element"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayIsLastElement",{enumerable:!0,get:function(){return t.arrayIsLastElement}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-join",["exports","ember-array-fns/helpers/array-join"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayJoin",{enumerable:!0,get:function(){return t.arrayJoin}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-last-index-of",["exports","ember-array-fns/helpers/array-last-index-of"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayLastIndexOf",{enumerable:!0,get:function(){return t.arrayLastIndexOf}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-map",["exports","ember-array-fns/helpers/array-map"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayMap",{enumerable:!0,get:function(){return t.arrayMap}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-reduce",["exports","ember-array-fns/helpers/array-reduce"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayReduce",{enumerable:!0,get:function(){return t.arrayReduce}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-reverse",["exports","ember-array-fns/helpers/array-reverse"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayReverse",{enumerable:!0,get:function(){return t.arrayReverse}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-slice",["exports","ember-array-fns/helpers/array-slice"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arraySlice",{enumerable:!0,get:function(){return t.arraySlice}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-some",["exports","ember-array-fns/helpers/array-some"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arraySome",{enumerable:!0,get:function(){return t.arraySome}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-sort",["exports","ember-array-fns/helpers/array-sort"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arraySort",{enumerable:!0,get:function(){return t.arraySort}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-splice",["exports","ember-array-fns/helpers/array-splice"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arraySplice",{enumerable:!0,get:function(){return t.arraySplice}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/asin",["exports","ember-math-helpers/helpers/asin"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"asin",{enumerable:!0,get:function(){return t.asin}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/asinh",["exports","ember-math-helpers/helpers/asinh"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"asinh",{enumerable:!0,get:function(){return t.asinh}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) -define("consul-ui/helpers/assign",["exports","ember-assign-helper/helpers/assign"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"assign",{enumerable:!0,get:function(){return t.assign}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/atan",["exports","ember-math-helpers/helpers/atan"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"atan",{enumerable:!0,get:function(){return t.atan}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/atan2",["exports","ember-math-helpers/helpers/atan2"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"atan2",{enumerable:!0,get:function(){return t.atan2}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/atanh",["exports","ember-math-helpers/helpers/atanh"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"atanh",{enumerable:!0,get:function(){return t.atanh}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/atob",["exports","@ember/component/helper","consul-ui/utils/atob"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=i})),define("consul-ui/helpers/append",["exports","ember-composable-helpers/helpers/append"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"append",{enumerable:!0,get:function(){return t.append}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-concat",["exports","ember-array-fns/helpers/array-concat"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayConcat",{enumerable:!0,get:function(){return t.arrayConcat}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-every",["exports","ember-array-fns/helpers/array-every"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayEvery",{enumerable:!0,get:function(){return t.arrayEvery}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-filter",["exports","ember-array-fns/helpers/array-filter"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayFilter",{enumerable:!0,get:function(){return t.arrayFilter}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-find-index",["exports","ember-array-fns/helpers/array-find-index"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayFindIndex",{enumerable:!0,get:function(){return t.arrayFindIndex}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-find",["exports","ember-array-fns/helpers/array-find"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayFind",{enumerable:!0,get:function(){return t.arrayFind}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-includes",["exports","ember-array-fns/helpers/array-includes"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayIncludes",{enumerable:!0,get:function(){return t.arrayIncludes}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-index-of",["exports","ember-array-fns/helpers/array-index-of"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayIndexOf",{enumerable:!0,get:function(){return t.arrayIndexOf}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-is-array",["exports","ember-array-fns/helpers/array-is-array"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayIsArray",{enumerable:!0,get:function(){return t.arrayIsArray}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-is-first-element",["exports","ember-array-fns/helpers/array-is-first-element"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayIsFirstElement",{enumerable:!0,get:function(){return t.arrayIsFirstElement}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-is-last-element",["exports","ember-array-fns/helpers/array-is-last-element"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayIsLastElement",{enumerable:!0,get:function(){return t.arrayIsLastElement}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-join",["exports","ember-array-fns/helpers/array-join"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayJoin",{enumerable:!0,get:function(){return t.arrayJoin}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-last-index-of",["exports","ember-array-fns/helpers/array-last-index-of"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayLastIndexOf",{enumerable:!0,get:function(){return t.arrayLastIndexOf}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-map",["exports","ember-array-fns/helpers/array-map"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayMap",{enumerable:!0,get:function(){return t.arrayMap}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-reduce",["exports","ember-array-fns/helpers/array-reduce"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayReduce",{enumerable:!0,get:function(){return t.arrayReduce}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-reverse",["exports","ember-array-fns/helpers/array-reverse"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arrayReverse",{enumerable:!0,get:function(){return t.arrayReverse}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-slice",["exports","ember-array-fns/helpers/array-slice"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arraySlice",{enumerable:!0,get:function(){return t.arraySlice}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-some",["exports","ember-array-fns/helpers/array-some"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arraySome",{enumerable:!0,get:function(){return t.arraySome}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-sort",["exports","ember-array-fns/helpers/array-sort"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arraySort",{enumerable:!0,get:function(){return t.arraySort}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/array-splice",["exports","ember-array-fns/helpers/array-splice"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"arraySplice",{enumerable:!0,get:function(){return t.arraySplice}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) +define("consul-ui/helpers/asin",["exports","ember-math-helpers/helpers/asin"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"asin",{enumerable:!0,get:function(){return t.asin}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/asinh",["exports","ember-math-helpers/helpers/asinh"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"asinh",{enumerable:!0,get:function(){return t.asinh}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/assign",["exports","ember-assign-helper/helpers/assign"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"assign",{enumerable:!0,get:function(){return t.assign}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/atan",["exports","ember-math-helpers/helpers/atan"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"atan",{enumerable:!0,get:function(){return t.atan}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/atan2",["exports","ember-math-helpers/helpers/atan2"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"atan2",{enumerable:!0,get:function(){return t.atan2}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/atanh",["exports","ember-math-helpers/helpers/atanh"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"atanh",{enumerable:!0,get:function(){return t.atanh}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/atob",["exports","@ember/component/helper","consul-ui/utils/atob"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var l=(0,t.helper)((function(e){let[t=""]=e return(0,n.default)(t)})) e.default=l})),define("consul-ui/helpers/block-params",["exports","block-slots/helpers/block-params"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/cached-model",["exports","@ember/component/helper","@ember/application"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 @@ -1867,10 +1877,10 @@ class o{}class a extends t.default{compute(e,t){let[l,r]=e if(l.length>0){const e=(0,n.get)(l,"firstObject")._internalModel.modelName return new(0,i[e])(l)}return new o}}e.default=a})),define("consul-ui/helpers/compact",["exports","ember-composable-helpers/helpers/compact"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/compute",["exports","ember-composable-helpers/helpers/compute"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"compute",{enumerable:!0,get:function(){return t.compute}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/contains",["exports","ember-composable-helpers/helpers/contains"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"contains",{enumerable:!0,get:function(){return t.contains}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/cos",["exports","ember-math-helpers/helpers/cos"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"cos",{enumerable:!0,get:function(){return t.cos}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/cosh",["exports","ember-math-helpers/helpers/cosh"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"cosh",{enumerable:!0,get:function(){return t.cosh}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/css-map",["exports","@ember/component/helper","@lit/reactive-element"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var l=(0,t.helper)((e=>e.filter((e=>e instanceof n.CSSResult||e[e.length-1])).map((e=>e instanceof n.CSSResult?e:e[0])))) -e.default=l})),define("consul-ui/helpers/css",["exports","@ember/component/helper","@lit/reactive-element"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=l})) +define("consul-ui/helpers/css",["exports","@ember/component/helper","@lit/reactive-element"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 class l extends t.default{compute(e,t){let[l]=e -return(0,n.css)([l])}}e.default=l})),define("consul-ui/helpers/dec",["exports","ember-composable-helpers/helpers/dec"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"dec",{enumerable:!0,get:function(){return t.dec}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) -define("consul-ui/helpers/did-insert",["exports","ember-render-helpers/helpers/did-insert"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/did-update",["exports","ember-render-helpers/helpers/did-update"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/div",["exports","ember-math-helpers/helpers/div"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"div",{enumerable:!0,get:function(){return t.div}})})),define("consul-ui/helpers/document-attrs",["exports","@ember/component/helper","@ember/service","@ember/debug","mnemonist/multi-map"],(function(e,t,n,l,r){var i,o,a +return(0,n.css)([l])}}e.default=l})),define("consul-ui/helpers/dec",["exports","ember-composable-helpers/helpers/dec"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"dec",{enumerable:!0,get:function(){return t.dec}}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/did-insert",["exports","ember-render-helpers/helpers/did-insert"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/did-update",["exports","ember-render-helpers/helpers/did-update"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/div",["exports","ember-math-helpers/helpers/div"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"div",{enumerable:!0,get:function(){return t.div}})})),define("consul-ui/helpers/document-attrs",["exports","@ember/component/helper","@ember/service","@ember/debug","mnemonist/multi-map"],(function(e,t,n,l,r){var i,o,a Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const u=new Map,s=new WeakMap let c=(i=(0,n.inject)("-document"),o=class extends t.default{constructor(){var e,t,n,l @@ -1917,8 +1927,8 @@ switch(!0){case 0!==a:return a+"d" case 0!==u:return u+"h" case 0!==s:return s+"m" default:return c+"s"}})) -e.default=n})),define("consul-ui/helpers/format-time",["exports","ember-intl/helpers/format-time"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/from-entries",["exports","ember-composable-helpers/helpers/from-entries"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"fromEntries",{enumerable:!0,get:function(){return t.fromEntries}})})) -define("consul-ui/helpers/fround",["exports","ember-math-helpers/helpers/fround"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"fround",{enumerable:!0,get:function(){return t.fround}})})),define("consul-ui/helpers/gcd",["exports","ember-math-helpers/helpers/gcd"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"gcd",{enumerable:!0,get:function(){return t.gcd}})})),define("consul-ui/helpers/group-by",["exports","ember-composable-helpers/helpers/group-by"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/gt",["exports","ember-truth-helpers/helpers/gt"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"gt",{enumerable:!0,get:function(){return t.gt}})})),define("consul-ui/helpers/gte",["exports","ember-truth-helpers/helpers/gte"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"gte",{enumerable:!0,get:function(){return t.gte}})})),define("consul-ui/helpers/has-next",["exports","ember-composable-helpers/helpers/has-next"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"hasNext",{enumerable:!0,get:function(){return t.hasNext}})})),define("consul-ui/helpers/has-previous",["exports","ember-composable-helpers/helpers/has-previous"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"hasPrevious",{enumerable:!0,get:function(){return t.hasPrevious}})})),define("consul-ui/helpers/hds-link-to-models",["exports","@hashicorp/design-system-components/helpers/hds-link-to-models"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/hds-link-to-query",["exports","@hashicorp/design-system-components/helpers/hds-link-to-query"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/href-to",["exports","@ember/component/helper","@ember/service","@ember/object","@ember/application","consul-ui/utils/routing/transitionable","consul-ui/utils/routing/wildcard","consul-ui/router"],(function(e,t,n,l,r,i,o,a){var u,s,c +e.default=n})) +define("consul-ui/helpers/format-time",["exports","ember-intl/helpers/format-time"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/from-entries",["exports","ember-composable-helpers/helpers/from-entries"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"fromEntries",{enumerable:!0,get:function(){return t.fromEntries}})})),define("consul-ui/helpers/fround",["exports","ember-math-helpers/helpers/fround"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"fround",{enumerable:!0,get:function(){return t.fround}})})),define("consul-ui/helpers/gcd",["exports","ember-math-helpers/helpers/gcd"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"gcd",{enumerable:!0,get:function(){return t.gcd}})})),define("consul-ui/helpers/group-by",["exports","ember-composable-helpers/helpers/group-by"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/gt",["exports","ember-truth-helpers/helpers/gt"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"gt",{enumerable:!0,get:function(){return t.gt}})})),define("consul-ui/helpers/gte",["exports","ember-truth-helpers/helpers/gte"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"gte",{enumerable:!0,get:function(){return t.gte}})})),define("consul-ui/helpers/has-next",["exports","ember-composable-helpers/helpers/has-next"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"hasNext",{enumerable:!0,get:function(){return t.hasNext}})})),define("consul-ui/helpers/has-previous",["exports","ember-composable-helpers/helpers/has-previous"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"hasPrevious",{enumerable:!0,get:function(){return t.hasPrevious}})})),define("consul-ui/helpers/hds-link-to-models",["exports","@hashicorp/design-system-components/helpers/hds-link-to-models"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/hds-link-to-query",["exports","@hashicorp/design-system-components/helpers/hds-link-to-query"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/href-to",["exports","@ember/component/helper","@ember/service","@ember/object","@ember/application","consul-ui/utils/routing/transitionable","consul-ui/utils/routing/wildcard","consul-ui/router"],(function(e,t,n,l,r,i,o,a){var u,s,c function d(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.hrefTo=e.default=void 0 const p=(0,o.default)(a.routes),f=function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{} @@ -1946,11 +1956,11 @@ e.is=r class i extends t.default{compute(e,t){let[n,l]=e return r(this,[n,l],t)}}e.default=i})),define("consul-ui/helpers/join",["exports","ember-composable-helpers/helpers/join"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/json-stringify",["exports","@ember/component/helper"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.helper)((function(e,t){try{return JSON.stringify(...e)}catch(n){return e[0].map((t=>JSON.stringify(t,e[1],e[2])))}})) -e.default=n})),define("consul-ui/helpers/keys",["exports","ember-composable-helpers/helpers/keys"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"keys",{enumerable:!0,get:function(){return t.keys}})})),define("consul-ui/helpers/last",["exports","@ember/component/helper"],(function(e,t){function n(e,t){let[n=""]=e +e.default=n})),define("consul-ui/helpers/keys",["exports","ember-composable-helpers/helpers/keys"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"keys",{enumerable:!0,get:function(){return t.keys}})})) +define("consul-ui/helpers/last",["exports","@ember/component/helper"],(function(e,t){function n(e,t){let[n=""]=e if(!0==("string"==typeof n))return n.substr(-1)}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0,e.last=n var l=(0,t.helper)(n) -e.default=l})),define("consul-ui/helpers/lcm",["exports","ember-math-helpers/helpers/lcm"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"lcm",{enumerable:!0,get:function(){return t.lcm}})})) -define("consul-ui/helpers/left-trim",["exports","@ember/component/helper","consul-ui/utils/left-trim"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=l})),define("consul-ui/helpers/lcm",["exports","ember-math-helpers/helpers/lcm"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"lcm",{enumerable:!0,get:function(){return t.lcm}})})),define("consul-ui/helpers/left-trim",["exports","@ember/component/helper","consul-ui/utils/left-trim"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var l=(0,t.helper)((function(e,t){let[l="",r=""]=e return(0,n.default)(l,r)})) e.default=l})),define("consul-ui/helpers/log-e",["exports","ember-math-helpers/helpers/log-e"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"logE",{enumerable:!0,get:function(){return t.logE}})})),define("consul-ui/helpers/log10",["exports","ember-math-helpers/helpers/log10"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"log10",{enumerable:!0,get:function(){return t.log10}})})),define("consul-ui/helpers/log1p",["exports","ember-math-helpers/helpers/log1p"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"log1p",{enumerable:!0,get:function(){return t.log1p}})})),define("consul-ui/helpers/log2",["exports","ember-math-helpers/helpers/log2"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"log2",{enumerable:!0,get:function(){return t.log2}})})),define("consul-ui/helpers/lowercase",["exports","ember-cli-string-helpers/helpers/lowercase"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"lowercase",{enumerable:!0,get:function(){return t.lowercase}})})),define("consul-ui/helpers/lt",["exports","ember-truth-helpers/helpers/lt"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"lt",{enumerable:!0,get:function(){return t.lt}})})),define("consul-ui/helpers/lte",["exports","ember-truth-helpers/helpers/lte"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"lte",{enumerable:!0,get:function(){return t.lte}})})),define("consul-ui/helpers/map-by",["exports","ember-composable-helpers/helpers/map-by"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/map",["exports","ember-composable-helpers/helpers/map"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/max",["exports","ember-math-helpers/helpers/max"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"max",{enumerable:!0,get:function(){return t.max}})})),define("consul-ui/helpers/merge-checks",["exports","@ember/component/helper","consul-ui/utils/merge-checks"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 @@ -1960,14 +1970,14 @@ e.default=l})),define("consul-ui/helpers/min",["exports","ember-math-helpers/hel var l=(0,t.helper)((function(e){return new n.default(e[0])})) e.default=l})),define("consul-ui/helpers/mod",["exports","ember-math-helpers/helpers/mod"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"mod",{enumerable:!0,get:function(){return t.mod}})})),define("consul-ui/helpers/mult",["exports","ember-math-helpers/helpers/mult"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"mult",{enumerable:!0,get:function(){return t.mult}})})),define("consul-ui/helpers/next",["exports","ember-composable-helpers/helpers/next"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"next",{enumerable:!0,get:function(){return t.next}})})),define("consul-ui/helpers/noop",["exports","ember-composable-helpers/helpers/noop"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"noop",{enumerable:!0,get:function(){return t.noop}})})),define("consul-ui/helpers/not-eq",["exports","ember-truth-helpers/helpers/not-eq"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"notEqualHelper",{enumerable:!0,get:function(){return t.notEqualHelper}})})),define("consul-ui/helpers/not",["exports","ember-truth-helpers/helpers/not"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"not",{enumerable:!0,get:function(){return t.not}})})),define("consul-ui/helpers/object-at",["exports","ember-composable-helpers/helpers/object-at"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"objectAt",{enumerable:!0,get:function(){return t.objectAt}})})),define("consul-ui/helpers/on-document",["exports","ember-on-helper/helpers/on-document"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/on-key",["exports","ember-keyboard/helpers/on-key.js"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/on-window",["exports","ember-on-helper/helpers/on-window"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/on",["exports","ember-on-helper/helpers/on"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/optional",["exports","ember-composable-helpers/helpers/optional"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"optional",{enumerable:!0,get:function(){return t.optional}})})),define("consul-ui/helpers/or",["exports","ember-truth-helpers/helpers/or"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"or",{enumerable:!0,get:function(){return t.or}})})),define("consul-ui/helpers/page-title",["exports","ember-page-title/helpers/page-title"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=t.default -e.default=n})),define("consul-ui/helpers/percentage-columns-layout",["exports","@ember/component/helper","ember-collection/layouts/percentage-columns"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=n})) +define("consul-ui/helpers/percentage-columns-layout",["exports","@ember/component/helper","ember-collection/layouts/percentage-columns"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var l=(0,t.helper)((function(e){return new n.default(e[0],e[1],e[2])})) e.default=l})),define("consul-ui/helpers/percentage-of",["exports","@ember/component/helper"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.helper)((function(e,t){let[n,l]=e const r=n/l*100 return isNaN(r)?0:r.toFixed(2)})) -e.default=n})) -define("consul-ui/helpers/perform",["exports","ember-concurrency/helpers/perform"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/pick",["exports","ember-composable-helpers/helpers/pick"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"pick",{enumerable:!0,get:function(){return t.pick}})})),define("consul-ui/helpers/pipe-action",["exports","ember-composable-helpers/helpers/pipe-action"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/pipe",["exports","ember-composable-helpers/helpers/pipe"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"pipe",{enumerable:!0,get:function(){return t.pipe}})})),define("consul-ui/helpers/pluralize",["exports","ember-inflector/lib/helpers/pluralize"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=n})),define("consul-ui/helpers/perform",["exports","ember-concurrency/helpers/perform"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/pick",["exports","ember-composable-helpers/helpers/pick"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"pick",{enumerable:!0,get:function(){return t.pick}})})),define("consul-ui/helpers/pipe-action",["exports","ember-composable-helpers/helpers/pipe-action"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/pipe",["exports","ember-composable-helpers/helpers/pipe"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"pipe",{enumerable:!0,get:function(){return t.pipe}})})),define("consul-ui/helpers/pluralize",["exports","ember-inflector/lib/helpers/pluralize"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=t.default e.default=n})),define("consul-ui/helpers/policy/datacenters",["exports","@ember/component/helper","@ember/object"],(function(e,t,n){function l(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{} const l=(0,n.get)(e[0],"Datacenters") @@ -2030,7 +2040,8 @@ switch(!0){case e:return"allow" case!n&&!e:return"deny" case n&&l:return"not-defined" default:return"allow"}}})) -e.default=n})),define("consul-ui/helpers/service/external-source",["exports","@ember/component/helper","@ember/object"],(function(e,t,n){function l(e,t){let l=(0,n.get)(e[0],"ExternalSources.firstObject") +e.default=n})) +define("consul-ui/helpers/service/external-source",["exports","@ember/component/helper","@ember/object"],(function(e,t,n){function l(e,t){let l=(0,n.get)(e[0],"ExternalSources.firstObject") l||(l=(0,n.get)(e[0],"Meta.external-source")) const r=void 0===t.prefix?"":t.prefix if(l&&["consul-api-gateway","vault","kubernetes","terraform","nomad","consul","aws","lambda"].includes(l))return`${r}${l}`}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0,e.serviceExternalSource=l @@ -2039,8 +2050,7 @@ e.default=r})),define("consul-ui/helpers/service/health-percentage",["exports"," var n=(0,t.helper)((function(e){let[t]=e const n=t.ChecksCritical+t.ChecksPassing+t.ChecksWarning return 0===n?"":{passing:Math.round(t.ChecksPassing/n*100),warning:Math.round(t.ChecksWarning/n*100),critical:Math.round(t.ChecksCritical/n*100)}})) -e.default=n})) -define("consul-ui/helpers/set",["exports","ember-set-helper/helpers/set"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/shuffle",["exports","ember-composable-helpers/helpers/shuffle"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"shuffle",{enumerable:!0,get:function(){return t.shuffle}})})),define("consul-ui/helpers/sign",["exports","ember-math-helpers/helpers/sign"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"sign",{enumerable:!0,get:function(){return t.sign}})})),define("consul-ui/helpers/sin",["exports","ember-math-helpers/helpers/sin"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"sin",{enumerable:!0,get:function(){return t.sin}})})),define("consul-ui/helpers/singularize",["exports","ember-inflector/lib/helpers/singularize"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=n})),define("consul-ui/helpers/set",["exports","ember-set-helper/helpers/set"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/shuffle",["exports","ember-composable-helpers/helpers/shuffle"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"shuffle",{enumerable:!0,get:function(){return t.shuffle}})})),define("consul-ui/helpers/sign",["exports","ember-math-helpers/helpers/sign"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"sign",{enumerable:!0,get:function(){return t.sign}})})),define("consul-ui/helpers/sin",["exports","ember-math-helpers/helpers/sin"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"sin",{enumerable:!0,get:function(){return t.sin}})})),define("consul-ui/helpers/singularize",["exports","ember-inflector/lib/helpers/singularize"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=t.default e.default=n})),define("consul-ui/helpers/slice",["exports","ember-composable-helpers/helpers/slice"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/helpers/slugify",["exports","@ember/component/helper"],(function(e,t){function n(e,t){let[n=""]=e return n.replace(/ /g,"-").toLowerCase()}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0,e.slugify=n @@ -2070,8 +2080,8 @@ var a,u,s,c,d,p e.default=o})),define("consul-ui/helpers/string-char-at",["exports","ember-string-fns/helpers/string-char-at"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringCharAt",{enumerable:!0,get:function(){return t.stringCharAt}})})),define("consul-ui/helpers/string-char-code-at",["exports","ember-string-fns/helpers/string-char-code-at"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringCharCodeAt",{enumerable:!0,get:function(){return t.stringCharCodeAt}})})),define("consul-ui/helpers/string-code-point-at",["exports","ember-string-fns/helpers/string-code-point-at"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringCodePointAt",{enumerable:!0,get:function(){return t.stringCodePointAt}})})),define("consul-ui/helpers/string-concat",["exports","ember-string-fns/helpers/string-concat"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringConcat",{enumerable:!0,get:function(){return t.stringConcat}})})),define("consul-ui/helpers/string-ends-with",["exports","ember-string-fns/helpers/string-ends-with"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringEndsWith",{enumerable:!0,get:function(){return t.stringEndsWith}})})),define("consul-ui/helpers/string-equals",["exports","ember-string-fns/helpers/string-equals"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringEquals",{enumerable:!0,get:function(){return t.stringEquals}})})),define("consul-ui/helpers/string-from-char-code",["exports","ember-string-fns/helpers/string-from-char-code"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringFromCharCode",{enumerable:!0,get:function(){return t.stringFromCharCode}})})),define("consul-ui/helpers/string-from-code-point",["exports","ember-string-fns/helpers/string-from-code-point"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringFromCodePoint",{enumerable:!0,get:function(){return t.stringFromCodePoint}})})),define("consul-ui/helpers/string-html-safe",["exports","@ember/component/helper","@ember/string"],(function(e,t,n){function l(e){let[t=""]=e return(0,n.htmlSafe)(t)}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0,e.stringHtmlSafe=l var r=(0,t.helper)(l) -e.default=r})),define("consul-ui/helpers/string-includes",["exports","ember-string-fns/helpers/string-includes"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringIncludes",{enumerable:!0,get:function(){return t.stringIncludes}})})),define("consul-ui/helpers/string-index-of",["exports","ember-string-fns/helpers/string-index-of"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringIndexOf",{enumerable:!0,get:function(){return t.stringIndexOf}})})),define("consul-ui/helpers/string-last-index-of",["exports","ember-string-fns/helpers/string-last-index-of"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringLastIndexOf",{enumerable:!0,get:function(){return t.stringLastIndexOf}})})),define("consul-ui/helpers/string-not-equals",["exports","ember-string-fns/helpers/string-not-equals"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringNotEquals",{enumerable:!0,get:function(){return t.stringNotEquals}})})),define("consul-ui/helpers/string-pad-end",["exports","ember-string-fns/helpers/string-pad-end"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringPadEnd",{enumerable:!0,get:function(){return t.stringPadEnd}})})),define("consul-ui/helpers/string-pad-start",["exports","ember-string-fns/helpers/string-pad-start"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringPadStart",{enumerable:!0,get:function(){return t.stringPadStart}})})),define("consul-ui/helpers/string-repeat",["exports","ember-string-fns/helpers/string-repeat"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringRepeat",{enumerable:!0,get:function(){return t.stringRepeat}})})),define("consul-ui/helpers/string-replace-all",["exports","ember-string-fns/helpers/string-replace-all"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringReplaceAll",{enumerable:!0,get:function(){return t.stringReplaceAll}})})) -define("consul-ui/helpers/string-replace",["exports","ember-string-fns/helpers/string-replace"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringReplace",{enumerable:!0,get:function(){return t.stringReplace}})})),define("consul-ui/helpers/string-slice",["exports","ember-string-fns/helpers/string-slice"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringSlice",{enumerable:!0,get:function(){return t.stringSlice}})})),define("consul-ui/helpers/string-split",["exports","ember-string-fns/helpers/string-split"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringSplit",{enumerable:!0,get:function(){return t.stringSplit}})})),define("consul-ui/helpers/string-starts-with",["exports","ember-string-fns/helpers/string-starts-with"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringStartsWith",{enumerable:!0,get:function(){return t.stringStartsWith}})})),define("consul-ui/helpers/string-substring",["exports","ember-string-fns/helpers/string-substring"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringSubstring",{enumerable:!0,get:function(){return t.stringSubstring}})})),define("consul-ui/helpers/string-to-camel-case",["exports","ember-string-fns/helpers/string-to-camel-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToCamelCase",{enumerable:!0,get:function(){return t.stringToCamelCase}})})),define("consul-ui/helpers/string-to-kebab-case",["exports","ember-string-fns/helpers/string-to-kebab-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToKebabCase",{enumerable:!0,get:function(){return t.stringToKebabCase}})})),define("consul-ui/helpers/string-to-lower-case",["exports","ember-string-fns/helpers/string-to-lower-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToLowerCase",{enumerable:!0,get:function(){return t.stringToLowerCase}})})),define("consul-ui/helpers/string-to-pascal-case",["exports","ember-string-fns/helpers/string-to-pascal-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToPascalCase",{enumerable:!0,get:function(){return t.stringToPascalCase}})})),define("consul-ui/helpers/string-to-sentence-case",["exports","ember-string-fns/helpers/string-to-sentence-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToSentenceCase",{enumerable:!0,get:function(){return t.stringToSentenceCase}})})),define("consul-ui/helpers/string-to-snake-case",["exports","ember-string-fns/helpers/string-to-snake-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToSnakeCase",{enumerable:!0,get:function(){return t.stringToSnakeCase}})})),define("consul-ui/helpers/string-to-title-case",["exports","ember-string-fns/helpers/string-to-title-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToTitleCase",{enumerable:!0,get:function(){return t.stringToTitleCase}})})),define("consul-ui/helpers/string-to-upper-case",["exports","ember-string-fns/helpers/string-to-upper-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToUpperCase",{enumerable:!0,get:function(){return t.stringToUpperCase}})})),define("consul-ui/helpers/string-trim-end",["exports","ember-string-fns/helpers/string-trim-end"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringTrimEnd",{enumerable:!0,get:function(){return t.stringTrimEnd}})})),define("consul-ui/helpers/string-trim-start",["exports","ember-string-fns/helpers/string-trim-start"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringTrimStart",{enumerable:!0,get:function(){return t.stringTrimStart}})})),define("consul-ui/helpers/string-trim",["exports","ember-string-fns/helpers/string-trim"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringTrim",{enumerable:!0,get:function(){return t.stringTrim}})})),define("consul-ui/helpers/style-map",["exports","@ember/component/helper"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=r})),define("consul-ui/helpers/string-includes",["exports","ember-string-fns/helpers/string-includes"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringIncludes",{enumerable:!0,get:function(){return t.stringIncludes}})})),define("consul-ui/helpers/string-index-of",["exports","ember-string-fns/helpers/string-index-of"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringIndexOf",{enumerable:!0,get:function(){return t.stringIndexOf}})})),define("consul-ui/helpers/string-last-index-of",["exports","ember-string-fns/helpers/string-last-index-of"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringLastIndexOf",{enumerable:!0,get:function(){return t.stringLastIndexOf}})})),define("consul-ui/helpers/string-not-equals",["exports","ember-string-fns/helpers/string-not-equals"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringNotEquals",{enumerable:!0,get:function(){return t.stringNotEquals}})})),define("consul-ui/helpers/string-pad-end",["exports","ember-string-fns/helpers/string-pad-end"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringPadEnd",{enumerable:!0,get:function(){return t.stringPadEnd}})})),define("consul-ui/helpers/string-pad-start",["exports","ember-string-fns/helpers/string-pad-start"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringPadStart",{enumerable:!0,get:function(){return t.stringPadStart}})})) +define("consul-ui/helpers/string-repeat",["exports","ember-string-fns/helpers/string-repeat"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringRepeat",{enumerable:!0,get:function(){return t.stringRepeat}})})),define("consul-ui/helpers/string-replace-all",["exports","ember-string-fns/helpers/string-replace-all"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringReplaceAll",{enumerable:!0,get:function(){return t.stringReplaceAll}})})),define("consul-ui/helpers/string-replace",["exports","ember-string-fns/helpers/string-replace"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringReplace",{enumerable:!0,get:function(){return t.stringReplace}})})),define("consul-ui/helpers/string-slice",["exports","ember-string-fns/helpers/string-slice"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringSlice",{enumerable:!0,get:function(){return t.stringSlice}})})),define("consul-ui/helpers/string-split",["exports","ember-string-fns/helpers/string-split"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringSplit",{enumerable:!0,get:function(){return t.stringSplit}})})),define("consul-ui/helpers/string-starts-with",["exports","ember-string-fns/helpers/string-starts-with"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringStartsWith",{enumerable:!0,get:function(){return t.stringStartsWith}})})),define("consul-ui/helpers/string-substring",["exports","ember-string-fns/helpers/string-substring"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringSubstring",{enumerable:!0,get:function(){return t.stringSubstring}})})),define("consul-ui/helpers/string-to-camel-case",["exports","ember-string-fns/helpers/string-to-camel-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToCamelCase",{enumerable:!0,get:function(){return t.stringToCamelCase}})})),define("consul-ui/helpers/string-to-kebab-case",["exports","ember-string-fns/helpers/string-to-kebab-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToKebabCase",{enumerable:!0,get:function(){return t.stringToKebabCase}})})),define("consul-ui/helpers/string-to-lower-case",["exports","ember-string-fns/helpers/string-to-lower-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToLowerCase",{enumerable:!0,get:function(){return t.stringToLowerCase}})})),define("consul-ui/helpers/string-to-pascal-case",["exports","ember-string-fns/helpers/string-to-pascal-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToPascalCase",{enumerable:!0,get:function(){return t.stringToPascalCase}})})),define("consul-ui/helpers/string-to-sentence-case",["exports","ember-string-fns/helpers/string-to-sentence-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToSentenceCase",{enumerable:!0,get:function(){return t.stringToSentenceCase}})})),define("consul-ui/helpers/string-to-snake-case",["exports","ember-string-fns/helpers/string-to-snake-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToSnakeCase",{enumerable:!0,get:function(){return t.stringToSnakeCase}})})),define("consul-ui/helpers/string-to-title-case",["exports","ember-string-fns/helpers/string-to-title-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToTitleCase",{enumerable:!0,get:function(){return t.stringToTitleCase}})})),define("consul-ui/helpers/string-to-upper-case",["exports","ember-string-fns/helpers/string-to-upper-case"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringToUpperCase",{enumerable:!0,get:function(){return t.stringToUpperCase}})})),define("consul-ui/helpers/string-trim-end",["exports","ember-string-fns/helpers/string-trim-end"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringTrimEnd",{enumerable:!0,get:function(){return t.stringTrimEnd}})})),define("consul-ui/helpers/string-trim-start",["exports","ember-string-fns/helpers/string-trim-start"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringTrimStart",{enumerable:!0,get:function(){return t.stringTrimStart}})})),define("consul-ui/helpers/string-trim",["exports","ember-string-fns/helpers/string-trim"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"stringTrim",{enumerable:!0,get:function(){return t.stringTrim}})})),define("consul-ui/helpers/style-map",["exports","@ember/component/helper"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.helper)((function(e){const t=e.reduce(((e,t)=>{let[n,l,r=""]=t return null==l?e:`${e}${n}:${l.toString()}${r};`}),"") return t.length>0?t:void 0})) @@ -2096,11 +2106,11 @@ Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 let o=(l=(0,n.inject)("temporal"),r=class extends t.default{constructor(){var e,t,n,l super(...arguments),e=this,t="temporal",l=this,(n=i)&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}compute(e,t){return this.temporal.within(e,t)}},a=r.prototype,u="temporal",s=[l],c={configurable:!0,enumerable:!0,writable:!0,initializer:null},p={},Object.keys(c).forEach((function(e){p[e]=c[e]})),p.enumerable=!!p.enumerable,p.configurable=!!p.configurable,("value"in p||p.initializer)&&(p.writable=!0),p=s.slice().reverse().reduce((function(e,t){return t(a,u,e)||e}),p),d&&void 0!==p.initializer&&(p.value=p.initializer?p.initializer.call(d):void 0,p.initializer=void 0),void 0===p.initializer&&(Object.defineProperty(a,u,p),p=null),i=p,r) var a,u,s,c,d,p -e.default=o})),define("consul-ui/helpers/test",["exports","consul-ui/helpers/can","consul-ui/helpers/is"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=o})) +define("consul-ui/helpers/test",["exports","consul-ui/helpers/can","consul-ui/helpers/is"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 class l extends t.default{compute(e,t){let[l,r]=e switch(!0){case l.startsWith("can "):return super.compute([l.substr(4),r],t) -case l.startsWith("is "):return(0,n.is)(this,[l.substr(3),r],t)}throw new Error(`${l} is not supported by the 'test' helper.`)}}e.default=l})),define("consul-ui/helpers/titleize",["exports","ember-cli-string-helpers/helpers/titleize"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"titleize",{enumerable:!0,get:function(){return t.titleize}})})) -define("consul-ui/helpers/to-hash",["exports","@ember/component/helper","@ember/object"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +case l.startsWith("is "):return(0,n.is)(this,[l.substr(3),r],t)}throw new Error(`${l} is not supported by the 'test' helper.`)}}e.default=l})),define("consul-ui/helpers/titleize",["exports","ember-cli-string-helpers/helpers/titleize"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"titleize",{enumerable:!0,get:function(){return t.titleize}})})),define("consul-ui/helpers/to-hash",["exports","@ember/component/helper","@ember/object"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var l=(0,t.helper)(((e,t)=>{let[l=[],r]=e return Array.isArray(l)||(l=l.toArray()),l.reduce(((e,t,l)=>(e[(0,n.get)(t,r)]=t,e)),{})})) e.default=l})),define("consul-ui/helpers/to-route",["exports","@ember/component/helper","@ember/service"],(function(e,t,n){var l,r,i,o,a @@ -2163,12 +2173,12 @@ e.default=l})),define("consul-ui/initializers/initialize-torii-session",["export var l={name:"torii-session",after:"torii",initialize(e){arguments[1]&&(e=arguments[1]) const l=(0,n.getConfiguration)() l.sessionServiceName&&(0,t.default)(e,l.sessionServiceName)}} -e.default=l})),define("consul-ui/initializers/initialize-torii",["exports","torii/bootstrap/torii","torii/configuration","consul-ui/config/environment"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=l})) +define("consul-ui/initializers/initialize-torii",["exports","torii/bootstrap/torii","torii/configuration","consul-ui/config/environment"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var r={name:"torii",initialize(e){arguments[1]&&(e=arguments[1]),(0,n.configure)(l.default.torii||{}),(0,t.default)(e)}},i=r e.default=i})),define("consul-ui/initializers/model-fragments",["exports","ember-data-model-fragments","ember-data-model-fragments/ext"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var l={name:"fragmentTransform",after:"ember-data",initialize(){}} -e.default=l})) -define("consul-ui/initializers/setup-ember-can",["exports","ember-can/initializers/setup-ember-can"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"initialize",{enumerable:!0,get:function(){return t.initialize}})})),define("consul-ui/instance-initializers/container",["exports","@ember/debug","require","deepmerge"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.services=e.default=void 0 +e.default=l})),define("consul-ui/initializers/setup-ember-can",["exports","ember-can/initializers/setup-ember-can"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}}),Object.defineProperty(e,"initialize",{enumerable:!0,get:function(){return t.initialize}})})),define("consul-ui/instance-initializers/container",["exports","@ember/debug","require","deepmerge"],(function(e,t,n,l){Object.defineProperty(e,"__esModule",{value:!0}),e.services=e.default=void 0 const r=document,i=l.default.all([...r.querySelectorAll("script[data-services]")].map((e=>JSON.parse(e.dataset.services)))) e.services=i var o={name:"container",initialize(e){(function(e,t){Object.entries(t).forEach((t=>{let[l,r]=t @@ -2253,7 +2263,7 @@ e.default=class{static create(){return new this(...arguments)}constructor(e,t,n) const l=this.doc.querySelector("base[href]") null!==l&&(this.baseURL=l.getAttribute("href"))}initState(){this.location=this.location||this.doc.defaultView.location,this.machine=this.machine||this.doc.defaultView.history,this.doc.defaultView.addEventListener("popstate",this.route) const e=this.machine.state,t=this.getURL(),n=this.formatURL(t) -e&&e.path===n?(this._previousPath=n,this._previousURL=t):this.dispatch("replace",n)}getURLFrom(e){return e=e||this.location.pathname,this.rootURL=this.rootURL.replace(o,""),this.baseURL=this.baseURL.replace(o,""),e.replace(new RegExp(`^${this.baseURL}(?=/|$)`),"").replace(new RegExp(`^${this.rootURL}(?=/|$)`),"")}getURLForTransition(e){return this.optional={},e=this.getURLFrom(e).split("/").filter(((e,t)=>{if(t<3){let t=!1 +e&&e.path===n?(this._previousPath=n,this._previousURL=t):this.dispatch("replace",n)}getURLFrom(e){return e=e||this.location.pathname,this.rootURL=this.rootURL.replace(o,""),this.baseURL=this.baseURL.replace(o,""),e.replace(new RegExp(`^${this.baseURL}(?=/|$)`),"").replace(new RegExp(`^${this.rootURL}(?=/|$)`),"")}getURLForTransition(e){return this.optional={},e=this.getURLFrom(e).split("/").filter(((e,t)=>{if(t<4){let t=!1 return Object.entries(i).reduce(((n,l)=>{let[r,i]=l const o=i.exec(e) return null!==o&&(n[r]={value:e,match:o[1]},t=!0),n}),this.optional),!t}return!0})).join("/")}optionalParams(){let e=this.optional||{} @@ -2320,8 +2330,8 @@ function U(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,config return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.SLUG_KEY=e.PRIMARY_KEY=void 0 e.PRIMARY_KEY="uid" e.SLUG_KEY="Name" -let K=(i=(0,t.attr)("string"),o=(0,t.attr)("string"),a=(0,t.attr)("string"),u=(0,t.attr)("string"),s=(0,t.attr)("string"),c=(0,t.attr)("string",{defaultValue:()=>""}),d=(0,t.attr)("string",{defaultValue:()=>""}),p=(0,t.attr)("string",{defaultValue:()=>"local"}),f=(0,t.attr)("string"),m=(0,t.attr)(),h=(0,n.or)("DisplayName","Name"),b=(0,t.attr)(),y=(0,t.attr)("string"),v=(0,t.attr)("number"),g=(0,t.attr)("number"),O=(0,t.attr)(),P=(0,t.attr)(),w=(0,r.computed)("MaxTokenTTL"),x=class extends t.default{constructor(){super(...arguments),U(this,"uid",j,this),U(this,"Name",_,this),U(this,"Datacenter",S,this),U(this,"Namespace",k,this),U(this,"Partition",N,this),U(this,"Description",C,this),U(this,"DisplayName",z,this),U(this,"TokenLocality",M,this),U(this,"Type",D,this),U(this,"NamespaceRules",T,this),U(this,"MethodName",E,this),U(this,"Config",L,this),U(this,"MaxTokenTTL",A,this),U(this,"CreateIndex",R,this),U(this,"ModifyIndex",I,this),U(this,"Datacenters",$,this),U(this,"meta",F,this)}get TokenTTL(){return(0,l.default)(this.MaxTokenTTL)}},j=B(x.prototype,"uid",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),_=B(x.prototype,"Name",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),S=B(x.prototype,"Datacenter",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),k=B(x.prototype,"Namespace",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),N=B(x.prototype,"Partition",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),C=B(x.prototype,"Description",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),z=B(x.prototype,"DisplayName",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),M=B(x.prototype,"TokenLocality",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),D=B(x.prototype,"Type",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),T=B(x.prototype,"NamespaceRules",[m],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=B(x.prototype,"MethodName",[h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),L=B(x.prototype,"Config",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),A=B(x.prototype,"MaxTokenTTL",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),R=B(x.prototype,"CreateIndex",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),I=B(x.prototype,"ModifyIndex",[g],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),$=B(x.prototype,"Datacenters",[O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),F=B(x.prototype,"meta",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),B(x.prototype,"TokenTTL",[w],Object.getOwnPropertyDescriptor(x.prototype,"TokenTTL"),x.prototype),x) -e.default=K})),define("consul-ui/models/binding-rule",["exports","@ember-data/model"],(function(e,t){var n,l,r,i,o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S +let q=(i=(0,t.attr)("string"),o=(0,t.attr)("string"),a=(0,t.attr)("string"),u=(0,t.attr)("string"),s=(0,t.attr)("string"),c=(0,t.attr)("string",{defaultValue:()=>""}),d=(0,t.attr)("string",{defaultValue:()=>""}),p=(0,t.attr)("string",{defaultValue:()=>"local"}),f=(0,t.attr)("string"),m=(0,t.attr)(),h=(0,n.or)("DisplayName","Name"),b=(0,t.attr)(),y=(0,t.attr)("string"),v=(0,t.attr)("number"),g=(0,t.attr)("number"),O=(0,t.attr)(),P=(0,t.attr)(),w=(0,r.computed)("MaxTokenTTL"),x=class extends t.default{constructor(){super(...arguments),U(this,"uid",j,this),U(this,"Name",_,this),U(this,"Datacenter",S,this),U(this,"Namespace",k,this),U(this,"Partition",N,this),U(this,"Description",C,this),U(this,"DisplayName",z,this),U(this,"TokenLocality",M,this),U(this,"Type",D,this),U(this,"NamespaceRules",T,this),U(this,"MethodName",E,this),U(this,"Config",L,this),U(this,"MaxTokenTTL",A,this),U(this,"CreateIndex",R,this),U(this,"ModifyIndex",I,this),U(this,"Datacenters",$,this),U(this,"meta",F,this)}get TokenTTL(){return(0,l.default)(this.MaxTokenTTL)}},j=B(x.prototype,"uid",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),_=B(x.prototype,"Name",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),S=B(x.prototype,"Datacenter",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),k=B(x.prototype,"Namespace",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),N=B(x.prototype,"Partition",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),C=B(x.prototype,"Description",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),z=B(x.prototype,"DisplayName",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),M=B(x.prototype,"TokenLocality",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),D=B(x.prototype,"Type",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),T=B(x.prototype,"NamespaceRules",[m],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=B(x.prototype,"MethodName",[h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),L=B(x.prototype,"Config",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),A=B(x.prototype,"MaxTokenTTL",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),R=B(x.prototype,"CreateIndex",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),I=B(x.prototype,"ModifyIndex",[g],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),$=B(x.prototype,"Datacenters",[O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),F=B(x.prototype,"meta",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),B(x.prototype,"TokenTTL",[w],Object.getOwnPropertyDescriptor(x.prototype,"TokenTTL"),x.prototype),x) +e.default=q})),define("consul-ui/models/binding-rule",["exports","@ember-data/model"],(function(e,t){var n,l,r,i,o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S function k(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function N(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.SLUG_KEY=e.PRIMARY_KEY=void 0 e.PRIMARY_KEY="uid" @@ -2373,35 +2383,35 @@ return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumera const d={Action:{defaultValue:"allow",allowedValues:["allow","deny"]}} e.schema=d let p=(r=(0,l.attr)("string",{defaultValue:()=>d.Action.defaultValue}),i=(0,n.fragment)("intention-permission-http"),o=class extends t.default{constructor(){super(...arguments),s(this,"Action",a,this),s(this,"HTTP",u,this)}},a=c(o.prototype,"Action",[r],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),u=c(o.prototype,"HTTP",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),o) -e.default=p})),define("consul-ui/models/intention",["exports","@ember-data/model","@ember/object","ember-data-model-fragments/attributes","consul-ui/decorators/replace"],(function(e,t,n,l,r){var i,o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z,M,D,T,E,L,A,R,I,$,F,U,B,K,q,H,Y,G,V,W,Z,Q,J,X,ee,te,ne +e.default=p})) +define("consul-ui/models/intention",["exports","@ember-data/model","@ember/object","ember-data-model-fragments/attributes","consul-ui/decorators/replace"],(function(e,t,n,l,r){var i,o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z,M,D,T,E,L,A,R,I,$,F,U,B,q,K,H,Y,G,V,W,Z,Q,J,X,ee,te,ne function le(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function re(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.SLUG_KEY=e.PRIMARY_KEY=void 0 e.PRIMARY_KEY="uid" e.SLUG_KEY="ID" -let ie=(i=(0,t.attr)("string"),o=(0,t.attr)("string"),a=(0,t.attr)("string"),u=(0,t.attr)("string"),s=(0,r.default)("",void 0),c=(0,t.attr)("string"),d=(0,t.attr)("string",{defaultValue:()=>"*"}),p=(0,t.attr)("string",{defaultValue:()=>"*"}),f=(0,t.attr)("string",{defaultValue:()=>"default"}),m=(0,t.attr)("string",{defaultValue:()=>"default"}),h=(0,t.attr)("string",{defaultValue:()=>"default"}),b=(0,t.attr)("string",{defaultValue:()=>"default"}),y=(0,t.attr)("number"),v=(0,t.attr)("string",{defaultValue:()=>"consul"}),g=(0,r.nullValue)(void 0),O=(0,t.attr)("string"),P=(0,t.attr)("string"),w=(0,t.attr)("boolean",{defaultValue:()=>!0}),x=(0,t.attr)("number"),j=(0,t.attr)("date"),_=(0,t.attr)("date"),S=(0,t.attr)("number"),k=(0,t.attr)("number"),N=(0,t.attr)(),C=(0,t.attr)({defaultValue:()=>[]}),z=(0,l.fragmentArray)("intention-permission"),M=(0,n.computed)("Meta"),D=class extends t.default{constructor(){super(...arguments),le(this,"uid",T,this),le(this,"ID",E,this),le(this,"Datacenter",L,this),le(this,"Description",A,this),le(this,"SourcePeer",R,this),le(this,"SourceName",I,this),le(this,"DestinationName",$,this),le(this,"SourceNS",F,this),le(this,"DestinationNS",U,this),le(this,"SourcePartition",B,this),le(this,"DestinationPartition",K,this),le(this,"Precedence",q,this),le(this,"SourceType",H,this),le(this,"Action",Y,this),le(this,"LegacyID",G,this),le(this,"Legacy",V,this),le(this,"SyncTime",W,this),le(this,"CreatedAt",Z,this),le(this,"UpdatedAt",Q,this),le(this,"CreateIndex",J,this),le(this,"ModifyIndex",X,this),le(this,"Meta",ee,this),le(this,"Resources",te,this),le(this,"Permissions",ne,this)}get IsManagedByCRD(){return void 0!==Object.entries(this.Meta||{}).find((e=>{let[t,n]=e -return"external-source"===t&&"kubernetes"===n}))}},T=re(D.prototype,"uid",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=re(D.prototype,"ID",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),L=re(D.prototype,"Datacenter",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),A=re(D.prototype,"Description",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),R=re(D.prototype,"SourcePeer",[s,c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),I=re(D.prototype,"SourceName",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),$=re(D.prototype,"DestinationName",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),F=re(D.prototype,"SourceNS",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),U=re(D.prototype,"DestinationNS",[m],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),B=re(D.prototype,"SourcePartition",[h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),K=re(D.prototype,"DestinationPartition",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),q=re(D.prototype,"Precedence",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),H=re(D.prototype,"SourceType",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Y=re(D.prototype,"Action",[g,O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),G=re(D.prototype,"LegacyID",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),V=re(D.prototype,"Legacy",[w],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),W=re(D.prototype,"SyncTime",[x],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Z=re(D.prototype,"CreatedAt",[j],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Q=re(D.prototype,"UpdatedAt",[_],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),J=re(D.prototype,"CreateIndex",[S],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),X=re(D.prototype,"ModifyIndex",[k],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ee=re(D.prototype,"Meta",[N],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),te=re(D.prototype,"Resources",[C],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ne=re(D.prototype,"Permissions",[z],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),re(D.prototype,"IsManagedByCRD",[M],Object.getOwnPropertyDescriptor(D.prototype,"IsManagedByCRD"),D.prototype),D) +let ie=(i=(0,t.attr)("string"),o=(0,t.attr)("string"),a=(0,t.attr)("string"),u=(0,t.attr)("string"),s=(0,r.default)("",void 0),c=(0,t.attr)("string"),d=(0,t.attr)("string",{defaultValue:()=>"*"}),p=(0,t.attr)("string",{defaultValue:()=>"*"}),f=(0,t.attr)("string",{defaultValue:()=>"default"}),m=(0,t.attr)("string",{defaultValue:()=>"default"}),h=(0,t.attr)("string",{defaultValue:()=>"default"}),b=(0,t.attr)("string",{defaultValue:()=>"default"}),y=(0,t.attr)("number"),v=(0,t.attr)("string",{defaultValue:()=>"consul"}),g=(0,r.nullValue)(void 0),O=(0,t.attr)("string"),P=(0,t.attr)("string"),w=(0,t.attr)("boolean",{defaultValue:()=>!0}),x=(0,t.attr)("number"),j=(0,t.attr)("date"),_=(0,t.attr)("date"),S=(0,t.attr)("number"),k=(0,t.attr)("number"),N=(0,t.attr)(),C=(0,t.attr)({defaultValue:()=>[]}),z=(0,l.fragmentArray)("intention-permission"),M=(0,n.computed)("Meta"),D=class extends t.default{constructor(){super(...arguments),le(this,"uid",T,this),le(this,"ID",E,this),le(this,"Datacenter",L,this),le(this,"Description",A,this),le(this,"SourcePeer",R,this),le(this,"SourceName",I,this),le(this,"DestinationName",$,this),le(this,"SourceNS",F,this),le(this,"DestinationNS",U,this),le(this,"SourcePartition",B,this),le(this,"DestinationPartition",q,this),le(this,"Precedence",K,this),le(this,"SourceType",H,this),le(this,"Action",Y,this),le(this,"LegacyID",G,this),le(this,"Legacy",V,this),le(this,"SyncTime",W,this),le(this,"CreatedAt",Z,this),le(this,"UpdatedAt",Q,this),le(this,"CreateIndex",J,this),le(this,"ModifyIndex",X,this),le(this,"Meta",ee,this),le(this,"Resources",te,this),le(this,"Permissions",ne,this)}get IsManagedByCRD(){return void 0!==Object.entries(this.Meta||{}).find((e=>{let[t,n]=e +return"external-source"===t&&"kubernetes"===n}))}},T=re(D.prototype,"uid",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=re(D.prototype,"ID",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),L=re(D.prototype,"Datacenter",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),A=re(D.prototype,"Description",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),R=re(D.prototype,"SourcePeer",[s,c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),I=re(D.prototype,"SourceName",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),$=re(D.prototype,"DestinationName",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),F=re(D.prototype,"SourceNS",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),U=re(D.prototype,"DestinationNS",[m],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),B=re(D.prototype,"SourcePartition",[h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),q=re(D.prototype,"DestinationPartition",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),K=re(D.prototype,"Precedence",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),H=re(D.prototype,"SourceType",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Y=re(D.prototype,"Action",[g,O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),G=re(D.prototype,"LegacyID",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),V=re(D.prototype,"Legacy",[w],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),W=re(D.prototype,"SyncTime",[x],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Z=re(D.prototype,"CreatedAt",[j],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Q=re(D.prototype,"UpdatedAt",[_],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),J=re(D.prototype,"CreateIndex",[S],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),X=re(D.prototype,"ModifyIndex",[k],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ee=re(D.prototype,"Meta",[N],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),te=re(D.prototype,"Resources",[C],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ne=re(D.prototype,"Permissions",[z],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),re(D.prototype,"IsManagedByCRD",[M],Object.getOwnPropertyDescriptor(D.prototype,"IsManagedByCRD"),D.prototype),D) e.default=ie})),define("consul-ui/models/kv",["exports","@ember-data/model","@ember/object","consul-ui/utils/isFolder","consul-ui/decorators/replace"],(function(e,t,n,l,r){var i,o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z,M,D,T,E,L,A function R(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function I(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.SLUG_KEY=e.PRIMARY_KEY=void 0 e.PRIMARY_KEY="uid" e.SLUG_KEY="Key" let $=(i=(0,t.attr)("string"),o=(0,t.attr)("string"),a=(0,t.attr)("number"),u=(0,t.attr)(),s=(0,t.attr)("string"),c=(0,t.attr)("string"),d=(0,t.attr)("string"),p=(0,t.attr)("number"),f=(0,t.attr)("number"),m=(0,r.nullValue)(void 0),h=(0,t.attr)("string"),b=(0,t.attr)("number"),y=(0,t.attr)("number"),v=(0,t.attr)("string"),g=(0,t.attr)({defaultValue:()=>[]}),O=(0,n.computed)("isFolder"),P=(0,n.computed)("Key"),w=class extends t.default{constructor(){super(...arguments),R(this,"uid",x,this),R(this,"Key",j,this),R(this,"SyncTime",_,this),R(this,"meta",S,this),R(this,"Datacenter",k,this),R(this,"Namespace",N,this),R(this,"Partition",C,this),R(this,"LockIndex",z,this),R(this,"Flags",M,this),R(this,"Value",D,this),R(this,"CreateIndex",T,this),R(this,"ModifyIndex",E,this),R(this,"Session",L,this),R(this,"Resources",A,this)}get Kind(){return this.isFolder?"folder":"key"}get isFolder(){return(0,l.default)(this.Key||"")}},x=I(w.prototype,"uid",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),j=I(w.prototype,"Key",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),_=I(w.prototype,"SyncTime",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),S=I(w.prototype,"meta",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),k=I(w.prototype,"Datacenter",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),N=I(w.prototype,"Namespace",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),C=I(w.prototype,"Partition",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),z=I(w.prototype,"LockIndex",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),M=I(w.prototype,"Flags",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),D=I(w.prototype,"Value",[m,h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),T=I(w.prototype,"CreateIndex",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=I(w.prototype,"ModifyIndex",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),L=I(w.prototype,"Session",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),A=I(w.prototype,"Resources",[g],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),I(w.prototype,"Kind",[O],Object.getOwnPropertyDescriptor(w.prototype,"Kind"),w.prototype),I(w.prototype,"isFolder",[P],Object.getOwnPropertyDescriptor(w.prototype,"isFolder"),w.prototype),w) -e.default=$})) -define("consul-ui/models/license",["exports","@ember-data/model"],(function(e,t){var n,l,r,i,o,a,u,s,c,d,p,f,m,h,b,y,v +e.default=$})),define("consul-ui/models/license",["exports","@ember-data/model"],(function(e,t){var n,l,r,i,o,a,u,s,c,d,p,f,m,h,b,y,v function g(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function O(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.PRIMARY_KEY=void 0 e.PRIMARY_KEY="uri" let P=(n=(0,t.attr)("string"),l=(0,t.attr)("boolean"),r=(0,t.attr)("number"),i=(0,t.attr)(),o=(0,t.attr)("string"),a=(0,t.attr)("string"),u=(0,t.attr)("string"),s=(0,t.attr)(),c=class extends t.default{constructor(){super(...arguments),g(this,"uri",d,this),g(this,"Valid",p,this),g(this,"SyncTime",f,this),g(this,"meta",m,this),g(this,"Datacenter",h,this),g(this,"Namespace",b,this),g(this,"Partition",y,this),g(this,"License",v,this)}},d=O(c.prototype,"uri",[n],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),p=O(c.prototype,"Valid",[l],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),f=O(c.prototype,"SyncTime",[r],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),m=O(c.prototype,"meta",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),h=O(c.prototype,"Datacenter",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),b=O(c.prototype,"Namespace",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),y=O(c.prototype,"Partition",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),v=O(c.prototype,"License",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),c) -e.default=P})),define("consul-ui/models/node",["exports","@ember-data/model","@ember/object","@ember/object/computed","ember-data-model-fragments/attributes"],(function(e,t,n,l,r){var i,o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z,M,D,T,E,L,A,R,I,$,F,U,B,K,q,H,Y,G,V +e.default=P})),define("consul-ui/models/node",["exports","@ember-data/model","@ember/object","@ember/object/computed","ember-data-model-fragments/attributes"],(function(e,t,n,l,r){var i,o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z,M,D,T,E,L,A,R,I,$,F,U,B,q,K,H,Y,G,V function W(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function Z(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.SLUG_KEY=e.PRIMARY_KEY=void 0 e.PRIMARY_KEY="uid" e.SLUG_KEY="ID" -let Q=(i=(0,t.attr)("string"),o=(0,t.attr)("string"),a=(0,t.attr)("string"),u=(0,t.attr)("string"),s=(0,t.attr)("string"),c=(0,t.attr)("string"),d=(0,t.attr)("string"),p=(0,t.attr)("number"),f=(0,t.attr)("number"),m=(0,t.attr)("number"),h=(0,t.attr)(),b=(0,t.attr)(),y=(0,t.attr)(),v=(0,t.attr)({defaultValue:()=>[]}),g=(0,t.hasMany)("service-instance"),O=(0,r.fragmentArray)("health-check"),P=(0,l.filter)("Services",(e=>"connect-proxy"!==e.Service.Kind)),w=(0,l.filter)("Services",(e=>"connect-proxy"===e.Service.Kind)),x=(0,l.filter)("Checks",(e=>""===e.ServiceID)),j=(0,n.computed)("ChecksCritical","ChecksPassing","ChecksWarning"),_=(0,n.computed)("NodeChecks.[]"),S=(0,n.computed)("NodeChecks.[]"),k=(0,n.computed)("NodeChecks.[]"),N=(0,n.computed)("Meta"),C=class extends t.default{constructor(){super(...arguments),W(this,"uid",z,this),W(this,"ID",M,this),W(this,"Datacenter",D,this),W(this,"PeerName",T,this),W(this,"Partition",E,this),W(this,"Address",L,this),W(this,"Node",A,this),W(this,"SyncTime",R,this),W(this,"CreateIndex",I,this),W(this,"ModifyIndex",$,this),W(this,"meta",F,this),W(this,"Meta",U,this),W(this,"TaggedAddresses",B,this),W(this,"Resources",K,this),W(this,"Services",q,this),W(this,"Checks",H,this),W(this,"MeshServiceInstances",Y,this),W(this,"ProxyServiceInstances",G,this),W(this,"NodeChecks",V,this)}get Status(){switch(!0){case 0!==this.ChecksCritical:return"critical" +let Q=(i=(0,t.attr)("string"),o=(0,t.attr)("string"),a=(0,t.attr)("string"),u=(0,t.attr)("string"),s=(0,t.attr)("string"),c=(0,t.attr)("string"),d=(0,t.attr)("string"),p=(0,t.attr)("number"),f=(0,t.attr)("number"),m=(0,t.attr)("number"),h=(0,t.attr)(),b=(0,t.attr)(),y=(0,t.attr)(),v=(0,t.attr)({defaultValue:()=>[]}),g=(0,t.hasMany)("service-instance"),O=(0,r.fragmentArray)("health-check"),P=(0,l.filter)("Services",(e=>"connect-proxy"!==e.Service.Kind)),w=(0,l.filter)("Services",(e=>"connect-proxy"===e.Service.Kind)),x=(0,l.filter)("Checks",(e=>""===e.ServiceID)),j=(0,n.computed)("ChecksCritical","ChecksPassing","ChecksWarning"),_=(0,n.computed)("NodeChecks.[]"),S=(0,n.computed)("NodeChecks.[]"),k=(0,n.computed)("NodeChecks.[]"),N=(0,n.computed)("Meta"),C=class extends t.default{constructor(){super(...arguments),W(this,"uid",z,this),W(this,"ID",M,this),W(this,"Datacenter",D,this),W(this,"PeerName",T,this),W(this,"Partition",E,this),W(this,"Address",L,this),W(this,"Node",A,this),W(this,"SyncTime",R,this),W(this,"CreateIndex",I,this),W(this,"ModifyIndex",$,this),W(this,"meta",F,this),W(this,"Meta",U,this),W(this,"TaggedAddresses",B,this),W(this,"Resources",q,this),W(this,"Services",K,this),W(this,"Checks",H,this),W(this,"MeshServiceInstances",Y,this),W(this,"ProxyServiceInstances",G,this),W(this,"NodeChecks",V,this)}get Status(){switch(!0){case 0!==this.ChecksCritical:return"critical" case 0!==this.ChecksWarning:return"warning" case 0!==this.ChecksPassing:return"passing" default:return"empty"}}get ChecksCritical(){return this.NodeChecks.filter((e=>"critical"===e.Status)).length}get ChecksPassing(){return this.NodeChecks.filter((e=>"passing"===e.Status)).length}get ChecksWarning(){return this.NodeChecks.filter((e=>"warning"===e.Status)).length}get Version(){var e,t -return null!==(e=null===(t=this.Meta)||void 0===t?void 0:t["consul-version"])&&void 0!==e?e:""}},z=Z(C.prototype,"uid",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),M=Z(C.prototype,"ID",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),D=Z(C.prototype,"Datacenter",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),T=Z(C.prototype,"PeerName",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=Z(C.prototype,"Partition",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),L=Z(C.prototype,"Address",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),A=Z(C.prototype,"Node",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),R=Z(C.prototype,"SyncTime",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),I=Z(C.prototype,"CreateIndex",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),$=Z(C.prototype,"ModifyIndex",[m],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),F=Z(C.prototype,"meta",[h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),U=Z(C.prototype,"Meta",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),B=Z(C.prototype,"TaggedAddresses",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),K=Z(C.prototype,"Resources",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),q=Z(C.prototype,"Services",[g],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),H=Z(C.prototype,"Checks",[O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Y=Z(C.prototype,"MeshServiceInstances",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),G=Z(C.prototype,"ProxyServiceInstances",[w],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),V=Z(C.prototype,"NodeChecks",[x],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Z(C.prototype,"Status",[j],Object.getOwnPropertyDescriptor(C.prototype,"Status"),C.prototype),Z(C.prototype,"ChecksCritical",[_],Object.getOwnPropertyDescriptor(C.prototype,"ChecksCritical"),C.prototype),Z(C.prototype,"ChecksPassing",[S],Object.getOwnPropertyDescriptor(C.prototype,"ChecksPassing"),C.prototype),Z(C.prototype,"ChecksWarning",[k],Object.getOwnPropertyDescriptor(C.prototype,"ChecksWarning"),C.prototype),Z(C.prototype,"Version",[N],Object.getOwnPropertyDescriptor(C.prototype,"Version"),C.prototype),C) +return null!==(e=null===(t=this.Meta)||void 0===t?void 0:t["consul-version"])&&void 0!==e?e:""}},z=Z(C.prototype,"uid",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),M=Z(C.prototype,"ID",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),D=Z(C.prototype,"Datacenter",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),T=Z(C.prototype,"PeerName",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=Z(C.prototype,"Partition",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),L=Z(C.prototype,"Address",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),A=Z(C.prototype,"Node",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),R=Z(C.prototype,"SyncTime",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),I=Z(C.prototype,"CreateIndex",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),$=Z(C.prototype,"ModifyIndex",[m],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),F=Z(C.prototype,"meta",[h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),U=Z(C.prototype,"Meta",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),B=Z(C.prototype,"TaggedAddresses",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),q=Z(C.prototype,"Resources",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),K=Z(C.prototype,"Services",[g],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),H=Z(C.prototype,"Checks",[O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Y=Z(C.prototype,"MeshServiceInstances",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),G=Z(C.prototype,"ProxyServiceInstances",[w],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),V=Z(C.prototype,"NodeChecks",[x],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Z(C.prototype,"Status",[j],Object.getOwnPropertyDescriptor(C.prototype,"Status"),C.prototype),Z(C.prototype,"ChecksCritical",[_],Object.getOwnPropertyDescriptor(C.prototype,"ChecksCritical"),C.prototype),Z(C.prototype,"ChecksPassing",[S],Object.getOwnPropertyDescriptor(C.prototype,"ChecksPassing"),C.prototype),Z(C.prototype,"ChecksWarning",[k],Object.getOwnPropertyDescriptor(C.prototype,"ChecksWarning"),C.prototype),Z(C.prototype,"Version",[N],Object.getOwnPropertyDescriptor(C.prototype,"Version"),C.prototype),C) e.default=Q})),define("consul-ui/models/nspace",["exports","@ember-data/model"],(function(e,t){var n,l,r,i,o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w function x(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function j(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.SLUG_KEY=e.PRIMARY_KEY=e.NSPACE_KEY=void 0 @@ -2451,7 +2461,7 @@ return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumera e.PRIMARY_KEY="uid" e.SLUG_KEY="ID" let R=(n=(0,t.attr)("string"),l=(0,t.attr)("string"),r=(0,t.attr)("string"),i=(0,t.attr)("string"),o=(0,t.attr)("string"),a=(0,t.attr)("string",{defaultValue:()=>""}),u=(0,t.attr)("string",{defaultValue:()=>""}),s=(0,t.attr)({defaultValue:()=>[]}),c=(0,t.attr)({defaultValue:()=>[]}),d=(0,t.attr)({defaultValue:()=>[]}),p=(0,t.attr)("number"),f=(0,t.attr)("number"),m=(0,t.attr)("number"),h=(0,t.attr)("number"),b=(0,t.attr)(),y=(0,t.attr)("string"),v=class extends t.default{constructor(){super(...arguments),L(this,"uid",g,this),L(this,"ID",O,this),L(this,"Datacenter",P,this),L(this,"Namespace",w,this),L(this,"Partition",x,this),L(this,"Name",j,this),L(this,"Description",_,this),L(this,"Policies",S,this),L(this,"ServiceIdentities",k,this),L(this,"NodeIdentities",N,this),L(this,"SyncTime",C,this),L(this,"CreateIndex",z,this),L(this,"ModifyIndex",M,this),L(this,"CreateTime",D,this),L(this,"Datacenters",T,this),L(this,"Hash",E,this)}},g=A(v.prototype,"uid",[n],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),O=A(v.prototype,"ID",[l],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),P=A(v.prototype,"Datacenter",[r],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),w=A(v.prototype,"Namespace",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),x=A(v.prototype,"Partition",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),j=A(v.prototype,"Name",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),_=A(v.prototype,"Description",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),S=A(v.prototype,"Policies",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),k=A(v.prototype,"ServiceIdentities",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),N=A(v.prototype,"NodeIdentities",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),C=A(v.prototype,"SyncTime",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),z=A(v.prototype,"CreateIndex",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),M=A(v.prototype,"ModifyIndex",[m],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),D=A(v.prototype,"CreateTime",[h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),T=A(v.prototype,"Datacenters",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=A(v.prototype,"Hash",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),v) -e.default=R})),define("consul-ui/models/service-instance",["exports","@ember-data/model","ember-data-model-fragments/attributes","@ember/object","@ember/object/computed","@glimmer/tracking"],(function(e,t,n,l,r,i){var o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z,M,D,T,E,L,A,R,I,$,F,U,B,K,q,H,Y,G,V,W,Z,Q,J,X,ee,te,ne,le +e.default=R})),define("consul-ui/models/service-instance",["exports","@ember-data/model","ember-data-model-fragments/attributes","@ember/object","@ember/object/computed","@glimmer/tracking"],(function(e,t,n,l,r,i){var o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z,M,D,T,E,L,A,R,I,$,F,U,B,q,K,H,Y,G,V,W,Z,Q,J,X,ee,te,ne,le function re(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function ie(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.SLUG_KEY=e.PRIMARY_KEY=e.Collection=void 0 e.PRIMARY_KEY="uid" @@ -2459,14 +2469,14 @@ e.SLUG_KEY="Node.Node,Service.ID" const oe=(a=ie((o=class{constructor(e){re(this,"items",a,this),this.items=e}get ExternalSources(){const e=this.items.reduce((function(e,t){return e.concat(t.ExternalSources||[])}),[]) return[...new Set(e)].filter(Boolean).sort()}}).prototype,"items",[i.tracked],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),o) e.Collection=oe -let ae=(u=(0,t.attr)("string"),s=(0,t.attr)("string"),c=(0,t.attr)(),d=(0,t.attr)(),p=(0,t.attr)(),f=(0,n.fragmentArray)("health-check"),m=(0,t.attr)("number"),h=(0,t.attr)(),b=(0,t.attr)({defaultValue:()=>[]}),y=(0,r.alias)("Service.Service"),v=(0,r.or)("Service.{ID,Service}"),g=(0,r.or)("Service.Address","Node.Service"),O=(0,t.attr)("string"),P=(0,r.alias)("Service.Tags"),w=(0,r.alias)("Service.Meta"),x=(0,r.alias)("Service.Namespace"),j=(0,r.alias)("Service.Partition"),_=(0,r.filter)("Checks.@each.Kind",((e,t,n)=>"service"===e.Kind)),S=(0,r.filter)("Checks.@each.Kind",((e,t,n)=>"node"===e.Kind)),k=(0,l.computed)("Service.Meta"),N=(0,l.computed)("Service.Kind"),C=(0,l.computed)("Service.Kind"),z=(0,l.computed)("IsOrigin"),M=(0,l.computed)("ChecksPassing","ChecksWarning","ChecksCritical"),D=(0,l.computed)("Checks.[]"),T=(0,l.computed)("Checks.[]"),E=(0,l.computed)("Checks.[]"),L=(0,l.computed)("Checks.[]","ChecksPassing"),A=(0,l.computed)("Checks.[]","ChecksWarning"),R=(0,l.computed)("Checks.[]","ChecksCritical"),I=class extends t.default{constructor(){super(...arguments),re(this,"uid",$,this),re(this,"Datacenter",F,this),re(this,"Proxy",U,this),re(this,"Node",B,this),re(this,"Service",K,this),re(this,"Checks",q,this),re(this,"SyncTime",H,this),re(this,"meta",Y,this),re(this,"Resources",G,this),re(this,"Name",V,this),re(this,"ID",W,this),re(this,"Address",Z,this),re(this,"SocketPath",Q,this),re(this,"Tags",J,this),re(this,"Meta",X,this),re(this,"Namespace",ee,this),re(this,"Partition",te,this),re(this,"ServiceChecks",ne,this),re(this,"NodeChecks",le,this)}get ExternalSources(){const e=Object.entries(this.Service.Meta||{}).filter((e=>{let[t,n]=e +let ae=(u=(0,t.attr)("string"),s=(0,t.attr)("string"),c=(0,t.attr)(),d=(0,t.attr)(),p=(0,t.attr)(),f=(0,n.fragmentArray)("health-check"),m=(0,t.attr)("number"),h=(0,t.attr)(),b=(0,t.attr)({defaultValue:()=>[]}),y=(0,r.alias)("Service.Service"),v=(0,r.or)("Service.{ID,Service}"),g=(0,r.or)("Service.Address","Node.Service"),O=(0,t.attr)("string"),P=(0,r.alias)("Service.Tags"),w=(0,r.alias)("Service.Meta"),x=(0,r.alias)("Service.Namespace"),j=(0,r.alias)("Service.Partition"),_=(0,r.filter)("Checks.@each.Kind",((e,t,n)=>"service"===e.Kind)),S=(0,r.filter)("Checks.@each.Kind",((e,t,n)=>"node"===e.Kind)),k=(0,l.computed)("Service.Meta"),N=(0,l.computed)("Service.Kind"),C=(0,l.computed)("Service.Kind"),z=(0,l.computed)("IsOrigin"),M=(0,l.computed)("ChecksPassing","ChecksWarning","ChecksCritical"),D=(0,l.computed)("Checks.[]"),T=(0,l.computed)("Checks.[]"),E=(0,l.computed)("Checks.[]"),L=(0,l.computed)("Checks.[]","ChecksPassing"),A=(0,l.computed)("Checks.[]","ChecksWarning"),R=(0,l.computed)("Checks.[]","ChecksCritical"),I=class extends t.default{constructor(){super(...arguments),re(this,"uid",$,this),re(this,"Datacenter",F,this),re(this,"Proxy",U,this),re(this,"Node",B,this),re(this,"Service",q,this),re(this,"Checks",K,this),re(this,"SyncTime",H,this),re(this,"meta",Y,this),re(this,"Resources",G,this),re(this,"Name",V,this),re(this,"ID",W,this),re(this,"Address",Z,this),re(this,"SocketPath",Q,this),re(this,"Tags",J,this),re(this,"Meta",X,this),re(this,"Namespace",ee,this),re(this,"Partition",te,this),re(this,"ServiceChecks",ne,this),re(this,"NodeChecks",le,this)}get ExternalSources(){const e=Object.entries(this.Service.Meta||{}).filter((e=>{let[t,n]=e return"external-source"===t})).map((e=>{let[t,n]=e return n})) return[...new Set(e)]}get IsProxy(){return["connect-proxy","mesh-gateway","ingress-gateway","terminating-gateway","api-gateway"].includes(this.Service.Kind)}get IsOrigin(){return!["connect-proxy","mesh-gateway"].includes(this.Service.Kind)}get IsMeshOrigin(){return this.IsOrigin&&!["terminating-gateway"].includes(this.Service.Kind)}get Status(){switch(!0){case 0!==this.ChecksCritical.length:return"critical" case 0!==this.ChecksWarning.length:return"warning" case 0!==this.ChecksPassing.length:return"passing" -default:return"empty"}}get ChecksPassing(){return this.Checks.filter((e=>"passing"===e.Status))}get ChecksWarning(){return this.Checks.filter((e=>"warning"===e.Status))}get ChecksCritical(){return this.Checks.filter((e=>"critical"===e.Status))}get PercentageChecksPassing(){return this.ChecksPassing.length/this.Checks.length*100}get PercentageChecksWarning(){return this.ChecksWarning.length/this.Checks.length*100}get PercentageChecksCritical(){return this.ChecksCritical.length/this.Checks.length*100}},$=ie(I.prototype,"uid",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),F=ie(I.prototype,"Datacenter",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),U=ie(I.prototype,"Proxy",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),B=ie(I.prototype,"Node",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),K=ie(I.prototype,"Service",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),q=ie(I.prototype,"Checks",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),H=ie(I.prototype,"SyncTime",[m],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Y=ie(I.prototype,"meta",[h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),G=ie(I.prototype,"Resources",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),V=ie(I.prototype,"Name",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),W=ie(I.prototype,"ID",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Z=ie(I.prototype,"Address",[g],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Q=ie(I.prototype,"SocketPath",[O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),J=ie(I.prototype,"Tags",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),X=ie(I.prototype,"Meta",[w],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ee=ie(I.prototype,"Namespace",[x],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),te=ie(I.prototype,"Partition",[j],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ne=ie(I.prototype,"ServiceChecks",[_],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),le=ie(I.prototype,"NodeChecks",[S],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ie(I.prototype,"ExternalSources",[k],Object.getOwnPropertyDescriptor(I.prototype,"ExternalSources"),I.prototype),ie(I.prototype,"IsProxy",[N],Object.getOwnPropertyDescriptor(I.prototype,"IsProxy"),I.prototype),ie(I.prototype,"IsOrigin",[C],Object.getOwnPropertyDescriptor(I.prototype,"IsOrigin"),I.prototype),ie(I.prototype,"IsMeshOrigin",[z],Object.getOwnPropertyDescriptor(I.prototype,"IsMeshOrigin"),I.prototype),ie(I.prototype,"Status",[M],Object.getOwnPropertyDescriptor(I.prototype,"Status"),I.prototype),ie(I.prototype,"ChecksPassing",[D],Object.getOwnPropertyDescriptor(I.prototype,"ChecksPassing"),I.prototype),ie(I.prototype,"ChecksWarning",[T],Object.getOwnPropertyDescriptor(I.prototype,"ChecksWarning"),I.prototype),ie(I.prototype,"ChecksCritical",[E],Object.getOwnPropertyDescriptor(I.prototype,"ChecksCritical"),I.prototype),ie(I.prototype,"PercentageChecksPassing",[L],Object.getOwnPropertyDescriptor(I.prototype,"PercentageChecksPassing"),I.prototype),ie(I.prototype,"PercentageChecksWarning",[A],Object.getOwnPropertyDescriptor(I.prototype,"PercentageChecksWarning"),I.prototype),ie(I.prototype,"PercentageChecksCritical",[R],Object.getOwnPropertyDescriptor(I.prototype,"PercentageChecksCritical"),I.prototype),I) -e.default=ae})),define("consul-ui/models/service",["exports","@ember-data/model","@ember/object","@glimmer/tracking","ember-data-model-fragments/attributes","consul-ui/decorators/replace"],(function(e,t,n,l,r,i){var o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z,M,D,T,E,L,A,R,I,$,F,U,B,K,q,H,Y,G,V,W,Z,Q,J,X,ee,te,ne,le,re,ie,oe,ae,ue,se,ce,de,pe,fe,me,he,be,ye,ve +default:return"empty"}}get ChecksPassing(){return this.Checks.filter((e=>"passing"===e.Status))}get ChecksWarning(){return this.Checks.filter((e=>"warning"===e.Status))}get ChecksCritical(){return this.Checks.filter((e=>"critical"===e.Status))}get PercentageChecksPassing(){return this.ChecksPassing.length/this.Checks.length*100}get PercentageChecksWarning(){return this.ChecksWarning.length/this.Checks.length*100}get PercentageChecksCritical(){return this.ChecksCritical.length/this.Checks.length*100}},$=ie(I.prototype,"uid",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),F=ie(I.prototype,"Datacenter",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),U=ie(I.prototype,"Proxy",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),B=ie(I.prototype,"Node",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),q=ie(I.prototype,"Service",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),K=ie(I.prototype,"Checks",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),H=ie(I.prototype,"SyncTime",[m],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Y=ie(I.prototype,"meta",[h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),G=ie(I.prototype,"Resources",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),V=ie(I.prototype,"Name",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),W=ie(I.prototype,"ID",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Z=ie(I.prototype,"Address",[g],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Q=ie(I.prototype,"SocketPath",[O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),J=ie(I.prototype,"Tags",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),X=ie(I.prototype,"Meta",[w],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ee=ie(I.prototype,"Namespace",[x],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),te=ie(I.prototype,"Partition",[j],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ne=ie(I.prototype,"ServiceChecks",[_],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),le=ie(I.prototype,"NodeChecks",[S],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ie(I.prototype,"ExternalSources",[k],Object.getOwnPropertyDescriptor(I.prototype,"ExternalSources"),I.prototype),ie(I.prototype,"IsProxy",[N],Object.getOwnPropertyDescriptor(I.prototype,"IsProxy"),I.prototype),ie(I.prototype,"IsOrigin",[C],Object.getOwnPropertyDescriptor(I.prototype,"IsOrigin"),I.prototype),ie(I.prototype,"IsMeshOrigin",[z],Object.getOwnPropertyDescriptor(I.prototype,"IsMeshOrigin"),I.prototype),ie(I.prototype,"Status",[M],Object.getOwnPropertyDescriptor(I.prototype,"Status"),I.prototype),ie(I.prototype,"ChecksPassing",[D],Object.getOwnPropertyDescriptor(I.prototype,"ChecksPassing"),I.prototype),ie(I.prototype,"ChecksWarning",[T],Object.getOwnPropertyDescriptor(I.prototype,"ChecksWarning"),I.prototype),ie(I.prototype,"ChecksCritical",[E],Object.getOwnPropertyDescriptor(I.prototype,"ChecksCritical"),I.prototype),ie(I.prototype,"PercentageChecksPassing",[L],Object.getOwnPropertyDescriptor(I.prototype,"PercentageChecksPassing"),I.prototype),ie(I.prototype,"PercentageChecksWarning",[A],Object.getOwnPropertyDescriptor(I.prototype,"PercentageChecksWarning"),I.prototype),ie(I.prototype,"PercentageChecksCritical",[R],Object.getOwnPropertyDescriptor(I.prototype,"PercentageChecksCritical"),I.prototype),I) +e.default=ae})),define("consul-ui/models/service",["exports","@ember-data/model","@ember/object","@glimmer/tracking","ember-data-model-fragments/attributes","consul-ui/decorators/replace"],(function(e,t,n,l,r,i){var o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z,M,D,T,E,L,A,R,I,$,F,U,B,q,K,H,Y,G,V,W,Z,Q,J,X,ee,te,ne,le,re,ie,oe,ae,ue,se,ce,de,pe,fe,me,he,be,ye,ve function ge(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function Oe(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.SLUG_KEY=e.PRIMARY_KEY=e.Collection=void 0 e.PRIMARY_KEY="uid" @@ -2474,7 +2484,7 @@ e.SLUG_KEY="Name,PeerName" const Pe=(a=Oe((o=class{constructor(e){ge(this,"items",a,this),this.items=e}get ExternalSources(){const e=this.items.reduce((function(e,t){return e.concat(t.ExternalSources||[])}),[]) return[...new Set(e)].filter(Boolean).sort()}get Partitions(){return[...new Set(this.items.map((e=>e.Partition)))].sort()}}).prototype,"items",[l.tracked],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),o) e.Collection=Pe -let we=(u=(0,t.attr)("string"),s=(0,t.attr)("string"),c=(0,t.attr)("string"),d=(0,t.attr)("string"),p=(0,t.attr)("string"),f=(0,t.attr)("string"),m=(0,i.default)("",void 0),h=(0,t.attr)("string"),b=(0,t.attr)("number"),y=(0,t.attr)("number"),v=(0,t.attr)("number"),g=(0,t.attr)("number"),O=(0,t.attr)("boolean"),P=(0,t.attr)("boolean"),w=(0,t.attr)({defaultValue:()=>[]}),x=(0,t.attr)("number"),j=(0,t.attr)("number"),_=(0,t.attr)("number"),S=(0,i.nullValue)([]),k=(0,t.attr)({defaultValue:()=>[]}),N=(0,t.attr)(),C=(0,t.attr)(),z=(0,r.fragment)("gateway-config"),M=(0,i.nullValue)([]),D=(0,t.attr)(),T=(0,t.attr)(),E=(0,t.attr)(),L=(0,t.belongsTo)({async:!1}),A=(0,n.computed)("peer","InstanceCount"),R=(0,n.computed)("peer.State"),I=(0,n.computed)("ChecksPassing","ChecksWarning","ChecksCritical"),$=(0,n.computed)("MeshChecksPassing","MeshChecksWarning","MeshChecksCritical"),F=(0,n.computed)("ConnectedWithProxy","ConnectedWithGateway"),U=(0,n.computed)("MeshEnabled","Kind"),B=(0,n.computed)("MeshChecksPassing","MeshChecksWarning","MeshChecksCritical","isZeroCountButPeered","peerIsFailing"),K=(0,n.computed)("isZeroCountButPeered","peerIsFailing","MeshStatus"),q=(0,n.computed)("ChecksPassing","Proxy.ChecksPassing"),H=(0,n.computed)("ChecksWarning","Proxy.ChecksWarning"),Y=(0,n.computed)("ChecksCritical","Proxy.ChecksCritical"),G=class extends t.default{constructor(){super(...arguments),ge(this,"uid",V,this),ge(this,"Name",W,this),ge(this,"Datacenter",Z,this),ge(this,"Namespace",Q,this),ge(this,"Partition",J,this),ge(this,"Kind",X,this),ge(this,"PeerName",ee,this),ge(this,"ChecksPassing",te,this),ge(this,"ChecksCritical",ne,this),ge(this,"ChecksWarning",le,this),ge(this,"InstanceCount",re,this),ge(this,"ConnectedWithGateway",ie,this),ge(this,"ConnectedWithProxy",oe,this),ge(this,"Resources",ae,this),ge(this,"SyncTime",ue,this),ge(this,"CreateIndex",se,this),ge(this,"ModifyIndex",ce,this),ge(this,"Tags",de,this),ge(this,"Nodes",pe,this),ge(this,"Proxy",fe,this),ge(this,"GatewayConfig",me,this),ge(this,"ExternalSources",he,this),ge(this,"Meta",be,this),ge(this,"meta",ye,this),ge(this,"peer",ve,this)}get isZeroCountButPeered(){return this.peer&&0===this.InstanceCount}get peerIsFailing(){return this.peer&&"FAILING"===this.peer.State}get ChecksTotal(){return this.ChecksPassing+this.ChecksWarning+this.ChecksCritical}get MeshChecksTotal(){return this.MeshChecksPassing+this.MeshChecksWarning+this.MeshChecksCritical}get MeshEnabled(){return this.ConnectedWithProxy||this.ConnectedWithGateway}get InMesh(){return this.MeshEnabled||(this.Kind||"").length>0}get MeshStatus(){switch(!0){case this.isZeroCountButPeered:case this.peerIsFailing:return"unknown" +let we=(u=(0,t.attr)("string"),s=(0,t.attr)("string"),c=(0,t.attr)("string"),d=(0,t.attr)("string"),p=(0,t.attr)("string"),f=(0,t.attr)("string"),m=(0,i.default)("",void 0),h=(0,t.attr)("string"),b=(0,t.attr)("number"),y=(0,t.attr)("number"),v=(0,t.attr)("number"),g=(0,t.attr)("number"),O=(0,t.attr)("boolean"),P=(0,t.attr)("boolean"),w=(0,t.attr)({defaultValue:()=>[]}),x=(0,t.attr)("number"),j=(0,t.attr)("number"),_=(0,t.attr)("number"),S=(0,i.nullValue)([]),k=(0,t.attr)({defaultValue:()=>[]}),N=(0,t.attr)(),C=(0,t.attr)(),z=(0,r.fragment)("gateway-config"),M=(0,i.nullValue)([]),D=(0,t.attr)(),T=(0,t.attr)(),E=(0,t.attr)(),L=(0,t.belongsTo)({async:!1}),A=(0,n.computed)("peer","InstanceCount"),R=(0,n.computed)("peer.State"),I=(0,n.computed)("ChecksPassing","ChecksWarning","ChecksCritical"),$=(0,n.computed)("MeshChecksPassing","MeshChecksWarning","MeshChecksCritical"),F=(0,n.computed)("ConnectedWithProxy","ConnectedWithGateway"),U=(0,n.computed)("MeshEnabled","Kind"),B=(0,n.computed)("MeshChecksPassing","MeshChecksWarning","MeshChecksCritical","isZeroCountButPeered","peerIsFailing"),q=(0,n.computed)("isZeroCountButPeered","peerIsFailing","MeshStatus"),K=(0,n.computed)("ChecksPassing","Proxy.ChecksPassing"),H=(0,n.computed)("ChecksWarning","Proxy.ChecksWarning"),Y=(0,n.computed)("ChecksCritical","Proxy.ChecksCritical"),G=class extends t.default{constructor(){super(...arguments),ge(this,"uid",V,this),ge(this,"Name",W,this),ge(this,"Datacenter",Z,this),ge(this,"Namespace",Q,this),ge(this,"Partition",J,this),ge(this,"Kind",X,this),ge(this,"PeerName",ee,this),ge(this,"ChecksPassing",te,this),ge(this,"ChecksCritical",ne,this),ge(this,"ChecksWarning",le,this),ge(this,"InstanceCount",re,this),ge(this,"ConnectedWithGateway",ie,this),ge(this,"ConnectedWithProxy",oe,this),ge(this,"Resources",ae,this),ge(this,"SyncTime",ue,this),ge(this,"CreateIndex",se,this),ge(this,"ModifyIndex",ce,this),ge(this,"Tags",de,this),ge(this,"Nodes",pe,this),ge(this,"Proxy",fe,this),ge(this,"GatewayConfig",me,this),ge(this,"ExternalSources",he,this),ge(this,"Meta",be,this),ge(this,"meta",ye,this),ge(this,"peer",ve,this)}get isZeroCountButPeered(){return this.peer&&0===this.InstanceCount}get peerIsFailing(){return this.peer&&"FAILING"===this.peer.State}get ChecksTotal(){return this.ChecksPassing+this.ChecksWarning+this.ChecksCritical}get MeshChecksTotal(){return this.MeshChecksPassing+this.MeshChecksWarning+this.MeshChecksCritical}get MeshEnabled(){return this.ConnectedWithProxy||this.ConnectedWithGateway}get InMesh(){return this.MeshEnabled||(this.Kind||"").length>0}get MeshStatus(){switch(!0){case this.isZeroCountButPeered:case this.peerIsFailing:return"unknown" case 0!==this.MeshChecksCritical:return"critical" case 0!==this.MeshChecksWarning:return"warning" case 0!==this.MeshChecksPassing:return"passing" @@ -2482,7 +2492,7 @@ default:return"empty"}}get healthTooltipText(){const{MeshStatus:e,isZeroCountBut return t?"This service currently has 0 instances. Check with the operator of its peer to make sure this is expected behavior.":n?"This peer is out of sync, so the current health statuses of its services are unknown.":"critical"===e?"At least one health check on one instance is failing.":"warning"===e?"At least one health check on one instance has a warning.":"passing"==e?"All health checks are passing.":"There are no health checks"}get MeshChecksPassing(){let e=0 return void 0!==this.Proxy&&(e=this.Proxy.ChecksPassing),this.ChecksPassing+e}get MeshChecksWarning(){let e=0 return void 0!==this.Proxy&&(e=this.Proxy.ChecksWarning),this.ChecksWarning+e}get MeshChecksCritical(){let e=0 -return void 0!==this.Proxy&&(e=this.Proxy.ChecksCritical),this.ChecksCritical+e}},V=Oe(G.prototype,"uid",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),W=Oe(G.prototype,"Name",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Z=Oe(G.prototype,"Datacenter",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Q=Oe(G.prototype,"Namespace",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),J=Oe(G.prototype,"Partition",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),X=Oe(G.prototype,"Kind",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ee=Oe(G.prototype,"PeerName",[m,h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),te=Oe(G.prototype,"ChecksPassing",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ne=Oe(G.prototype,"ChecksCritical",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),le=Oe(G.prototype,"ChecksWarning",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),re=Oe(G.prototype,"InstanceCount",[g],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ie=Oe(G.prototype,"ConnectedWithGateway",[O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),oe=Oe(G.prototype,"ConnectedWithProxy",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ae=Oe(G.prototype,"Resources",[w],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ue=Oe(G.prototype,"SyncTime",[x],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),se=Oe(G.prototype,"CreateIndex",[j],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ce=Oe(G.prototype,"ModifyIndex",[_],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),de=Oe(G.prototype,"Tags",[S,k],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),pe=Oe(G.prototype,"Nodes",[N],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),fe=Oe(G.prototype,"Proxy",[C],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),me=Oe(G.prototype,"GatewayConfig",[z],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),he=Oe(G.prototype,"ExternalSources",[M,D],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),be=Oe(G.prototype,"Meta",[T],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ye=Oe(G.prototype,"meta",[E],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ve=Oe(G.prototype,"peer",[L],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Oe(G.prototype,"isZeroCountButPeered",[A],Object.getOwnPropertyDescriptor(G.prototype,"isZeroCountButPeered"),G.prototype),Oe(G.prototype,"peerIsFailing",[R],Object.getOwnPropertyDescriptor(G.prototype,"peerIsFailing"),G.prototype),Oe(G.prototype,"ChecksTotal",[I],Object.getOwnPropertyDescriptor(G.prototype,"ChecksTotal"),G.prototype),Oe(G.prototype,"MeshChecksTotal",[$],Object.getOwnPropertyDescriptor(G.prototype,"MeshChecksTotal"),G.prototype),Oe(G.prototype,"MeshEnabled",[F],Object.getOwnPropertyDescriptor(G.prototype,"MeshEnabled"),G.prototype),Oe(G.prototype,"InMesh",[U],Object.getOwnPropertyDescriptor(G.prototype,"InMesh"),G.prototype),Oe(G.prototype,"MeshStatus",[B],Object.getOwnPropertyDescriptor(G.prototype,"MeshStatus"),G.prototype),Oe(G.prototype,"healthTooltipText",[K],Object.getOwnPropertyDescriptor(G.prototype,"healthTooltipText"),G.prototype),Oe(G.prototype,"MeshChecksPassing",[q],Object.getOwnPropertyDescriptor(G.prototype,"MeshChecksPassing"),G.prototype),Oe(G.prototype,"MeshChecksWarning",[H],Object.getOwnPropertyDescriptor(G.prototype,"MeshChecksWarning"),G.prototype),Oe(G.prototype,"MeshChecksCritical",[Y],Object.getOwnPropertyDescriptor(G.prototype,"MeshChecksCritical"),G.prototype),G) +return void 0!==this.Proxy&&(e=this.Proxy.ChecksCritical),this.ChecksCritical+e}},V=Oe(G.prototype,"uid",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),W=Oe(G.prototype,"Name",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Z=Oe(G.prototype,"Datacenter",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Q=Oe(G.prototype,"Namespace",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),J=Oe(G.prototype,"Partition",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),X=Oe(G.prototype,"Kind",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ee=Oe(G.prototype,"PeerName",[m,h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),te=Oe(G.prototype,"ChecksPassing",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ne=Oe(G.prototype,"ChecksCritical",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),le=Oe(G.prototype,"ChecksWarning",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),re=Oe(G.prototype,"InstanceCount",[g],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ie=Oe(G.prototype,"ConnectedWithGateway",[O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),oe=Oe(G.prototype,"ConnectedWithProxy",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ae=Oe(G.prototype,"Resources",[w],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ue=Oe(G.prototype,"SyncTime",[x],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),se=Oe(G.prototype,"CreateIndex",[j],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ce=Oe(G.prototype,"ModifyIndex",[_],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),de=Oe(G.prototype,"Tags",[S,k],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),pe=Oe(G.prototype,"Nodes",[N],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),fe=Oe(G.prototype,"Proxy",[C],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),me=Oe(G.prototype,"GatewayConfig",[z],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),he=Oe(G.prototype,"ExternalSources",[M,D],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),be=Oe(G.prototype,"Meta",[T],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ye=Oe(G.prototype,"meta",[E],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),ve=Oe(G.prototype,"peer",[L],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Oe(G.prototype,"isZeroCountButPeered",[A],Object.getOwnPropertyDescriptor(G.prototype,"isZeroCountButPeered"),G.prototype),Oe(G.prototype,"peerIsFailing",[R],Object.getOwnPropertyDescriptor(G.prototype,"peerIsFailing"),G.prototype),Oe(G.prototype,"ChecksTotal",[I],Object.getOwnPropertyDescriptor(G.prototype,"ChecksTotal"),G.prototype),Oe(G.prototype,"MeshChecksTotal",[$],Object.getOwnPropertyDescriptor(G.prototype,"MeshChecksTotal"),G.prototype),Oe(G.prototype,"MeshEnabled",[F],Object.getOwnPropertyDescriptor(G.prototype,"MeshEnabled"),G.prototype),Oe(G.prototype,"InMesh",[U],Object.getOwnPropertyDescriptor(G.prototype,"InMesh"),G.prototype),Oe(G.prototype,"MeshStatus",[B],Object.getOwnPropertyDescriptor(G.prototype,"MeshStatus"),G.prototype),Oe(G.prototype,"healthTooltipText",[q],Object.getOwnPropertyDescriptor(G.prototype,"healthTooltipText"),G.prototype),Oe(G.prototype,"MeshChecksPassing",[K],Object.getOwnPropertyDescriptor(G.prototype,"MeshChecksPassing"),G.prototype),Oe(G.prototype,"MeshChecksWarning",[H],Object.getOwnPropertyDescriptor(G.prototype,"MeshChecksWarning"),G.prototype),Oe(G.prototype,"MeshChecksCritical",[Y],Object.getOwnPropertyDescriptor(G.prototype,"MeshChecksCritical"),G.prototype),G) e.default=we})),define("consul-ui/models/session",["exports","@ember-data/model","@ember/object","consul-ui/decorators/replace"],(function(e,t,n,l){var r,i,o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z,M,D,T,E,L,A,R,I,$ function F(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function U(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.SLUG_KEY=e.PRIMARY_KEY=void 0 @@ -2490,12 +2500,12 @@ e.PRIMARY_KEY="uid" e.SLUG_KEY="ID" let B=(r=(0,t.attr)("string"),i=(0,t.attr)("string"),o=(0,t.attr)("string"),a=(0,t.attr)("string"),u=(0,t.attr)("string"),s=(0,t.attr)("string"),c=(0,t.attr)("string"),d=(0,t.attr)("string"),p=(0,t.attr)("string"),f=(0,t.attr)("number"),m=(0,t.attr)("number"),h=(0,t.attr)("number"),b=(0,t.attr)("number"),y=(0,l.nullValue)([]),v=(0,t.attr)({defaultValue:()=>[]}),g=(0,l.nullValue)([]),O=(0,t.attr)({defaultValue:()=>[]}),P=(0,t.attr)({defaultValue:()=>[]}),w=(0,n.computed)("NodeChecks","ServiceChecks"),x=class extends t.default{constructor(){super(...arguments),F(this,"uid",j,this),F(this,"ID",_,this),F(this,"Name",S,this),F(this,"Datacenter",k,this),F(this,"Namespace",N,this),F(this,"Partition",C,this),F(this,"Node",z,this),F(this,"Behavior",M,this),F(this,"TTL",D,this),F(this,"LockDelay",T,this),F(this,"SyncTime",E,this),F(this,"CreateIndex",L,this),F(this,"ModifyIndex",A,this),F(this,"NodeChecks",R,this),F(this,"ServiceChecks",I,this),F(this,"Resources",$,this)}get checks(){return[...this.NodeChecks,...this.ServiceChecks.map((e=>{let{ID:t}=e return t}))]}},j=U(x.prototype,"uid",[r],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),_=U(x.prototype,"ID",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),S=U(x.prototype,"Name",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),k=U(x.prototype,"Datacenter",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),N=U(x.prototype,"Namespace",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),C=U(x.prototype,"Partition",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),z=U(x.prototype,"Node",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),M=U(x.prototype,"Behavior",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),D=U(x.prototype,"TTL",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),T=U(x.prototype,"LockDelay",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=U(x.prototype,"SyncTime",[m],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),L=U(x.prototype,"CreateIndex",[h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),A=U(x.prototype,"ModifyIndex",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),R=U(x.prototype,"NodeChecks",[y,v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),I=U(x.prototype,"ServiceChecks",[g,O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),$=U(x.prototype,"Resources",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),U(x.prototype,"checks",[w],Object.getOwnPropertyDescriptor(x.prototype,"checks"),x.prototype),x) -e.default=B})),define("consul-ui/models/token",["exports","@ember-data/model","@ember/object","consul-ui/models/policy"],(function(e,t,n,l){var r,i,o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z,M,D,T,E,L,A,R,I,$,F,U,B,K,q,H,Y,G,V,W,Z +e.default=B})),define("consul-ui/models/token",["exports","@ember-data/model","@ember/object","consul-ui/models/policy"],(function(e,t,n,l){var r,i,o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z,M,D,T,E,L,A,R,I,$,F,U,B,q,K,H,Y,G,V,W,Z function Q(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function J(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.SLUG_KEY=e.PRIMARY_KEY=void 0 e.PRIMARY_KEY="uid" e.SLUG_KEY="AccessorID" -let X=(r=(0,t.attr)("string"),i=(0,t.attr)("string"),o=(0,t.attr)("string"),a=(0,t.attr)("string"),u=(0,t.attr)("string"),s=(0,t.attr)("string"),c=(0,t.attr)("string"),d=(0,t.attr)("boolean"),p=(0,t.attr)("boolean"),f=(0,t.attr)("string",{defaultValue:()=>""}),m=(0,t.attr)(),h=(0,t.attr)({defaultValue:()=>[]}),b=(0,t.attr)({defaultValue:()=>[]}),y=(0,t.attr)({defaultValue:()=>[]}),v=(0,t.attr)({defaultValue:()=>[]}),g=(0,t.attr)("date"),O=(0,t.attr)("string"),P=(0,t.attr)("number"),w=(0,t.attr)("number"),x=(0,t.attr)("string"),j=(0,t.attr)("string",{defaultValue:()=>""}),_=(0,t.attr)("string"),S=(0,n.computed)("Policies.[]"),k=(0,n.computed)("SecretID"),N=class extends t.default{constructor(){super(...arguments),Q(this,"uid",C,this),Q(this,"AccessorID",z,this),Q(this,"Datacenter",M,this),Q(this,"Namespace",D,this),Q(this,"Partition",T,this),Q(this,"IDPName",E,this),Q(this,"SecretID",L,this),Q(this,"Legacy",A,this),Q(this,"Local",R,this),Q(this,"Description",I,this),Q(this,"meta",$,this),Q(this,"Policies",F,this),Q(this,"Roles",U,this),Q(this,"ServiceIdentities",B,this),Q(this,"NodeIdentities",K,this),Q(this,"CreateTime",q,this),Q(this,"Hash",H,this),Q(this,"CreateIndex",Y,this),Q(this,"ModifyIndex",G,this),Q(this,"Type",V,this),Q(this,"Name",W,this),Q(this,"Rules",Z,this)}get isGlobalManagement(){return(this.Policies||[]).find((e=>e.ID===l.MANAGEMENT_ID))}get hasSecretID(){return""!==this.SecretID&&""!==this.SecretID}},C=J(N.prototype,"uid",[r],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),z=J(N.prototype,"AccessorID",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),M=J(N.prototype,"Datacenter",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),D=J(N.prototype,"Namespace",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),T=J(N.prototype,"Partition",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=J(N.prototype,"IDPName",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),L=J(N.prototype,"SecretID",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),A=J(N.prototype,"Legacy",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),R=J(N.prototype,"Local",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),I=J(N.prototype,"Description",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),$=J(N.prototype,"meta",[m],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),F=J(N.prototype,"Policies",[h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),U=J(N.prototype,"Roles",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),B=J(N.prototype,"ServiceIdentities",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),K=J(N.prototype,"NodeIdentities",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),q=J(N.prototype,"CreateTime",[g],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),H=J(N.prototype,"Hash",[O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Y=J(N.prototype,"CreateIndex",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),G=J(N.prototype,"ModifyIndex",[w],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),V=J(N.prototype,"Type",[x],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),W=J(N.prototype,"Name",[j],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Z=J(N.prototype,"Rules",[_],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),J(N.prototype,"isGlobalManagement",[S],Object.getOwnPropertyDescriptor(N.prototype,"isGlobalManagement"),N.prototype),J(N.prototype,"hasSecretID",[k],Object.getOwnPropertyDescriptor(N.prototype,"hasSecretID"),N.prototype),N) +let X=(r=(0,t.attr)("string"),i=(0,t.attr)("string"),o=(0,t.attr)("string"),a=(0,t.attr)("string"),u=(0,t.attr)("string"),s=(0,t.attr)("string"),c=(0,t.attr)("string"),d=(0,t.attr)("boolean"),p=(0,t.attr)("boolean"),f=(0,t.attr)("string",{defaultValue:()=>""}),m=(0,t.attr)(),h=(0,t.attr)({defaultValue:()=>[]}),b=(0,t.attr)({defaultValue:()=>[]}),y=(0,t.attr)({defaultValue:()=>[]}),v=(0,t.attr)({defaultValue:()=>[]}),g=(0,t.attr)("date"),O=(0,t.attr)("string"),P=(0,t.attr)("number"),w=(0,t.attr)("number"),x=(0,t.attr)("string"),j=(0,t.attr)("string",{defaultValue:()=>""}),_=(0,t.attr)("string"),S=(0,n.computed)("Policies.[]"),k=(0,n.computed)("SecretID"),N=class extends t.default{constructor(){super(...arguments),Q(this,"uid",C,this),Q(this,"AccessorID",z,this),Q(this,"Datacenter",M,this),Q(this,"Namespace",D,this),Q(this,"Partition",T,this),Q(this,"IDPName",E,this),Q(this,"SecretID",L,this),Q(this,"Legacy",A,this),Q(this,"Local",R,this),Q(this,"Description",I,this),Q(this,"meta",$,this),Q(this,"Policies",F,this),Q(this,"Roles",U,this),Q(this,"ServiceIdentities",B,this),Q(this,"NodeIdentities",q,this),Q(this,"CreateTime",K,this),Q(this,"Hash",H,this),Q(this,"CreateIndex",Y,this),Q(this,"ModifyIndex",G,this),Q(this,"Type",V,this),Q(this,"Name",W,this),Q(this,"Rules",Z,this)}get isGlobalManagement(){return(this.Policies||[]).find((e=>e.ID===l.MANAGEMENT_ID))}get hasSecretID(){return""!==this.SecretID&&""!==this.SecretID}},C=J(N.prototype,"uid",[r],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),z=J(N.prototype,"AccessorID",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),M=J(N.prototype,"Datacenter",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),D=J(N.prototype,"Namespace",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),T=J(N.prototype,"Partition",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),E=J(N.prototype,"IDPName",[s],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),L=J(N.prototype,"SecretID",[c],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),A=J(N.prototype,"Legacy",[d],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),R=J(N.prototype,"Local",[p],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),I=J(N.prototype,"Description",[f],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),$=J(N.prototype,"meta",[m],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),F=J(N.prototype,"Policies",[h],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),U=J(N.prototype,"Roles",[b],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),B=J(N.prototype,"ServiceIdentities",[y],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),q=J(N.prototype,"NodeIdentities",[v],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),K=J(N.prototype,"CreateTime",[g],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),H=J(N.prototype,"Hash",[O],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Y=J(N.prototype,"CreateIndex",[P],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),G=J(N.prototype,"ModifyIndex",[w],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),V=J(N.prototype,"Type",[x],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),W=J(N.prototype,"Name",[j],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),Z=J(N.prototype,"Rules",[_],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),J(N.prototype,"isGlobalManagement",[S],Object.getOwnPropertyDescriptor(N.prototype,"isGlobalManagement"),N.prototype),J(N.prototype,"hasSecretID",[k],Object.getOwnPropertyDescriptor(N.prototype,"hasSecretID"),N.prototype),N) e.default=X})),define("consul-ui/models/topology",["exports","@ember-data/model","@ember/object"],(function(e,t,n){var l,r,i,o,a,u,s,c,d,p,f,m,h,b,y,v,g,O,P,w,x,j,_,S,k,N,C,z function M(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function D(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=e.SLUG_KEY=e.PRIMARY_KEY=void 0 @@ -2558,15 +2568,15 @@ const r=function(e){for(var t=1;tr.after())).catch((e=>{if("TransitionAborted"!==e.name)throw e})).then((e=>{this.notify.add(r)})):this.notify.add(r),(0,l.registerDestructor)(this,s)}},d=i.prototype,p="notify",f=[r],m={configurable:!0,enumerable:!0,writable:!0,initializer:null},b={},Object.keys(m).forEach((function(e){b[e]=m[e]})),b.enumerable=!!b.enumerable,b.configurable=!!b.configurable,("value"in b||b.initializer)&&(b.writable=!0),b=f.slice().reverse().reduce((function(e,t){return t(d,p,e)||e}),b),h&&void 0!==b.initializer&&(b.value=b.initializer?b.initializer.call(h):void 0,b.initializer=void 0),void 0===b.initializer&&(Object.defineProperty(d,p,b),b=null),o=b,i) var d,p,f,m,h,b -e.default=c})),define("consul-ui/modifiers/on-key",["exports","ember-keyboard/modifiers/on-key.js"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/modifiers/on-outside",["exports","ember-modifier","@ember/object","@ember/service","@ember/destroyable"],(function(e,t,n,l,r){var i,o,a +e.default=c})),define("consul-ui/modifiers/on-key",["exports","ember-keyboard/modifiers/on-key.js"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) +define("consul-ui/modifiers/on-outside",["exports","ember-modifier","@ember/object","@ember/service","@ember/destroyable"],(function(e,t,n,l,r){var i,o,a function u(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}function s(e){var t e&&(null===(t=e.doc)||void 0===t||t.removeEventListener("click",e.listen))}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 let c=(i=(0,l.inject)("dom"),o=class extends t.default{constructor(e,t){var n,l,i,o super(e,t),n=this,l="dom",o=this,(i=a)&&Object.defineProperty(n,l,{enumerable:i.enumerable,configurable:i.configurable,writable:i.writable,value:i.initializer?i.initializer.call(o):void 0}),this.doc=this.dom.document(),(0,r.registerDestructor)(this,s)}async modify(e,t,n){s.call(this),this.params=t,this.options=n,this.element=e,await new Promise((e=>setTimeout(e,0))) try{this.doc.addEventListener(t[0],this.listen)}catch(l){}}listen(e){if(this.element&&this.dom.isOutside(this.element,e.target)){("function"==typeof this.params[1]?this.params[1]:e=>{}).apply(this.element,[e])}}},a=u(o.prototype,"dom",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),u(o.prototype,"listen",[n.action],Object.getOwnPropertyDescriptor(o.prototype,"listen"),o.prototype),o) -e.default=c})),define("consul-ui/modifiers/on-resize",["exports","ember-on-resize-modifier/modifiers/on-resize"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})) -define("consul-ui/modifiers/style",["exports","ember-modifier","@ember/debug"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=c})),define("consul-ui/modifiers/on-resize",["exports","ember-on-resize-modifier/modifiers/on-resize"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/modifiers/style",["exports","ember-modifier","@ember/debug"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 class l extends t.default{setStyles(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[] const t=this._oldStyles||new Set Array.isArray(e)||(e=Object.entries(e)),e.forEach((e=>{let[n,l]=e,r="" @@ -2664,18 +2674,18 @@ e.callback(t),function e(t,n){"/"!==n.name&&(t=t[n.name]={_options:{path:n.name} t||(t="data:,%s") const n=(0,o.dump)(c) t.startsWith("data:,")?(e=window.open("","_blank"),e.document.write(`
${n}
`)):e=window.open(t.replace("%s",encodeURIComponent(n)),"_blank"),e.focus()}})) -class d extends t.default{constructor(){super(...arguments),a(this,"location",(0,i.env)("locationType")),a(this,"rootURL",(0,i.env)("rootURL"))}}e.default=d,d.map((0,o.default)(c))})),define("consul-ui/routes/application",["exports","consul-ui/routing/route","@ember/object","@ember/service","consul-ui/mixins/with-blocking-actions"],(function(e,t,n,l,r){var i,o,a,u,s,c,d -function p(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function f(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e +class d extends t.default{constructor(){super(...arguments),a(this,"location",(0,i.env)("locationType")),a(this,"rootURL",(0,i.env)("rootURL"))}}e.default=d,d.map((0,o.default)(c))})),define("consul-ui/routes/application",["exports","consul-ui/routing/route","@ember/object","@ember/service","consul-ui/mixins/with-blocking-actions"],(function(e,t,n,l,r){var i,o,a,u,s,c,d,p,f +function m(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function h(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e var n=e[Symbol.toPrimitive] if(void 0!==n){var l=n.call(e,t||"default") if("object"!=typeof l)return l throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string") -return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function m(e,t,n,l,r){var i={} +return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function b(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -let h=(i=(0,l.inject)("client/http"),o=(0,l.inject)("env"),a=(0,l.inject)(),u=class extends(t.default.extend(r.default)){constructor(){super(...arguments),p(this,"client",s,this),p(this,"env",c,this),p(this,"hcp",d,this),f(this,"data",void 0)}async model(){return this.env.var("CONSUL_ACLS_ENABLED")&&await this.hcp.updateTokenIfNecessary(this.env.var("CONSUL_HTTP_TOKEN")),{}}onClientChanged(e){let t=e.data +let y=(i=(0,l.inject)("client/http"),o=(0,l.inject)("env"),a=(0,l.inject)(),u=(0,l.inject)(),s=class extends(t.default.extend(r.default)){constructor(){super(...arguments),m(this,"client",c,this),m(this,"env",d,this),m(this,"hcp",p,this),m(this,"router",f,this),h(this,"data",void 0)}beforeModel(){this.env.var("CONSUL_V2_CATALOG_ENABLED")&&this.router.replaceWith("unavailable")}async model(){return this.env.var("CONSUL_ACLS_ENABLED")&&await this.hcp.updateTokenIfNecessary(this.env.var("CONSUL_HTTP_TOKEN")),{}}onClientChanged(e){let t=e.data ""===t&&(t={blocking:!0}),void 0!==this.data?(!0===this.data.blocking&&!1===t.blocking&&this.client.abort(),this.data=Object.assign({},t)):this.data=Object.assign({},t)}error(e,t){let n={status:e.code||e.statusCode||"",message:e.message||e.detail||"Error"} -return e.errors&&e.errors[0]&&(n=e.errors[0],n.message=n.message||n.title||n.detail||"Error"),""===n.status&&(n.message="Error"),this.controllerFor("application").setProperties({error:n}),!0}},s=m(u.prototype,"client",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),c=m(u.prototype,"env",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),d=m(u.prototype,"hcp",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),m(u.prototype,"onClientChanged",[n.action],Object.getOwnPropertyDescriptor(u.prototype,"onClientChanged"),u.prototype),m(u.prototype,"error",[n.action],Object.getOwnPropertyDescriptor(u.prototype,"error"),u.prototype),u) -e.default=h})),define("consul-ui/routes/dc",["exports","@ember/service","consul-ui/routing/route"],(function(e,t,n){var l,r,i +return e.errors&&e.errors[0]&&(n=e.errors[0],n.message=n.message||n.title||n.detail||"Error"),""===n.status&&(n.message="Error"),this.controllerFor("application").setProperties({error:n}),!0}},c=b(s.prototype,"client",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),d=b(s.prototype,"env",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),p=b(s.prototype,"hcp",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),f=b(s.prototype,"router",[u],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),b(s.prototype,"onClientChanged",[n.action],Object.getOwnPropertyDescriptor(s.prototype,"onClientChanged"),s.prototype),b(s.prototype,"error",[n.action],Object.getOwnPropertyDescriptor(s.prototype,"error"),s.prototype),s) +e.default=y})),define("consul-ui/routes/dc",["exports","@ember/service","consul-ui/routing/route"],(function(e,t,n){var l,r,i Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 let o=(l=(0,t.inject)("repository/permission"),r=class extends n.default{constructor(){var e,t,n,l super(...arguments),e=this,t="permissionsRepo",l=this,(n=i)&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}async model(e){const t=await this.permissionsRepo.findAll({dc:e.dc,ns:this.optionalParams().nspace,partition:this.optionalParams().partition}) @@ -2779,7 +2789,11 @@ let b=(r=(0,n.inject)("data-source/service"),i=(0,n.inject)("repository/intentio try{let r=(await this.intentions).find((n=>n.Datacenter===e.Datacenter&&n.SourceName===e.Name&&n.SourceNS===e.Namespace&&n.SourcePartition===e.Partition&&n.DestinationName===t.Name&&n.DestinationNS===t.Namespace&&n.DestinationPartition===t.Partition)) void 0===r?r=this.repo.create({Datacenter:e.Datacenter,SourceName:e.Name,SourceNS:e.Namespace||"default",SourcePartition:e.Partition||"default",DestinationName:t.Name,DestinationNS:t.Namespace||"default",DestinationPartition:t.Partition||"default"}):n=this.feedback.notification("update","intention"),(0,l.set)(r,"Action","allow"),await this.repo.persist(r),n.success(r)}catch(r){n.error(r)}this.refresh()}afterModel(e,t){const n=p(p(p({},this.optionalParams()),this.paramsFor("dc")),this.paramsFor("dc.services.show")) this.intentions=this.data.source((e=>e`/${n.partition}/${n.nspace}/${n.dc}/intentions/for-service/${n.name}`))}async deactivate(e){(await this.intentions).destroy()}},u=h(a.prototype,"data",[r],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),s=h(a.prototype,"repo",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),c=h(a.prototype,"feedback",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),h(a.prototype,"createIntention",[l.action],Object.getOwnPropertyDescriptor(a.prototype,"createIntention"),a.prototype),a) -e.default=b})),define("consul-ui/routing/route",["exports","@ember/routing/route","@ember/object","@ember/service","consul-ui/utils/path/resolve","consul-ui/router"],(function(e,t,n,l,r,i){var o,a,u,s,c,d,p,f,m,h,b +e.default=b})),define("consul-ui/routes/unavailable",["exports","consul-ui/routing/route","@ember/service"],(function(e,t,n){var l,r,i,o,a +function u(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function s(e,t,n,l,r){var i={} +return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +let c=(l=(0,n.inject)("env"),r=(0,n.inject)(),i=class extends t.default{constructor(){super(...arguments),u(this,"env",o,this),u(this,"router",a,this)}beforeModel(){this.env.var("CONSUL_V2_CATALOG_ENABLED")||this.router.replaceWith("index")}},o=s(i.prototype,"env",[l],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),a=s(i.prototype,"router",[r],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),i) +e.default=c})),define("consul-ui/routing/route",["exports","@ember/routing/route","@ember/object","@ember/service","consul-ui/utils/path/resolve","consul-ui/router"],(function(e,t,n,l,r,i){var o,a,u,s,c,d,p,f,m,h,b function y(e,t){var n=Object.keys(e) if(Object.getOwnPropertySymbols){var l=Object.getOwnPropertySymbols(e) t&&(l=l.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,l)}return n}function v(e){for(var t=1;te.ID,Name:e=>e.Name}})),define("consul-ui/search/predicates/auth-method",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default={ID:e=>e.ID,Name:e=>e.Name}})) +define("consul-ui/search/predicates/auth-method",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 e.default={Name:e=>e.Name,DisplayName:e=>e.DisplayName}})),define("consul-ui/search/predicates/health-check",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var t={Name:e=>e.Name,Node:e=>e.Node,Service:e=>e.ServiceName,CheckID:e=>e.CheckID||"",ID:e=>e.Service.ID||"",Notes:e=>e.Notes,Output:e=>e.Output,ServiceTags:e=>{return t=e.ServiceTags,Array.isArray(t)?t:t.toArray() var t}} e.default=t})),define("consul-ui/search/predicates/intention",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const t="All Services (*)" var n={SourceName:e=>[e.SourceName,"*"===e.SourceName?t:void 0].filter(Boolean),DestinationName:e=>[e.DestinationName,"*"===e.DestinationName?t:void 0].filter(Boolean)} -e.default=n})) -define("consul-ui/search/predicates/kv",["exports","consul-ui/utils/right-trim"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=n})),define("consul-ui/search/predicates/kv",["exports","consul-ui/utils/right-trim"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n={Key:e=>(0,t.default)(e.Key.toLowerCase()).split("/").filter((e=>Boolean(e))).pop()} e.default=n})),define("consul-ui/search/predicates/node",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var t={Node:e=>e.Node,Address:e=>e.Address,PeerName:e=>e.PeerName,Meta:e=>Object.entries(e.Meta||{}).reduce(((e,t)=>e.concat(t)),[])} @@ -2956,7 +2970,8 @@ if(void 0!==n){var l=n.call(e,t||"default") if("object"!=typeof l)return l throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string") return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -class r extends t.default{constructor(){super(...arguments),l(this,"primaryKey",n.PRIMARY_KEY),l(this,"slugKey",n.SLUG_KEY)}}e.default=r})),define("consul-ui/serializers/proxy",["exports","consul-ui/serializers/application","consul-ui/models/proxy"],(function(e,t,n){function l(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e +class r extends t.default{constructor(){super(...arguments),l(this,"primaryKey",n.PRIMARY_KEY),l(this,"slugKey",n.SLUG_KEY)}}e.default=r})) +define("consul-ui/serializers/proxy",["exports","consul-ui/serializers/application","consul-ui/models/proxy"],(function(e,t,n){function l(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e var n=e[Symbol.toPrimitive] if(void 0!==n){var l=n.call(e,t||"default") if("object"!=typeof l)return l @@ -2984,8 +2999,7 @@ break case"critical":e.ChecksCritical.push(t)}return e}),{ChecksPassing:[],ChecksWarning:[],ChecksCritical:[]}),o=r(r({},i),{},{Service:t,Checks:l,Node:{Datacenter:e.Datacenter,Namespace:e.Namespace,Partition:e.Partition,ID:e.ID,Node:e.Node,Address:e.Address,TaggedAddresses:e.TaggedAddresses,Meta:e.Meta}}) return o.uid=this.extractUid(o),o}respondForQuery(e,t){return super.respondForQuery((n=>e(((e,l)=>{if(0===l.length){const e=new Error throw e.errors=[{status:"404",title:"Not found"}],e}return l.forEach((e=>{e.Datacenter=t.dc,e.Namespace=t.ns||"default",e.Partition=t.partition||"default",e.uid=this.extractUid(e)})),n(e,l)}))),t)}respondForQueryRecord(e,t){return super.respondForQueryRecord((n=>e(((e,l)=>{if(l.forEach((e=>{e.Datacenter=t.dc,e.Namespace=t.ns||"default",e.Partition=t.partition||"default",e.uid=this.extractUid(e)})),void 0===(l=l.find((function(e){return e.Node.Node===t.node&&e.Service.ID===t.serviceId})))){const e=new Error -throw e.errors=[{status:"404",title:"Not found"}],e}return l.Namespace=l.Service.Namespace,l.Partition=l.Service.Partition,n(e,l)}))),t)}}e.default=o})) -define("consul-ui/serializers/service",["exports","consul-ui/serializers/application","consul-ui/models/service","@ember/object","consul-ui/utils/http/consul"],(function(e,t,n,l,r){function i(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e +throw e.errors=[{status:"404",title:"Not found"}],e}return l.Namespace=l.Service.Namespace,l.Partition=l.Service.Partition,n(e,l)}))),t)}}e.default=o})),define("consul-ui/serializers/service",["exports","consul-ui/serializers/application","consul-ui/models/service","@ember/object","consul-ui/utils/http/consul"],(function(e,t,n,l,r){function i(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e var n=e[Symbol.toPrimitive] if(void 0!==n){var l=n.call(e,t||"default") if("object"!=typeof l)return l @@ -3161,7 +3175,8 @@ let o=(l=(0,t.inject)("settings"),r=class extends t.default{constructor(){var e, super(...arguments),e=this,t="repo",l=this,(n=i)&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}source(e,t){const l=e.split(":").pop() return new n.StorageEventSource((e=>this.repo.findBySlug(l)),{key:e,uri:t.uri})}},a=r.prototype,u="repo",s=[l],c={configurable:!0,enumerable:!0,writable:!0,initializer:null},p={},Object.keys(c).forEach((function(e){p[e]=c[e]})),p.enumerable=!!p.enumerable,p.configurable=!!p.configurable,("value"in p||p.initializer)&&(p.writable=!0),p=s.slice().reverse().reduce((function(e,t){return t(a,u,e)||e}),p),d&&void 0!==p.initializer&&(p.value=p.initializer?p.initializer.call(d):void 0,p.initializer=void 0),void 0===p.initializer&&(Object.defineProperty(a,u,p),p=null),i=p,r) var a,u,s,c,d,p -e.default=o})),define("consul-ui/services/data-source/service",["exports","@ember/service","@ember/debug","consul-ui/utils/dom/event-source","@ember/runloop","mnemonist/multi-map"],(function(e,t,n,l,r,i){var o,a,u,s,c,d,p,f,m +e.default=o})) +define("consul-ui/services/data-source/service",["exports","@ember/service","@ember/debug","consul-ui/utils/dom/event-source","@ember/runloop","mnemonist/multi-map"],(function(e,t,n,l,r,i){var o,a,u,s,c,d,p,f,m function h(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function b(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 let y=null,v=null,g=null @@ -3202,8 +3217,7 @@ let l=new IntersectionObserver(((e,t)=>{e.map((e=>{const t=y.get(e.target) "function"==typeof t&&t(e.isIntersecting)}))}),{rootMargin:"0px",threshold:n}) return l.observe(e),()=>{l.unobserve(e),y&&y.delete(e),l.disconnect(),l=null}}},O=p.prototype,P="doc",w=[d],x={configurable:!0,enumerable:!0,writable:!0,initializer:null},_={},Object.keys(x).forEach((function(e){_[e]=x[e]})),_.enumerable=!!_.enumerable,_.configurable=!!_.configurable,("value"in _||_.initializer)&&(_.writable=!0),_=w.slice().reverse().reduce((function(e,t){return t(O,P,e)||e}),_),j&&void 0!==_.initializer&&(_.value=_.initializer?_.initializer.call(j):void 0,_.initializer=void 0),void 0===_.initializer&&(Object.defineProperty(O,P,_),_=null),f=_,p) var O,P,w,x,j,_ -e.default=g})) -define("consul-ui/services/encoder",["exports","@ember/service","@ember/object","@ember/debug","consul-ui/utils/atob","consul-ui/utils/btoa"],(function(e,t,n,l,r,i){function o(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e +e.default=g})),define("consul-ui/services/encoder",["exports","@ember/service","@ember/object","@ember/debug","consul-ui/utils/atob","consul-ui/utils/btoa"],(function(e,t,n,l,r,i){function o(e,t,n){return(t=function(e){var t=function(e,t){if("object"!=typeof e||null===e)return e var n=e[Symbol.toPrimitive] if(void 0!==n){var l=n.call(e,t||"default") if("object"!=typeof l)return l @@ -3291,7 +3305,7 @@ throw e.errors=[{status:"403"}],e}}const l=await e(n.resources) return(0,r.get)(l,"Resources")&&(0,r.set)(l,"Resources",n.resources),l}shouldReconcile(e,t){if((0,r.get)(e,"Datacenter")!==t.dc)return!1 if(this.env.var("CONSUL_NSPACES_ENABLED")){const n=(0,r.get)(e,"Namespace") if(void 0!==n&&n!==t.ns)return!1}if(this.env.var("CONSUL_PARTITIONS_ENABLED")){const n=(0,r.get)(e,"Partition") -if(void 0!==n&&n!==t.partition)return!1}return!0}reconcile(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{} +if(void 0!==n&&n!==t.partition)return!1}return!this.env.var("CONSUL_PEERINGS_ENABLED")||"service"!==this.getModelName()}reconcile(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{} void 0!==e.date&&this.store.peekAll(this.getModelName()).forEach((n=>{const l=(0,r.get)(n,"SyncTime") !n.isDeleted&&void 0!==l&&l!=e.date&&this.shouldReconcile(n,t)&&this.store.unloadRecord(n)}))}peekOne(e){return this.store.peekRecord(this.getModelName(),e)}peekAll(){return this.store.peekAll(this.getModelName())}cached(e){const t=Object.entries(e) return this.store.peekAll(this.getModelName()).filter((e=>t.every((t=>{let[n,l]=t @@ -3439,7 +3453,8 @@ t.fetch=(e,t)=>this.client.fetchWithToken(`/v1/internal/ui/metrics-proxy${e}`,t) try{this.provider=window.consul.getMetricsProvider(n,t)}catch(l){this.error=new Error(`metrics provider not initialized: ${l}`),console.error(this.error)}}findServiceSummary(e){if(this.error)return Promise.reject(this.error) const t=[this.provider.serviceRecentSummarySeries(e.slug,e.dc,e.ns,e.protocol,{}),this.provider.serviceRecentSummaryStats(e.slug,e.dc,e.ns,e.protocol,{})] return Promise.all(t).then((e=>({meta:{interval:this.env.var("CONSUL_METRICS_POLL_INTERVAL")||1e4},series:e[0],stats:e[1].stats})))}findUpstreamSummary(e){return this.error?Promise.reject(this.error):this.provider.upstreamRecentSummaryStats(e.slug,e.dc,e.ns,{}).then((e=>(e.meta={interval:this.env.var("CONSUL_METRICS_POLL_INTERVAL")||1e4},e)))}findDownstreamSummary(e){return this.error?Promise.reject(this.error):this.provider.downstreamRecentSummaryStats(e.slug,e.dc,e.ns,{}).then((e=>(e.meta={interval:this.env.var("CONSUL_METRICS_POLL_INTERVAL")||1e4},e)))}},d=b(c.prototype,"config",[r],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),p=b(c.prototype,"env",[i],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),f=b(c.prototype,"client",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),b(c.prototype,"findServiceSummary",[a],Object.getOwnPropertyDescriptor(c.prototype,"findServiceSummary"),c.prototype),b(c.prototype,"findUpstreamSummary",[u],Object.getOwnPropertyDescriptor(c.prototype,"findUpstreamSummary"),c.prototype),b(c.prototype,"findDownstreamSummary",[s],Object.getOwnPropertyDescriptor(c.prototype,"findDownstreamSummary"),c.prototype),c) -e.default=y})),define("consul-ui/services/repository/node",["exports","consul-ui/services/repository","consul-ui/decorators/data-source"],(function(e,t,n){var l,r,i,o +e.default=y})) +define("consul-ui/services/repository/node",["exports","consul-ui/services/repository","consul-ui/decorators/data-source"],(function(e,t,n){var l,r,i,o function a(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 let u=(l=(0,n.default)("/:partition/:ns/:dc/nodes"),r=(0,n.default)("/:partition/:ns/:dc/node/:id/:peer"),i=(0,n.default)("/:partition/:ns/:dc/leader"),o=class extends t.default{getModelName(){return"node"}async findAllByDatacenter(){return super.findAllByDatacenter(...arguments)}async findBySlug(){return super.findBySlug(...arguments)}findLeader(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{} @@ -3465,8 +3480,7 @@ return this.store.logout(this.getModelName(),i)}close(){this.manager.close(b)}fi if(!0===e.message.startsWith("remote was closed"))t=new Error("Remote was closed"),t.statusCode=499 else t=new Error(e.message),t.statusCode=500 this.store.adapterFor(this.getModelName()).error(t)}))}},p=h(d.prototype,"manager",[o],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),f=h(d.prototype,"settings",[a],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),h(d.prototype,"findAllByDatacenter",[u],Object.getOwnPropertyDescriptor(d.prototype,"findAllByDatacenter"),d.prototype),h(d.prototype,"findBySlug",[s],Object.getOwnPropertyDescriptor(d.prototype,"findBySlug"),d.prototype),h(d.prototype,"authorize",[c],Object.getOwnPropertyDescriptor(d.prototype,"authorize"),d.prototype),d) -e.default=y})) -define("consul-ui/services/repository/partition",["exports","@ember/service","@ember/debug","consul-ui/services/repository","consul-ui/models/partition","consul-ui/decorators/data-source","consul-ui/utils/form/builder"],(function(e,t,n,l,r,i,o){var a,u,s,c,d,p,f,m,h +e.default=y})),define("consul-ui/services/repository/partition",["exports","@ember/service","@ember/debug","consul-ui/services/repository","consul-ui/models/partition","consul-ui/decorators/data-source","consul-ui/utils/form/builder"],(function(e,t,n,l,r,i,o){var a,u,s,c,d,p,f,m,h function b(e,t,n,l){n&&Object.defineProperty(e,t,{enumerable:n.enumerable,configurable:n.configurable,writable:n.writable,value:n.initializer?n.initializer.call(l):void 0})}function y(e,t,n,l,r){var i={} return Object.keys(l).forEach((function(e){i[e]=l[e]})),i.enumerable=!!i.enumerable,i.configurable=!!i.configurable,("value"in i||i.initializer)&&(i.writable=!0),i=n.slice().reverse().reduce((function(n,l){return l(e,t,n)||n}),i),r&&void 0!==i.initializer&&(i.value=i.initializer?i.initializer.call(r):void 0,i.initializer=void 0),void 0===i.initializer&&(Object.defineProperty(e,t,i),i=null),i}Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 let v=(a=(0,t.inject)("settings"),u=(0,t.inject)("form"),s=(0,t.inject)("repository/permission"),c=(0,i.default)("/:partition/:ns/:dc/partitions"),d=(0,i.default)("/:partition/:ns/:dc/partition/:id"),p=class extends l.default{constructor(){super(...arguments),b(this,"settings",f,this),b(this,"form",m,this),b(this,"permissions",h,this)}getModelName(){return"partition"}getPrimaryKey(){return r.PRIMARY_KEY}getSlugKey(){return r.SLUG_KEY}async findAll(){return this.permissions.can("use partitions")?super.findAll(...arguments).catch((()=>[])):[]}async findBySlug(e){let t @@ -3708,7 +3722,8 @@ break case t.startsWith("_"):e.partition=t.substr(1) break case void 0===e.dc:e.dc=t}return e}),{})}async get(){return this.env.var("CONSUL_UI_CONFIG")}getSync(){return this.env.var("CONSUL_UI_CONFIG")}},s=c(u.prototype,"env",[r],{configurable:!0,enumerable:!0,writable:!0,initializer:null}),c(u.prototype,"findByPath",[i],Object.getOwnPropertyDescriptor(u.prototype,"findByPath"),u.prototype),c(u.prototype,"parsePath",[o],Object.getOwnPropertyDescriptor(u.prototype,"parsePath"),u.prototype),c(u.prototype,"get",[a],Object.getOwnPropertyDescriptor(u.prototype,"get"),u.prototype),u) -e.default=d})),define("consul-ui/sort/comparators/auth-method",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=d})) +define("consul-ui/sort/comparators/auth-method",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 e.default=e=>{let{properties:t}=e return function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"MethodName:asc" return t(["MethodName","TokenTTL"])(e)}}})),define("consul-ui/sort/comparators/health-check",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 @@ -3723,8 +3738,7 @@ case"critical":return"critical"===a?0:-1 case"warning":switch(a){case"passing":return-1 case"critical":return 1 default:return 0}}return 0}:t(["Name","Kind"])(e)}}})),define("consul-ui/sort/comparators/intention",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -e.default=()=>e=>[e]})) -define("consul-ui/sort/comparators/kv",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=()=>e=>[e]})),define("consul-ui/sort/comparators/kv",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 e.default=e=>{let{properties:t}=e return e=>t(["Key","Kind"])(e)}})),define("consul-ui/sort/comparators/node",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 e.default=e=>{let{properties:t}=e @@ -3892,7 +3906,7 @@ e.default=e=>e` } } `})),define("consul-ui/templates/application",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -var n=(0,t.createTemplateFactory)({id:"p3hjSYtO",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n\\n"],[1," "],[8,[30,1,["Announcer"]],null,[["@title"],["Consul"]],null],[1,"\\n"],[41,[28,[37,3],["use acls"],null],[[[1," "],[1,[28,[35,4],null,[["class"],["has-acls"]]]],[1,"\\n"]],[]],null],[41,[28,[37,3],["use nspaces"],null],[[[1," "],[1,[28,[35,4],null,[["class"],["has-nspaces"]]]],[1,"\\n"]],[]],null],[41,[28,[37,3],["use partitions"],null],[[[1," "],[1,[28,[35,4],null,[["class"],["has-partitions"]]]],[1,"\\n"]],[]],null],[1,"\\n"],[1," "],[8,[39,5],null,[["@src","@onchange"],[[28,[37,6],["settings://consul:client"],null],[28,[37,7],["onClientChanged"],null]]],null],[1,"\\n\\n"],[1," "],[8,[39,5],null,[["@src"],[[28,[37,6],["settings://consul:theme"],null]]],[["default"],[[[[1,"\\n"],[42,[28,[37,9],[[30,2,["data"]]],null],null,[[[41,[28,[37,10],[[30,3],[28,[37,11],[[30,4],[28,[37,12],["color-scheme","contrast"],null]],null]],null],[[[1," "],[1,[28,[35,4],null,[["class"],[[28,[37,13],["prefers-",[30,4],"-",[30,3]],null]]]]],[1,"\\n"]],[]],null]],[3,4]],null],[1," "]],[2]]]]],[1,"\\n\\n"],[41,[28,[37,3],["use acls"],null],[[[1," "],[8,[39,5],null,[["@src","@onchange"],[[28,[37,6],["settings://consul:token"],null],[28,[37,14],[[30,0],[28,[37,15],[[33,16]],null]],[["value"],["data"]]]]],null],[1,"\\n"]],[]],null],[1,"\\n"],[41,[28,[37,17],[[30,1,["currentName"]],"oauth-provider-debug"],null],[[[1,"\\n"],[41,[28,[37,18],[[30,1,["currentName"]],"index"],null],[[[1,"\\n"],[1," "],[1,[28,[35,19],[[28,[37,7],["replaceWith","dc.services.index",[28,[37,20],null,[["dc"],[[28,[37,21],["CONSUL_DATACENTER_LOCAL"],null]]]]],null]],null]],[1,"\\n"]],[]],[[[41,[28,[37,18],[[30,1,["currentName"]],"notfound"],null],[[[1," "],[8,[39,5],null,[["@src","@onchange"],[[28,[37,6],["/*/*/*/notfound/${path}",[28,[37,20],null,[["path"],[[30,1,["params","notfound"]]]]]],null],[28,[37,14],[[30,0],[28,[37,15],[[33,22]],null]],[["value"],["data"]]]]],null],[1,"\\n"]],[]],null],[1,"\\n"],[44,[[52,[28,[37,3],["use partitions"],null],[28,[37,24],[[30,1,["params","partition"]],[33,22,["partition"]],[33,16,["Partition"]],""],null],""],[52,[28,[37,3],["use nspaces"],null],[28,[37,24],[[30,1,["params","nspace"]],[33,22,["nspace"]],[33,16,["Namespace"]],""],null],""]],[[[1,"\\n"],[1," "],[8,[39,5],null,[["@src"],[[28,[37,6],["/*/*/*/datacenters"],null]]],[["default"],[[[[1,"\\n"],[44,[[28,[37,24],[[52,[33,25,["dc"]],[28,[37,26],[0,[28,[37,27],["dc",[28,[37,20],null,[["Name"],[[33,22,["dc"]]]]]],null]],null]],[28,[37,26],[0,[28,[37,27],["dc",[28,[37,20],null,[["Name"],[[30,1,["params","dc"]]]]]],null]],null],[28,[37,20],null,[["Name"],[[28,[37,21],["CONSUL_DATACENTER_LOCAL"],null]]]]],null],[30,7,["data"]]],[[[41,[28,[37,10],[[28,[37,28],[[30,8,["Name","length"]],0],null],[30,9]],null],[[[1,"\\n"],[1," "],[8,[39,5],null,[["@src"],[[28,[37,6],["/${partition}/*/${dc}/datacenter-cache/${name}",[28,[37,20],null,[["dc","partition","name"],[[30,8,["Name"]],[30,5],[30,8,["Name"]]]]]],null]]],[["default"],[[[[1,"\\n"],[41,[30,10,["data"]],[[[1," "],[8,[39,29],[[24,1,"wrapper"]],[["@dcs","@dc","@partition","@nspace","@user","@onchange"],[[30,9],[30,10,["data"]],[30,5],[30,6],[28,[37,20],null,[["token"],[[33,16]]]],[28,[37,14],[[30,0],"reauthorize"],null]]],[["default"],[[[[1,"\\n\\n"],[41,[33,30],[[[1," "],[8,[39,31],null,[["@error","@login"],[[99,30,["@error"]],[30,11,["login","open"]]]],null],[1,"\\n"]],[]],[[[1," "],[8,[39,32],null,[["@name","@model"],["application",[28,[37,20],null,[["app","user","dc","dcs"],[[30,11],[28,[37,20],null,[["token"],[[33,16]]]],[30,10,["data"]],[30,9]]]]]],[["default"],[[[[1,"\\n "],[46,[28,[37,34],null,null],null,null,null],[1,"\\n "]],[12]]]]],[1,"\\n\\n"],[1," "],[8,[39,35],[[24,0,"view-loader"]],null,null],[1,"\\n"]],[]]],[1,"\\n "]],[11]]]]],[1,"\\n"]],[]],null],[1," "]],[10]]]]],[1,"\\n"]],[]],null]],[8,9]]],[1," "]],[7]]]]],[1,"\\n"]],[5,6]]]],[]]]],[]],[[[1," "],[8,[39,32],null,[["@name","@model"],["application",[28,[37,20],null,[["user"],[[28,[37,20],null,[["token"],[[33,16]]]]]]]]],[["default"],[[[[1,"\\n "],[46,[28,[37,34],null,null],null,null,null],[1,"\\n "]],[13]]]]],[1,"\\n"]],[]]]],[1]]]]],[1,"\\n"]],["route","source","value","key","partition","nspace","dcs","dc","dcs","dc","consul","o","o"],false,["route","routeName","if","can","document-attrs","data-source","uri","route-action","each","-each-in","and","includes","array","concat","action","mut","token","not-eq","eq","did-insert","hash","env","notfound","let","or","nofound","object-at","cached-model","gt","hashicorp-consul","error","app-error","outlet","component","-outlet","consul/loader"]]',moduleName:"consul-ui/templates/application.hbs",isStrictMode:!1}) +var n=(0,t.createTemplateFactory)({id:"Srw5qTey",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n\\n"],[1," "],[8,[30,1,["Announcer"]],null,[["@title"],["Consul"]],null],[1,"\\n\\n"],[41,[51,[28,[37,3],["CONSUL_V2_CATALOG_ENABLED"],null]],[[[41,[28,[37,5],["use acls"],null],[[[1," "],[1,[28,[35,6],null,[["class"],["has-acls"]]]],[1,"\\n"]],[]],null],[41,[28,[37,5],["use nspaces"],null],[[[1," "],[1,[28,[35,6],null,[["class"],["has-nspaces"]]]],[1,"\\n"]],[]],null],[41,[28,[37,5],["use partitions"],null],[[[1," "],[1,[28,[35,6],null,[["class"],["has-partitions"]]]],[1,"\\n"]],[]],null],[1,"\\n"],[1," "],[8,[39,7],null,[["@src","@onchange"],[[28,[37,8],["settings://consul:client"],null],[28,[37,9],["onClientChanged"],null]]],null],[1,"\\n\\n"],[1," "],[8,[39,7],null,[["@src"],[[28,[37,8],["settings://consul:theme"],null]]],[["default"],[[[[1,"\\n"],[42,[28,[37,11],[[30,2,["data"]]],null],null,[[[41,[28,[37,12],[[30,3],[28,[37,13],[[30,4],[28,[37,14],["color-scheme","contrast"],null]],null]],null],[[[1," "],[1,[28,[35,6],null,[["class"],[[28,[37,15],["prefers-",[30,4],"-",[30,3]],null]]]]],[1,"\\n"]],[]],null]],[3,4]],null],[1," "]],[2]]]]],[1,"\\n\\n"],[41,[28,[37,5],["use acls"],null],[[[1," "],[8,[39,7],null,[["@src","@onchange"],[[28,[37,8],["settings://consul:token"],null],[28,[37,16],[[30,0],[28,[37,17],[[33,18]],null]],[["value"],["data"]]]]],null],[1,"\\n"]],[]],null]],[]],null],[1,"\\n"],[41,[28,[37,12],[[28,[37,19],[[28,[37,3],["CONSUL_V2_CATALOG_ENABLED"],null]],null],[28,[37,20],[[30,1,["currentName"]],"oauth-provider-debug"],null]],null],[[[1,"\\n"],[41,[28,[37,21],[[30,1,["currentName"]],"index"],null],[[[1,"\\n"],[1," "],[1,[28,[35,22],[[28,[37,9],["replaceWith","dc.services.index",[28,[37,23],null,[["dc"],[[28,[37,3],["CONSUL_DATACENTER_LOCAL"],null]]]]],null]],null]],[1,"\\n"]],[]],[[[41,[28,[37,21],[[30,1,["currentName"]],"notfound"],null],[[[1," "],[8,[39,7],null,[["@src","@onchange"],[[28,[37,8],["/*/*/*/notfound/${path}",[28,[37,23],null,[["path"],[[30,1,["params","notfound"]]]]]],null],[28,[37,16],[[30,0],[28,[37,17],[[33,24]],null]],[["value"],["data"]]]]],null],[1,"\\n"]],[]],null],[1,"\\n"],[44,[[52,[28,[37,5],["use partitions"],null],[28,[37,26],[[30,1,["params","partition"]],[33,24,["partition"]],[33,18,["Partition"]],""],null],""],[52,[28,[37,5],["use nspaces"],null],[28,[37,26],[[30,1,["params","nspace"]],[33,24,["nspace"]],[33,18,["Namespace"]],""],null],""]],[[[1,"\\n"],[1," "],[8,[39,7],null,[["@src"],[[28,[37,8],["/*/*/*/datacenters"],null]]],[["default"],[[[[1,"\\n"],[44,[[28,[37,26],[[52,[33,27,["dc"]],[28,[37,28],[0,[28,[37,29],["dc",[28,[37,23],null,[["Name"],[[33,24,["dc"]]]]]],null]],null]],[28,[37,28],[0,[28,[37,29],["dc",[28,[37,23],null,[["Name"],[[30,1,["params","dc"]]]]]],null]],null],[28,[37,23],null,[["Name"],[[28,[37,3],["CONSUL_DATACENTER_LOCAL"],null]]]]],null],[30,7,["data"]]],[[[41,[28,[37,12],[[28,[37,30],[[30,8,["Name","length"]],0],null],[30,9]],null],[[[1,"\\n"],[1," "],[8,[39,7],null,[["@src"],[[28,[37,8],["/${partition}/*/${dc}/datacenter-cache/${name}",[28,[37,23],null,[["dc","partition","name"],[[30,8,["Name"]],[30,5],[30,8,["Name"]]]]]],null]]],[["default"],[[[[1,"\\n"],[41,[30,10,["data"]],[[[1," "],[8,[39,31],[[24,1,"wrapper"]],[["@dcs","@dc","@partition","@nspace","@user","@onchange"],[[30,9],[30,10,["data"]],[30,5],[30,6],[28,[37,23],null,[["token"],[[33,18]]]],[28,[37,16],[[30,0],"reauthorize"],null]]],[["default"],[[[[1,"\\n\\n"],[41,[33,32],[[[1," "],[8,[39,33],null,[["@error","@login"],[[99,32,["@error"]],[30,11,["login","open"]]]],null],[1,"\\n"]],[]],[[[1," "],[8,[39,34],null,[["@name","@model"],["application",[28,[37,23],null,[["app","user","dc","dcs"],[[30,11],[28,[37,23],null,[["token"],[[33,18]]]],[30,10,["data"]],[30,9]]]]]],[["default"],[[[[1,"\\n "],[46,[28,[37,36],null,null],null,null,null],[1,"\\n "]],[12]]]]],[1,"\\n\\n"],[1," "],[8,[39,37],[[24,0,"view-loader"]],null,null],[1,"\\n"]],[]]],[1,"\\n "]],[11]]]]],[1,"\\n"]],[]],null],[1," "]],[10]]]]],[1,"\\n"]],[]],null]],[8,9]]],[1," "]],[7]]]]],[1,"\\n"]],[5,6]]]],[]]]],[]],[[[1," "],[8,[39,34],null,[["@name","@model"],["application",[28,[37,23],null,[["user"],[[28,[37,23],null,[["token"],[[33,18]]]]]]]]],[["default"],[[[[1,"\\n "],[46,[28,[37,36],null,null],null,null,null],[1,"\\n "]],[13]]]]],[1,"\\n"]],[]]]],[1]]]]]],["route","source","value","key","partition","nspace","dcs","dc","dcs","dc","consul","o","o"],false,["route","routeName","unless","env","if","can","document-attrs","data-source","uri","route-action","each","-each-in","and","includes","array","concat","action","mut","token","not","not-eq","eq","did-insert","hash","notfound","let","or","nofound","object-at","cached-model","gt","hashicorp-consul","error","app-error","outlet","component","-outlet","consul/loader"]]',moduleName:"consul-ui/templates/application.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/components/basic-dropdown-content",["exports","ember-basic-dropdown/templates/components/basic-dropdown-content"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/templates/components/basic-dropdown-optional-tag",["exports","ember-basic-dropdown/templates/components/basic-dropdown-optional-tag"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/templates/components/basic-dropdown-trigger",["exports","ember-basic-dropdown/templates/components/basic-dropdown-trigger"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/templates/components/basic-dropdown",["exports","ember-basic-dropdown/templates/components/basic-dropdown"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.default}})})),define("consul-ui/templates/dc",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"ex21k2WB",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@name","@model"],[[99,1,["@name"]],[30,1,["model"]]]],[["default"],[[[[1,"\\n "],[46,[28,[37,4],null,null],null,null,null],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]],[1,"\\n"]],["route","o"],false,["route","routeName","outlet","component","-outlet"]]',moduleName:"consul-ui/templates/dc.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/acls",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 @@ -3907,17 +3921,17 @@ e.default=n})),define("consul-ui/templates/dc/acls/auth-methods/show/binding-rul var n=(0,t.createTemplateFactory)({id:"lp1F+P1r",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/binding-rules/for-auth-method/${name}",[28,[37,4],null,[["partition","nspace","dc","name"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]],[30,1,["params","id"]]]]]],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,5],null,[["@name"],["error"]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n\\n "],[8,[39,5],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n"],[44,[[30,2,["data"]]],[[[1,"\\n "],[10,0],[14,0,"tab-section"],[12],[1,"\\n"],[41,[28,[37,9],[[30,3,["length"]],0],null],[[[1," "],[10,2],[12],[1,"\\n Binding rules allow an operator to express a systematic way of automatically linking roles and service identities to newly created tokens without operator intervention.\\n "],[13],[1,"\\n "],[10,2],[12],[1,"\\n Successful authentication with an auth method returns a set of trusted identity attributes corresponding to the authenticated identity. Those attributes are matched against all configured binding rules for that auth method to determine what privileges to grant the Consul ACL token it will ultimately create.\\n "],[13],[1,"\\n "],[10,"hr"],[12],[13],[1,"\\n"],[42,[28,[37,11],[[28,[37,11],[[30,3]],null]],null],null,[[[1," "],[8,[39,12],null,[["@item"],[[30,4]]],null],[1,"\\n "],[10,"hr"],[12],[13],[1,"\\n"]],[4]],null]],[]],[[[1," "],[8,[39,13],null,null,[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"\\n "],[1,[28,[35,14],["routes.dc.acls.auth-methods.show.binding-rules.index.empty.header"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[1,[28,[35,14],["routes.dc.acls.auth-methods.show.binding-rules.index.empty.body"],[["htmlSafe"],[true]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[10,"li"],[14,0,"docs-link"],[12],[1,"\\n "],[10,3],[15,6,[29,[[28,[37,15],["CONSUL_DOCS_API_URL"],null],"/acl/binding-rules"]]],[14,"rel","noopener noreferrer"],[14,"target","_blank"],[12],[1,"Read the documentation"],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]]],[1," "],[13],[1,"\\n"]],[3]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]],[1,"\\n"]],["route","loader","items","item"],false,["route","routeName","data-loader","uri","hash","block-slot","app-error","let","if","gt","each","-track-array","consul/auth-method/binding-list","empty-state","t","env"]]',moduleName:"consul-ui/templates/dc/acls/auth-methods/show/binding-rules.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/acls/auth-methods/show/nspace-rules",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"B9VyTw0K",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n"],[44,[[30,1,["model","item"]]],[[[1," "],[10,0],[14,0,"tab-section"],[12],[1,"\\n"],[41,[28,[37,4],[[30,2,["NamespaceRules","length"]],0],null],[[[1," "],[10,2],[12],[1,"\\n A set of rules that can control which namespace tokens created via this auth method will be created within. Unlike binding rules, the first matching namespace rule wins.\\n "],[13],[1,"\\n "],[8,[39,5],null,[["@items"],[[30,2,["NamespaceRules"]]]],null],[1,"\\n"]],[]],[[[1," "],[8,[39,6],null,null,[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"\\n "],[1,[28,[35,8],[[28,[37,9],[[30,1,["t"]],"empty.header"],null]],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[1,[28,[35,8],[[28,[37,9],[[30,1,["t"]],"empty.body",[28,[37,10],null,[["htmlSafe"],[true]]]],null]],null]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[10,"li"],[14,0,"docs-link"],[12],[1,"\\n "],[10,3],[15,6,[29,[[28,[37,11],["CONSUL_DOCS_API_URL"],null],"/acl/auth-methods#namespacerules"]]],[14,"rel","noopener noreferrer"],[14,"target","_blank"],[12],[1,"Read the documentation"],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]]],[1," "],[13],[1,"\\n"]],[2]]]],[1]]]]],[1,"\\n"]],["route","item"],false,["route","routeName","let","if","gt","consul/auth-method/nspace-list","empty-state","block-slot","compute","fn","hash","env"]]',moduleName:"consul-ui/templates/dc/acls/auth-methods/show/nspace-rules.hbs",isStrictMode:!1}) -e.default=n})),define("consul-ui/templates/dc/acls/index",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=n})) +define("consul-ui/templates/dc/acls/index",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"u49Giiy6",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[28,[37,3],["replaceWith","dc.acls.tokens"],null]],null]],[1,"\\n"]],[1]]]]],[1,"\\n"]],["route"],false,["route","routeName","did-insert","route-action"]]',moduleName:"consul-ui/templates/dc/acls/index.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/acls/policies/-form",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"HeHpiPG6",block:'[[[1,"\\n"],[10,"form"],[12],[1,"\\n "],[8,[39,0],null,[["@form","@partition","@nspace","@item"],[[99,1,["@form"]],[99,2,["@partition"]],[99,3,["@nspace"]],[99,4,["@item"]]]],[["default"],[[[[1,"\\n"],[1," "],[8,[39,5],null,[["@name"],["template"]],null],[1,"\\n "]],[]]]]],[1,"\\n"],[41,[28,[37,7],[[33,8]],null],[[[1," "],[8,[39,9],null,[["@src","@onchange"],[[28,[37,10],["/${partition}/${nspace}/${dc}/tokens/for-policy/${id}",[28,[37,11],null,[["partition","nspace","dc","id"],[[33,2],[33,3],[33,12],[28,[37,13],[[33,14],""],null]]]]],null],[28,[37,15],[[30,0],[28,[37,16],[[33,17]],null]],[["value"],["data"]]]]],null],[1,"\\n"],[41,[28,[37,18],[[33,17,["length"]],0],null],[[[1," "],[8,[39,19],null,[["@caption","@items"],["Applied to the following tokens:",[99,17,["@items"]]]],null],[1,"\\n"]],[]],null]],[]],null],[1," "],[10,0],[12],[1,"\\n "],[8,[39,20],null,null,[["default"],[[[[1,"\\n"],[41,[28,[37,21],[[33,8],[28,[37,22],["create tokens"],null]],null],[[[1," "],[8,[39,23],[[16,"disabled",[52,[28,[37,13],[[33,4,["isPristine"]],[33,4,["isInvalid"]],[28,[37,24],[[33,4,["Name"]],""],null]],null],"disabled"]],[24,4,"submit"],[4,[38,25],["click",[28,[37,26],["create",[33,4]],null]],null]],[["@text"],["Save"]],null],[1,"\\n"]],[]],[[[41,[28,[37,22],["write policy"],[["item"],[[33,4]]]],[[[1," "],[8,[39,23],[[16,"disabled",[52,[33,4,["isInvalid"]],"disabled"]],[24,4,"submit"],[4,[38,25],["click",[28,[37,26],["update",[33,4]],null]],null]],[["@text"],["Save"]],null],[1,"\\n"]],[]],null]],[]]],[1," "],[8,[39,23],[[24,4,"reset"],[4,[38,15],[[30,0],"cancel",[33,4]],null]],[["@text","@color"],["Cancel","secondary"]],null],[1,"\\n"],[41,[28,[37,21],[[28,[37,7],[[33,8]],null],[28,[37,22],["delete policy"],[["item"],[[33,4]]]]],null],[[[1," "],[8,[39,27],null,[["@message"],["Are you sure you want to delete this Policy?"]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["action"]],[["default"],[[[[1,"\\n "],[8,[39,23],[[4,[38,15],[[30,0],[30,1],"delete",[33,4]],null]],[["@text","@color"],["Delete","critical"]],null],[1,"\\n "]],[1]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["dialog"]],[["default"],[[[[1,"\\n"],[41,[28,[37,18],[[33,17,["length"]],0],null],[[[1," "],[8,[39,28],null,[["@onclose","@open","@aria"],[[28,[37,15],[[30,0],[30,3]],null],true,[28,[37,11],null,[["label"],["Policy in Use"]]]]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"Policy in Use"],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[10,2],[12],[1,"\\n This Policy is currently in use. If you choose to delete this Policy, it will be removed from the\\n following "],[10,"strong"],[12],[1,[33,17,["length"]]],[1," Tokens"],[13],[1,":\\n "],[13],[1,"\\n "],[8,[39,19],null,[["@items","@target"],[[99,17,["@items"]],"_blank"]],null],[1,"\\n "],[10,2],[12],[1,"\\n This action cannot be undone. "],[1,[30,4]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[8,[39,20],null,null,[["default"],[[[[1,"\\n "],[8,[39,23],[[4,[38,15],[[30,0],[30,2]],null]],[["@text","@color"],["Yes, Delete","critical"]],null],[1,"\\n "],[8,[39,23],[[4,[38,15],[[30,0],[30,5]],null]],[["@text","@color"],["Cancel","secondary"]],null],[1,"\\n "]],[]]]]],[1,"\\n "]],[5]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],[[[1," "],[8,[39,29],null,[["@message","@execute","@cancel"],[[30,4],[30,2],[30,3]]],null],[1,"\\n"]],[]]],[1," "]],[2,3,4]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],null],[1," "]],[]]]]],[1,"\\n "],[13],[1,"\\n"],[13],[1,"\\n"]],["confirm","execute","cancel","message","close"],false,["policy-form","form","partition","nspace","item","block-slot","if","not","create","data-source","uri","hash","dc","or","id","action","mut","items","gt","token-list","hds/button-set","and","can","hds/button","eq","on","route-action","confirmation-dialog","modal-dialog","delete-confirmation"]]',moduleName:"consul-ui/templates/dc/acls/policies/-form.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/acls/policies/edit",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"ZeBYu7Wq",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/policy/${id}",[28,[37,4],null,[["partition","nspace","dc","id"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]],[28,[37,5],[[30,1,["params","id"]],""],null]]]]],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,6],null,[["@name"],["error"]],[["default"],[[[[1,"\\n"],[41,[28,[37,8],[[30,2,["error","status"]],"401"],null],[[[1," "],[8,[39,9],null,null,null],[1,"\\n"]],[]],[[[1," "],[8,[39,10],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n"]],[]]],[1," "]],[]]]]],[1,"\\n\\n "],[8,[39,6],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n"],[44,[[30,1,["params","dc"]],[30,1,["params","partition"]],[30,1,["params","nspace"]],[28,[37,5],[[30,1,["params","id"]],""],null],[30,2,["data"]],[30,2,["data","isNew"]]],[[[1," "],[8,[39,12],null,[["@login"],[[30,1,["model","app","login","open"]]]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["breadcrumbs"]],[["default"],[[[[1,"\\n "],[10,"ol"],[12],[1,"\\n "],[10,"li"],[12],[10,3],[15,6,[28,[37,13],["dc.acls.policies"],null]],[12],[1,"All Policies"],[13],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h1"],[12],[1,"\\n"],[41,[30,8],[[[1," "],[8,[30,1,["Title"]],null,[["@title"],["New Policy"]],null],[1,"\\n"]],[]],[[[41,[28,[37,14],["write policy"],[["item"],[[30,7]]]],[[[1," "],[8,[30,1,["Title"]],null,[["@title"],["Edit Policy"]],null],[1,"\\n"]],[]],[[[1," "],[8,[30,1,["Title"]],null,[["@title"],["View Policy"]],null],[1,"\\n"]],[]]]],[]]],[1," "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["content"]],[["default"],[[[[1,"\\n"],[41,[28,[37,15],[[30,8]],null],[[[1," "],[10,0],[14,0,"definition-table"],[12],[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,"Policy ID"],[13],[1,"\\n "],[10,"dd"],[12],[1,"\\n "],[8,[39,16],null,[["@value","@name"],[[30,7,["ID"]],"Policy ID"]],null],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n"]],[]],null],[41,[28,[37,5],[[28,[37,8],[[28,[37,17],[[30,7]],null],"policy-management"],null],[28,[37,8],[[28,[37,17],[[30,7]],null],"read-only"],null]],null],[[[1," "],[8,[39,18],[[24,0,"mb-3 mt-2"]],[["@type","@icon"],["inline","star-fill"]],[["default"],[[[[1,"\\n "],[8,[30,9,["Title"]],null,null,[["default"],[[[[1,"Built-in policy"]],[]]]]],[1,"\\n "],[8,[30,9,["Description"]],null,null,[["default"],[[[[1,"This policy is built into Consul\'s ACL system. You can use this special policy by adding it to a token. This policy is not editable or removable, but can be ignored by not applying it to any tokens.\\n "]],[]]]]],[1,"\\n "],[8,[30,9,["Link::Standalone"]],null,[["@text","@href","@icon","@iconPosition"],["Learn more",[29,[[28,[37,19],["CONSUL_DOCS_URL"],null],"/guides/acl.html#builtin-policies"]],"docs-link","trailing"]],null],[1,"\\n "]],[9]]]]],[1,"\\n "],[10,0],[14,0,"definition-table"],[12],[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,"Name"],[13],[1,"\\n "],[10,"dd"],[12],[1,[30,7,["Name"]]],[13],[1,"\\n "],[10,"dt"],[12],[1,"Valid Datacenters"],[13],[1,"\\n "],[10,"dd"],[12],[1,[28,[35,20],[", ",[28,[37,21],[[30,7]],null]],null]],[13],[1,"\\n "],[10,"dt"],[12],[1,"Description"],[13],[1,"\\n "],[10,"dd"],[12],[1,[30,7,["Description"]]],[13],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "],[8,[39,22],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/tokens/for-policy/${id}",[28,[37,4],null,[["partition","nspace","dc","id"],[[30,4],[30,5],[30,3],[30,6]]]]],null]]],[["default"],[[[[1,"\\n"],[41,[28,[37,23],[[30,10,["data","length"]],0],null],[[[1," "],[8,[39,24],null,[["@caption","@items"],["Applied to the following tokens:",[30,10,["data"]]]],null],[1,"\\n"]],[]],null],[1," "]],[10]]]]],[1,"\\n"]],[]],[[[1," "],[19,"dc/acls/policies/form",[1,2,3,4,5,6,7,8]],[1,"\\n"]],[]]],[1," "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[3,4,5,6,7,8]]],[1," "]],[]]]]],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]]],["route","loader","dc","partition","nspace","id","item","create","A","loader"],true,["route","routeName","data-loader","uri","hash","or","block-slot","if","eq","consul/acl/disabled","app-error","let","app-view","href-to","can","not","copyable-code","policy/typeof","hds/alert","env","join","policy/datacenters","data-source","gt","token-list","partial"]]',moduleName:"consul-ui/templates/dc/acls/policies/edit.hbs",isStrictMode:!1}) -e.default=n})) -define("consul-ui/templates/dc/acls/policies/index",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=n})),define("consul-ui/templates/dc/acls/policies/index",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"fHN/RQtj",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/policies",[28,[37,4],null,[["partition","nspace","dc"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]]]]]],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,5],null,[["@name"],["error"]],[["default"],[[[[1,"\\n"],[41,[28,[37,7],[[30,2,["error","status"]],"401"],null],[[[1," "],[8,[39,8],null,null,null],[1,"\\n"]],[]],[[[1," "],[8,[39,9],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n"]],[]]],[1," "]],[]]]]],[1,"\\n\\n "],[8,[39,5],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n"],[44,[[28,[37,4],null,[["value","change"],[[28,[37,11],[[33,12],"Name:asc"],null],[28,[37,13],[[30,0],[28,[37,14],[[33,12]],null]],[["value"],["target.selected"]]]]]],[28,[37,4],null,[["kind","datacenter","searchproperty"],[[28,[37,4],null,[["value","change"],[[52,[33,15],[28,[37,16],[[33,15],","],null],[27]],[28,[37,13],[[30,0],[28,[37,14],[[33,15]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change"],[[52,[33,17],[28,[37,16],[[33,17],","],null],[27]],[28,[37,13],[[30,0],[28,[37,14],[[33,17]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change","default"],[[52,[28,[37,18],[[33,19],[27]],null],[28,[37,16],[[33,19],","],null],[33,20]],[28,[37,13],[[30,0],[28,[37,14],[[33,19]],null]],[["value"],["target.selectedItems"]]],[33,20]]]]]]],[30,2,["data"]]],[[[1," "],[8,[39,21],null,[["@login"],[[30,1,["model","app","login","open"]]]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h1"],[12],[1,"\\n "],[8,[30,1,["Title"]],null,[["@title"],["Policies"]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n"],[41,[28,[37,22],["create policies"],null],[[[1," "],[8,[39,23],null,[["@text","@route"],["Create","dc.acls.policies.create"]],null],[1,"\\n"]],[]],null],[1," "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["toolbar"]],[["default"],[[[[1,"\\n"],[41,[28,[37,24],[[30,5,["length"]],0],null],[[[1," "],[8,[39,25],null,[["@partition","@search","@onsearch","@sort","@filter"],[[30,1,["params","partition"]],[99,26,["@search"]],[28,[37,13],[[30,0],[28,[37,14],[[33,26]],null]],[["value"],["target.value"]]],[30,3],[30,4]]],null],[1,"\\n"]],[]],null],[1," "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["content"]],[["default"],[[[[1,"\\n "],[8,[39,27],null,[["@type","@sort","@filters","@search","@items"],["policy",[30,3,["value"]],[30,4],[99,26,["@search"]],[30,5]]],[["default"],[[[[1,"\\n "],[8,[30,6,["Collection"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,28],null,[["@items","@ondelete"],[[30,6,["items"]],[28,[37,29],["delete"],null]]],null],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,6,["Empty"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,30],null,[["@login"],[[30,1,["model","app","login","open"]]]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"\\n "],[1,[28,[35,31],["routes.dc.acls.policies.index.empty.header"],[["items"],[[30,5,["length"]]]]]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[1,[28,[35,31],["routes.dc.acls.policies.index.empty.body"],[["items","htmlSafe"],[[30,5,["length"]],true]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,32],null,[["@text","@href","@icon","@iconPosition","@size"],["Documentation on policies",[29,[[28,[37,33],["CONSUL_DOCS_URL"],null],"/commands/acl/policy"]],"docs-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,32],null,[["@text","@href","@icon","@iconPosition","@size"],["Take the tutorial",[29,[[28,[37,33],["CONSUL_LEARN_URL"],null],"/consul/security-networking/managing-acl-policies"]],"learn-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[6]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n\\n"]],[3,4,5]]],[1," "]],[]]]]],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]]],["route","loader","sort","filters","items","collection"],false,["route","routeName","data-loader","uri","hash","block-slot","if","eq","consul/acl/disabled","app-error","let","or","sortBy","action","mut","kind","split","datacenter","not-eq","searchproperty","searchProperties","app-view","can","hds/button","gt","consul/policy/search-bar","search","data-collection","consul/policy/list","route-action","empty-state","t","hds/link/standalone","env"]]',moduleName:"consul-ui/templates/dc/acls/policies/index.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/acls/roles/-form",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -var n=(0,t.createTemplateFactory)({id:"xSvLYGJe",block:'[[[1,"\\n"],[10,"form"],[12],[1,"\\n "],[8,[39,0],null,[["@form","@item","@dc","@nspace","@partition"],[[99,1,["@form"]],[99,2,["@item"]],[99,3,["@dc"]],[99,4,["@nspace"]],[99,5,["@partition"]]]],null],[1,"\\n"],[41,[28,[37,7],[[33,8]],null],[[[1," "],[8,[39,9],null,[["@src"],[[28,[37,10],["/${partition}/${nspace}/${dc}/tokens/for-role/${id}",[28,[37,11],null,[["partition","nspace","dc","id"],[[33,5],[33,4],[33,3],[28,[37,12],[[33,13],""],null]]]]],null]]],[["default"],[[[[1,"\\n"],[41,[28,[37,14],[[30,1,["data","length"]],0],null],[[[1," "],[10,"h2"],[12],[1,"Where is this role used?"],[13],[1,"\\n "],[10,2],[12],[1,"\\n We\'re only able to show information for the primary datacenter and the current datacenter. This list may not\\n show every case where this role is applied.\\n "],[13],[1,"\\n "],[8,[39,15],null,[["@caption","@items"],["Tokens",[30,1,["data"]]]],null],[1,"\\n"]],[]],null],[1," "]],[1]]]]],[1,"\\n"]],[]],null],[1," "],[10,0],[12],[1,"\\n "],[8,[39,16],null,null,[["default"],[[[[1,"\\n"],[41,[28,[37,17],[[33,8],[28,[37,18],["create roles"],null]],null],[[[1," "],[8,[39,19],[[16,"disabled",[28,[37,12],[[33,2,["isPristine"]],[33,2,["isInvalid"]],[28,[37,20],[[33,2,["Name"]],""],null]],null]],[24,4,"submit"],[4,[38,21],["click",[28,[37,22],["create",[33,2]],null]],null]],[["@text"],["Save"]],null],[1,"\\n"]],[]],[[[41,[28,[37,18],["write role"],[["item"],[[33,2]]]],[[[1," "],[8,[39,19],[[16,"disabled",[33,2,["isInvalid"]]],[24,4,"submit"],[4,[38,21],["click",[28,[37,22],["update",[33,2]],null]],null]],[["@text"],["Save"]],null],[1,"\\n"]],[]],null]],[]]],[1," "],[8,[39,19],[[24,4,"reset"],[4,[38,23],[[30,0],"cancel",[33,2]],null]],[["@text","@color"],["Cancel","secondary"]],null],[1,"\\n\\n"],[41,[28,[37,17],[[28,[37,7],[[33,8]],null],[28,[37,18],["delete role"],[["item"],[[33,2]]]]],null],[[[1," "],[8,[39,24],null,[["@message"],["Are you sure you want to delete this Role?"]],[["default"],[[[[1,"\\n "],[8,[39,25],null,[["@name"],["action"]],[["default"],[[[[1,"\\n "],[8,[39,19],[[4,[38,23],[[30,0],[30,2],"delete",[33,2]],null]],[["@text","@color"],["Delete","critical"]],null],[1,"\\n "]],[2]]]]],[1,"\\n "],[8,[39,25],null,[["@name"],["dialog"]],[["default"],[[[[1,"\\n"],[41,[28,[37,14],[[33,26,["length"]],0],null],[[[1," "],[8,[39,27],null,[["@onclose","@aria"],[[28,[37,23],[[30,0],[30,4]],null],[28,[37,11],null,[["label"],["Role in Use"]]]]],[["default"],[[[[1,"\\n "],[8,[39,25],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"Role in Use"],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,25],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[10,2],[12],[1,"\\n This Role is currently in use. If you choose to delete this Role, it will be removed from the\\n following "],[10,"strong"],[12],[1,[33,26,["length"]]],[1," Tokens"],[13],[1,":\\n "],[13],[1,"\\n "],[8,[39,15],null,[["@items","@target"],[[99,26,["@items"]],"_blank"]],null],[1,"\\n "],[10,2],[12],[1,"\\n This action cannot be undone. "],[1,[30,5]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,25],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[8,[39,16],null,null,[["default"],[[[[1,"\\n "],[8,[39,19],[[4,[38,23],[[30,0],[30,3]],null]],[["@text","@color"],["Yes, Delete","critical"]],null],[1,"\\n "],[8,[39,19],[[4,[38,23],[[30,0],[30,6]],null]],[["@text","@color"],["Cancel","secondary"]],null],[1,"\\n "]],[]]]]],[1,"\\n "]],[6]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],[[[1," "],[8,[39,28],null,[["@message","@execute","@cancel"],[[30,5],[30,3],[30,4]]],null],[1,"\\n"]],[]]],[1," "]],[3,4,5]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],null],[1," "]],[]]]]],[1,"\\n "],[13],[1,"\\n"],[13],[1,"\\n"]],["loader","confirm","execute","cancel","message","close"],false,["role-form","form","item","dc","nspace","partition","if","not","create","data-source","uri","hash","or","id","gt","token-list","hds/button-set","and","can","hds/button","eq","on","route-action","action","confirmation-dialog","block-slot","items","modal-dialog","delete-confirmation"]]',moduleName:"consul-ui/templates/dc/acls/roles/-form.hbs",isStrictMode:!1}) +var n=(0,t.createTemplateFactory)({id:"6qtq9TRB",block:'[[[1,"\\n"],[10,"form"],[12],[1,"\\n "],[8,[39,0],null,[["@form","@item","@dc","@nspace","@partition"],[[99,1,["@form"]],[99,2,["@item"]],[99,3,["@dc"]],[99,4,["@nspace"]],[99,5,["@partition"]]]],null],[1,"\\n"],[41,[28,[37,7],[[33,8]],null],[[[1," "],[8,[39,9],null,[["@src"],[[28,[37,10],["/${partition}/${nspace}/${dc}/tokens/for-role/${id}",[28,[37,11],null,[["partition","nspace","dc","id"],[[33,5],[33,4],[33,3],[52,[33,2],[33,2,["ID"]],""]]]]],null]]],[["default"],[[[[1,"\\n"],[41,[28,[37,12],[[30,1,["data","length"]],0],null],[[[1," "],[10,"h2"],[12],[1,"Where is this role used?"],[13],[1,"\\n "],[10,2],[12],[1,"\\n We\'re only able to show information for the primary datacenter and the current datacenter. This list may not\\n show every case where this role is applied.\\n "],[13],[1,"\\n "],[8,[39,13],null,[["@caption","@items"],["Tokens",[30,1,["data"]]]],null],[1,"\\n"]],[]],null],[1," "]],[1]]]]],[1,"\\n"]],[]],null],[1," "],[10,0],[12],[1,"\\n "],[8,[39,14],null,null,[["default"],[[[[1,"\\n"],[41,[28,[37,15],[[33,8],[28,[37,16],["create roles"],null]],null],[[[1," "],[8,[39,17],[[16,"disabled",[28,[37,18],[[33,2,["isPristine"]],[33,2,["isInvalid"]],[28,[37,19],[[33,2,["Name"]],""],null]],null]],[24,4,"submit"],[4,[38,20],["click",[28,[37,21],["create",[33,2]],null]],null]],[["@text"],["Save"]],null],[1,"\\n"]],[]],[[[41,[28,[37,16],["write role"],[["item"],[[33,2]]]],[[[1," "],[8,[39,17],[[16,"disabled",[33,2,["isInvalid"]]],[24,4,"submit"],[4,[38,20],["click",[28,[37,21],["update",[33,2]],null]],null]],[["@text"],["Save"]],null],[1,"\\n"]],[]],null]],[]]],[1," "],[8,[39,17],[[24,4,"reset"],[4,[38,22],[[30,0],"cancel",[33,2]],null]],[["@text","@color"],["Cancel","secondary"]],null],[1,"\\n\\n"],[41,[28,[37,15],[[28,[37,7],[[33,8]],null],[28,[37,16],["delete role"],[["item"],[[33,2]]]]],null],[[[1," "],[8,[39,23],null,[["@message"],["Are you sure you want to delete this Role?"]],[["default"],[[[[1,"\\n "],[8,[39,24],null,[["@name"],["action"]],[["default"],[[[[1,"\\n "],[8,[39,17],[[4,[38,22],[[30,0],[30,2],"delete",[33,2]],null]],[["@text","@color"],["Delete","critical"]],null],[1,"\\n "]],[2]]]]],[1,"\\n "],[8,[39,24],null,[["@name"],["dialog"]],[["default"],[[[[1,"\\n"],[41,[28,[37,12],[[33,25,["length"]],0],null],[[[1," "],[8,[39,26],null,[["@onclose","@aria"],[[28,[37,22],[[30,0],[30,4]],null],[28,[37,11],null,[["label"],["Role in Use"]]]]],[["default"],[[[[1,"\\n "],[8,[39,24],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"Role in Use"],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,24],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[10,2],[12],[1,"\\n This Role is currently in use. If you choose to delete this Role, it will be removed from the\\n following "],[10,"strong"],[12],[1,[33,25,["length"]]],[1," Tokens"],[13],[1,":\\n "],[13],[1,"\\n "],[8,[39,13],null,[["@items","@target"],[[99,25,["@items"]],"_blank"]],null],[1,"\\n "],[10,2],[12],[1,"\\n This action cannot be undone. "],[1,[30,5]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,24],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[8,[39,14],null,null,[["default"],[[[[1,"\\n "],[8,[39,17],[[4,[38,22],[[30,0],[30,3]],null]],[["@text","@color"],["Yes, Delete","critical"]],null],[1,"\\n "],[8,[39,17],[[4,[38,22],[[30,0],[30,6]],null]],[["@text","@color"],["Cancel","secondary"]],null],[1,"\\n "]],[]]]]],[1,"\\n "]],[6]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],[[[1," "],[8,[39,27],null,[["@message","@execute","@cancel"],[[30,5],[30,3],[30,4]]],null],[1,"\\n"]],[]]],[1," "]],[3,4,5]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]],null],[1," "]],[]]]]],[1,"\\n "],[13],[1,"\\n"],[13],[1,"\\n"]],["loader","confirm","execute","cancel","message","close"],false,["role-form","form","item","dc","nspace","partition","if","not","create","data-source","uri","hash","gt","token-list","hds/button-set","and","can","hds/button","or","eq","on","route-action","action","confirmation-dialog","block-slot","items","modal-dialog","delete-confirmation"]]',moduleName:"consul-ui/templates/dc/acls/roles/-form.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/acls/roles/edit",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"B1T3t9iM",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/role/${id}",[28,[37,4],null,[["partition","nspace","dc","id"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]],[28,[37,5],[[30,1,["params","id"]],""],null]]]]],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,6],null,[["@name"],["error"]],[["default"],[[[[1,"\\n"],[41,[28,[37,8],[[30,2,["error","status"]],"401"],null],[[[1," "],[8,[39,9],null,null,null],[1,"\\n"]],[]],[[[1," "],[8,[39,10],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n"]],[]]],[1," "]],[]]]]],[1,"\\n\\n "],[8,[39,6],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n"],[44,[[30,1,["params","dc"]],[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,2,["data"]],[30,2,["data","isNew"]]],[[[1," "],[8,[39,12],null,[["@login"],[[30,1,["model","app","login","open"]]]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["breadcrumbs"]],[["default"],[[[[1,"\\n "],[10,"ol"],[12],[1,"\\n "],[10,"li"],[12],[10,3],[15,6,[28,[37,13],["dc.acls.roles"],null]],[12],[1,"All Roles"],[13],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h1"],[12],[1,"\\n"],[41,[30,7],[[[1," "],[8,[30,1,["Title"]],null,[["@title"],["New Role"]],null],[1,"\\n"]],[]],[[[1," "],[8,[30,1,["Title"]],null,[["@title"],["Edit Role"]],null],[1,"\\n"]],[]]],[1," "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["content"]],[["default"],[[[[1,"\\n"],[41,[28,[37,14],[[30,7]],null],[[[1," "],[10,0],[14,0,"definition-table"],[12],[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,"Role ID"],[13],[1,"\\n "],[10,"dd"],[12],[1,"\\n "],[8,[39,15],null,[["@value","@name"],[[30,6,["ID"]],"Role ID"]],null],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n"]],[]],null],[1," "],[19,"dc/acls/roles/form",[1,2,3,4,5,6,7]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[3,4,5,6,7]]],[1," "]],[]]]]],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]]],["route","loader","dc","partition","nspace","item","create"],true,["route","routeName","data-loader","uri","hash","or","block-slot","if","eq","consul/acl/disabled","app-error","let","app-view","href-to","not","copyable-code","partial"]]',moduleName:"consul-ui/templates/dc/acls/roles/edit.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/acls/roles/index",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 @@ -3968,19 +3982,19 @@ e.default=n})),define("consul-ui/templates/dc/peers/index",["exports","@ember/te var n=(0,t.createTemplateFactory)({id:"uPTWAtBk",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/peers",[28,[37,4],null,[["partition","nspace","dc"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]]]]]],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,5],null,[["@name"],["error"]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n\\n "],[8,[39,5],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n"],[44,[[28,[37,4],null,[["value","change"],[[28,[37,8],[[33,9],"State:asc"],null],[28,[37,10],[[30,0],[28,[37,11],[[33,9]],null]],[["value"],["target.selected"]]]]]],[28,[37,4],null,[["state","searchproperty"],[[28,[37,4],null,[["value","change"],[[52,[33,13],[28,[37,14],[[33,13],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,13]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change","default"],[[52,[28,[37,15],[[33,16],[27]],null],[28,[37,14],[[33,16],","],null],[33,17]],[28,[37,10],[[30,0],[28,[37,11],[[33,16]],null]],[["value"],["target.selectedItems"]]],[33,17]]]]]]],[30,2,["data"]]],[[[1," "],[8,[39,18],null,null,[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h1"],[12],[1,"\\n "],[8,[30,1,["Title"]],null,[["@title"],["Peers"]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["toolbar"]],[["default"],[[[[1,"\\n\\n"],[41,[28,[37,19],[[30,5,["length"]],0],null],[[[1," "],[8,[39,20],null,[["@search","@onsearch","@sort","@filter"],[[99,21,["@search"]],[28,[37,10],[[30,0],[28,[37,11],[[33,21]],null]],[["value"],["target.value"]]],[30,3],[30,4]]],null],[1,"\\n"]],[]],null],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n\\n "],[8,[39,22],[[24,0,"peer-create-modal"]],[["@aria"],[[28,[37,4],null,[["label"],["Add peer connection"]]]]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[1,[28,[35,23],[[28,[37,24],[[30,0],"create",[30,6]],null]],null]],[1,"\\n "],[10,"h2"],[12],[1,"\\n Add peer connection\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["body"]],[["default"],[[[[1,"\\n\\n"],[41,[30,6,["opened"]],[[[1," "],[8,[39,25],null,[["@params"],[[30,1,["params"]]]],[["default"],[[[[1,"\\n "],[8,[30,7,["Form"]],null,[["@onchange","@onsubmit"],[[30,2,["invalidate"]],[28,[37,26],[[30,0,["redirectToPeerShow"]],[30,6,["close"]]],null]]],[["default"],[[[[1,"\\n "],[1,[28,[35,23],[[28,[37,24],[[30,0],"form",[30,8]],null]],null]],[1,"\\n "],[8,[30,8,["Fieldsets"]],null,null,null],[1,"\\n "]],[8]]]]],[1,"\\n "]],[7]]]]],[1,"\\n"]],[]],null],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[8,[30,0,["form","Actions"]],null,[["@onclose"],[[30,0,["create","close"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n "]],[6]]]]],[1,"\\n "],[8,[39,27],[[4,[38,28],["click",[28,[37,29],[[30,0,["create","open"]]],null]],null]],[["@color","@text"],["primary","Add peer connection"]],null],[1,"\\n\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["content"]],[["default"],[[[[1,"\\n\\n "],[8,[39,30],null,[["@sink","@type","@label"],[[28,[37,3],["/${partition}/${dc}/${nspace}/peer/",[28,[37,4],null,[["partition","nspace","dc"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]]]]]],null],"peer","Peer"]],[["default"],[[[[1,"\\n\\n "],[8,[39,5],null,[["@name"],["removed"]],[["default"],[[[[1,"\\n "],[8,[39,31],[[4,[38,32],null,[["after"],[[28,[37,10],[[30,0],[30,10]],null]]]]],[["@type"],["remove"]],null],[1,"\\n "]],[10]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["content"]],[["default"],[[[[1,"\\n\\n "],[8,[39,22],null,[["@aria","@onclose"],[[28,[37,4],null,[["label"],["Regenerate token"]]],[28,[37,24],[[30,0],"item",[27]],null]]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[1,[28,[35,23],[[28,[37,24],[[30,0],"regenerate",[30,11]],null]],null]],[1,"\\n "],[10,"h2"],[12],[1,"\\n Regenerate token\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["body"]],[["default"],[[[[1,"\\n"],[41,[30,0,["item"]],[[[1," "],[8,[39,33],null,[["@item","@onchange","@regenerate"],[[30,0,["item"]],[30,2,["invalidate"]],true]],[["default"],[[[[1,"\\n "],[1,[28,[35,23],[[28,[37,24],[[30,0],"regenerateForm",[30,12]],null]],null]],[1,"\\n "],[8,[30,12,["Fieldsets"]],null,null,null],[1,"\\n "]],[12]]]]],[1,"\\n"]],[]],null],[1," "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[8,[30,0,["regenerateForm","Actions"]],null,[["@onclose"],[[30,0,["regenerate","close"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n "]],[11]]]]],[1,"\\n\\n "],[8,[39,34],null,[["@type","@sort","@filters","@search","@items"],["peer",[30,3,["value"]],[30,4],[99,21,["@search"]],[30,5]]],[["default"],[[[[1,"\\n "],[8,[30,13,["Collection"]],null,null,[["default"],[[[[1,"\\n\\n "],[8,[39,35],null,[["@items","@onedit","@ondelete"],[[30,13,["items"]],[28,[37,36],[[28,[37,24],[[30,0],"item"],null],[30,0,["regenerate","open"]]],null],[30,9,["delete"]]]],null],[1,"\\n\\n "]],[]]]]],[1,"\\n "],[8,[30,13,["Empty"]],null,null,[["default"],[[[[1,"\\n"],[1," "],[8,[39,37],null,[["@login"],[[30,1,["model","app","login","open"]]]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"\\n "],[1,[28,[35,38],["routes.dc.peers.index.empty.header"],[["items"],[[30,5,["length"]]]]]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[10,2],[12],[1,"\\n "],[1,[28,[35,38],["routes.dc.peers.index.empty.body"],[["items","canUsePartitions","canUseACLs","htmlSafe"],[[30,5,["length"]],[28,[37,39],["use partitions"],null],[28,[37,39],["use acls"],null],true]]]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[10,"li"],[12],[1,"\\n"],[1," "],[8,[39,40],null,[["@text","@href","@icon","@iconPosition","@size"],["Documentation on Peers",[29,[[28,[37,41],["CONSUL_DOCS_URL"],null],"/connect/cluster-peering"]],"docs-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,40],null,[["@text","@href","@icon","@iconPosition","@size"],["Take the tutorial",[29,[[28,[37,41],["CONSUL_DOCS_URL"],null],"/connect/cluster-peering/create-manage-peering"]],"learn-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[13]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[9]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[3,4,5]]],[1," "]],[]]]]],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]]],["route","loader","sort","filters","items","modal","form","form","writer","after","modal","form","collection"],false,["route","routeName","data-loader","uri","hash","block-slot","app-error","let","or","sortBy","action","mut","if","state","split","not-eq","searchproperty","searchProperties","app-view","gt","consul/peer/search-bar","search","modal-dialog","did-insert","set","consul/peer/form","fn","hds/button","on","optional","data-writer","consul/peer/notifications","notification","consul/peer/form/generate","data-collection","consul/peer/list","queue","empty-state","t","can","hds/link/standalone","env"]]',moduleName:"consul-ui/templates/dc/peers/index.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/peers/show",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"AvoaCEAO",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/peer/${name}",[28,[37,4],null,[["partition","nspace","dc","name"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]],[30,1,["params","name"]]]]]],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,5],null,[["@name"],["error"]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n\\n "],[8,[39,5],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n"],[44,[[30,1,["params","dc"]],[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,2,["data"]]],[[[1," "],[8,[39,8],null,null,[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["breadcrumbs"]],[["default"],[[[[1,"\\n "],[10,"ol"],[12],[1,"\\n "],[10,"li"],[12],[10,3],[15,6,[28,[37,9],["dc.peers"],null]],[12],[1,"All Peers"],[13],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h1"],[12],[1,"\\n "],[8,[30,1,["Title"]],null,[["@title"],[[30,6,["Name"]]]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["content"]],[["default"],[[[[1,"\\n "],[8,[39,10],null,[["@peering"],[[30,6]]],null],[1,"\\n "],[8,[39,11],null,[["@peer"],[[30,6]]],[["default"],[[[[1,"\\n "],[8,[39,12],null,[["@items"],[[30,7,["data","tabs"]]]],null],[1,"\\n\\n "]],[7]]]]],[1,"\\n "],[8,[39,13],null,[["@name","@model"],[[99,1,["@name"]],[28,[37,14],[[28,[37,4],null,[["items","peer"],[[30,6,["PeerServerAddresses"]],[30,6]]]],[30,1,["model"]]],null]]],[["default"],[[[[1,"\\n "],[46,[28,[37,16],null,null],null,null,null],[1,"\\n "]],[8]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[3,4,5,6]]],[1," "]],[]]]]],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]]],["route","loader","dc","partition","nspace","item","peering","o"],false,["route","routeName","data-loader","uri","hash","block-slot","app-error","let","app-view","href-to","consul/peer/bento-box","peerings/provider","tab-nav","outlet","assign","component","-outlet"]]',moduleName:"consul-ui/templates/dc/peers/show.hbs",isStrictMode:!1}) -e.default=n})),define("consul-ui/templates/dc/peers/show/addresses",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=n})) +define("consul-ui/templates/dc/peers/show/addresses",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"3k5sOecZ",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n"],[41,[28,[37,3],[[30,1,["model","items","length"]],0],null],[[[1," "],[8,[39,4],null,[["@items"],[[30,1,["model","items"]]]],null],[1,"\\n"]],[]],[[[1," "],[8,[39,5],null,[["@login"],[[30,1,["model","app","login","open"]]]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"\\n "],[1,[28,[35,7],["routes.dc.peers.show.addresses.empty.header"],null]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[1,[28,[35,7],["routes.dc.peers.show.addresses.empty.body"],[["htmlSafe"],[true]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,6],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,8],null,[["@text","@href","@icon","@iconPosition","@size"],["Documentation on Peers",[29,[[28,[37,9],["CONSUL_DOCS_URL"],null],"/connect/cluster-peering"]],"docs-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,8],null,[["@text","@href","@icon","@iconPosition","@size"],["Take the tutorial",[29,[[28,[37,9],["CONSUL_DOCS_URL"],null],"/connect/cluster-peering/create-manage-peering"]],"learn-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]]]],[1]]]]]],["route"],false,["route","routeName","if","gt","consul/peer/address/list","empty-state","block-slot","t","hds/link/standalone","env"]]',moduleName:"consul-ui/templates/dc/peers/show/addresses.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/peers/show/exported",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"Z0gVGZWd",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/exported-services/${name}",[28,[37,4],null,[["partition","nspace","dc","name"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]],[30,1,["model","peer","Name"]]]]]],null]]],[["default"],[[[[1,"\\n"],[44,[[28,[37,6],[[30,1,["params","partition"]],[30,1,["model","user","token","Partition"]],"default"],null],[30,2,["data"]]],[[[1,"\\n "],[8,[39,7],null,[["@name"],["error"]],[["default"],[[[[1,"\\n "],[8,[39,8],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n\\n "],[8,[39,7],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n"],[41,[30,4,["length"]],[[[1," "],[10,0],[14,0,"search-bar"],[12],[1,"\\n "],[10,"form"],[14,0,"filter-bar"],[12],[1,"\\n "],[8,[39,10],[[24,0,"!w-80"]],[["@onsearch","@value","@placeholder"],[[28,[37,11],["target.value",[30,0,["updateSearch"]]],null],[30,0,["search"]],"Search"]],null],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n"]],[]],null],[1," "],[8,[39,12],null,[["@items","@search","@searchProperties"],[[30,4],[30,0,["search"]],[28,[37,13],["Name"],null]]],[["default"],[[[[1,"\\n "],[8,[39,14],null,null,[["default"],[[[[1,"\\n"],[41,[30,6,["data","height"]],[[[1," "],[10,0],[15,5,[30,6,["data","fillRemainingHeightStyle"]]],[14,0,"overflow-y-scroll"],[12],[1,"\\n"],[41,[30,5,["data","items","length"]],[[[1," "],[8,[39,15],null,[["@tagName","@estimateHeight","@items"],["ul",[30,6,["data","height"]],[30,5,["data","items"]]]],[["default"],[[[[1,"\\n "],[10,"li"],[14,0,"px-3 h-12 border-bottom-primary"],[12],[1,"\\n "],[10,3],[14,0,"hds-typography-display-300 hds-foreground-strong hds-font-weight-semibold h-full w-full flex items-center"],[15,6,[28,[37,16],["dc.services.show.index",[30,7,["Name"]]],[["params"],[[52,[28,[37,17],[[30,7,["Partition"]],[30,3]],null],[28,[37,4],null,[["partition","nspace","peer"],[[30,7,["Partition"]],[30,7,["Namespace"]],[30,7,["PeerName"]]]]],[28,[37,4],null,[["peer"],[[30,7,["PeerName"]]]]]]]]]],[12],[1,"\\n "],[1,[30,7,["Name"]]],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "]],[7,8]]]]],[1,"\\n"]],[]],[[[1," "],[8,[39,18],null,[["@login"],[[30,1,["model","app","login","open"]]]],[["default"],[[[[1,"\\n "],[8,[39,7],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"\\n "],[1,[28,[35,19],["routes.dc.peers.show.exported.empty.header"],[["name"],[[30,1,["model","peer","Name"]]]]]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[1,[28,[35,19],["routes.dc.peers.show.exported.empty.body"],[["items","name","htmlSafe"],[[30,4,["length"]],[30,1,["model","peer","Name"]],true]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,7],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,20],null,[["@text","@href","@icon","@iconPosition","@size"],["Documentation on Peers",[29,[[28,[37,21],["CONSUL_DOCS_URL"],null],"/connect/cluster-peering"]],"docs-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,20],null,[["@text","@href","@icon","@iconPosition","@size"],["Take the tutorial",[29,[[28,[37,21],["CONSUL_DOCS_URL"],null],"/connect/cluster-peering/create-manage-peering"]],"learn-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[]]],[1," "],[13],[1,"\\n"]],[]],null],[1," "]],[6]]]]],[1,"\\n\\n "]],[5]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[3,4]]],[1," "]],[2]]]]],[1,"\\n\\n"]],[1]]]]]],["route","api","partition","items","search","p","service","index"],false,["route","routeName","data-loader","uri","hash","let","or","block-slot","app-error","if","freetext-filter","pick","providers/search","array","providers/dimension","vertical-collection","href-to","not-eq","empty-state","t","hds/link/standalone","env"]]',moduleName:"consul-ui/templates/dc/peers/show/exported.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/peers/show/imported",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -var n=(0,t.createTemplateFactory)({id:"WW9UdfAm",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/services/${peer}/${peerId}",[28,[37,4],null,[["partition","nspace","dc","peer","peerId"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]],[30,1,["model","peer","Name"]],[30,1,["model","peer","id"]]]]]],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,5],null,[["@name"],["error"]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n\\n "],[8,[39,5],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n"],[44,[[28,[37,4],null,[["value","change"],[[28,[37,8],[[33,9],"Status:asc"],null],[28,[37,10],[[30,0],[28,[37,11],[[33,9]],null]],[["value"],["target.selected"]]]]]],[28,[37,4],null,[["status","kind","source","searchproperty"],[[28,[37,4],null,[["value","change"],[[52,[33,13],[28,[37,14],[[33,13],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,13]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change"],[[52,[33,15],[28,[37,14],[[33,15],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,15]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change"],[[52,[33,16],[28,[37,14],[[33,16],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,16]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change","default"],[[52,[28,[37,17],[[33,18],[27]],null],[28,[37,14],[[33,18],","],null],[30,0,["searchProperties"]]],[28,[37,10],[[30,0],[28,[37,11],[[33,18]],null]],[["value"],["target.selectedItems"]]],[30,0,["searchProperties"]]]]]]]],[28,[37,19],["Kind","connect-proxy",[30,2,["data"]]],null],[28,[37,8],[[30,1,["params","partition"]],[30,1,["model","user","token","Partition"]],"default"],null],[28,[37,8],[[30,1,["params","nspace"]],[30,1,["model","user","token","Namespace"]],"default"],null]],[[[1,"\\n"],[41,[28,[37,20],[[30,5,["length"]],0],null],[[[44,[[28,[37,21],[[30,5]],null]],[[[1," "],[8,[39,22],null,[["@sources","@partitions","@partition","@search","@onsearch","@sort","@filter","@peer"],[[28,[37,23],[[30,8],"ExternalSources"],null],[28,[37,23],[[30,8],"Partitions"],null],[30,6],[99,24,["@search"]],[28,[37,10],[[30,0],[28,[37,11],[[33,24]],null]],[["value"],["target.value"]]],[30,3],[30,4],[30,1,["model","peer"]]]],null],[1,"\\n"]],[8]]]],[]],null],[1," "],[8,[39,25],null,[["@type","@sort","@filters","@search","@items"],["service",[30,3,["value"]],[30,4],[99,24,["@search"]],[30,5]]],[["default"],[[[[1,"\\n "],[8,[30,9,["Collection"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,26],null,[["@items","@partition","@isPeerDetail"],[[30,9,["items"]],[30,6],true]],null],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,9,["Empty"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,27],null,[["@login"],[[30,1,["model","app","login","open"]]]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"\\n "],[1,[28,[35,28],["routes.dc.peers.show.imported.empty.header"],[["name"],[[30,1,["model","peer","Name"]]]]]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[1,[28,[35,28],["routes.dc.peers.show.imported.empty.body"],[["items","name","htmlSafe"],[[30,5,["length"]],[30,1,["model","peer","Name"]],true]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[10,"li"],[12],[1,"\\n"],[1," "],[8,[39,29],null,[["@text","@href","@icon","@iconPosition","@size"],["Documentation on Peers",[29,[[28,[37,30],["CONSUL_DOCS_URL"],null],"/connect/cluster-peering"]],"docs-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,29],null,[["@text","@href","@icon","@iconPosition","@size"],["Take the tutorial",[29,[[28,[37,30],["CONSUL_DOCS_URL"],null],"/connect/cluster-peering/create-manage-peering"]],"learn-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[9]]]]],[1,"\\n"]],[3,4,5,6,7]]],[1," "]],[]]]]],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]]],["route","api","sort","filters","items","partition","nspace","items","collection"],false,["route","routeName","data-loader","uri","hash","block-slot","app-error","let","or","sortBy","action","mut","if","status","split","kind","source","not-eq","searchproperty","reject-by","gt","collection","consul/service/search-bar","get","search","data-collection","consul/service/list","empty-state","t","hds/link/standalone","env"]]',moduleName:"consul-ui/templates/dc/peers/show/imported.hbs",isStrictMode:!1}) -e.default=n})) -define("consul-ui/templates/dc/peers/show/index",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +var n=(0,t.createTemplateFactory)({id:"Ioh7AUof",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/services/${peer}/${peerId}",[28,[37,4],null,[["partition","nspace","dc","peer","peerId"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]],[30,1,["model","peer","Name"]],[30,1,["model","peer","id"]]]]]],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,5],null,[["@name"],["error"]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n\\n "],[8,[39,5],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n"],[44,[[28,[37,4],null,[["value","change"],[[28,[37,8],[[33,9],"Status:asc"],null],[28,[37,10],[[30,0],[28,[37,11],[[33,9]],null]],[["value"],["target.selected"]]]]]],[28,[37,4],null,[["status","kind","source","searchproperty"],[[28,[37,4],null,[["value","change"],[[52,[33,13],[28,[37,14],[[33,13],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,13]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change"],[[52,[33,15],[28,[37,14],[[33,15],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,15]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change"],[[52,[33,16],[28,[37,14],[[33,16],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,16]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change","default"],[[52,[28,[37,17],[[33,18],[27]],null],[28,[37,14],[[33,18],","],null],[30,0,["searchProperties"]]],[28,[37,10],[[30,0],[28,[37,11],[[33,18]],null]],[["value"],["target.selectedItems"]]],[30,0,["searchProperties"]]]]]]]],[28,[37,19],["Kind","connect-proxy",[30,2,["data"]]],null],[28,[37,8],[[30,1,["params","partition"]],[30,1,["model","user","token","Partition"]],"default"],null],[28,[37,8],[[30,1,["params","nspace"]],[30,1,["model","user","token","Namespace"]],"default"],null]],[[[1,"\\n"],[41,[28,[37,20],[[30,5,["length"]],0],null],[[[44,[[28,[37,21],[[30,5]],null]],[[[1," "],[8,[39,22],null,[["@sources","@partitions","@partition","@search","@onsearch","@sort","@filter","@peer"],[[28,[37,23],[[30,8],"ExternalSources"],null],[28,[37,23],[[30,8],"Partitions"],null],[30,6],[99,24,["@search"]],[28,[37,10],[[30,0],[28,[37,11],[[33,24]],null]],[["value"],["target.value"]]],[30,3],[30,4],[30,1,["model","peer"]]]],null],[1,"\\n"]],[8]]]],[]],null],[1," "],[8,[39,25],null,[["@type","@sort","@filters","@search","@items"],["service",[30,3,["value"]],[30,4],[99,24,["@search"]],[30,5]]],[["default"],[[[[1,"\\n "],[8,[30,9,["Collection"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,26],null,[["@items","@partition","@nspace","@isPeerDetail"],[[30,9,["items"]],[30,6],[30,7],true]],null],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,9,["Empty"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,27],null,[["@login"],[[30,1,["model","app","login","open"]]]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"\\n "],[1,[28,[35,28],["routes.dc.peers.show.imported.empty.header"],[["name"],[[30,1,["model","peer","Name"]]]]]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[1,[28,[35,28],["routes.dc.peers.show.imported.empty.body"],[["items","name","htmlSafe"],[[30,5,["length"]],[30,1,["model","peer","Name"]],true]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[10,"li"],[12],[1,"\\n"],[1," "],[8,[39,29],null,[["@text","@href","@icon","@iconPosition","@size"],["Documentation on Peers",[29,[[28,[37,30],["CONSUL_DOCS_URL"],null],"/connect/cluster-peering"]],"docs-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,29],null,[["@text","@href","@icon","@iconPosition","@size"],["Take the tutorial",[29,[[28,[37,30],["CONSUL_DOCS_URL"],null],"/connect/cluster-peering/create-manage-peering"]],"learn-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[9]]]]],[1,"\\n"]],[3,4,5,6,7]]],[1," "]],[]]]]],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]]],["route","api","sort","filters","items","partition","nspace","items","collection"],false,["route","routeName","data-loader","uri","hash","block-slot","app-error","let","or","sortBy","action","mut","if","status","split","kind","source","not-eq","searchproperty","reject-by","gt","collection","consul/service/search-bar","get","search","data-collection","consul/service/list","empty-state","t","hds/link/standalone","env"]]',moduleName:"consul-ui/templates/dc/peers/show/imported.hbs",isStrictMode:!1}) +e.default=n})),define("consul-ui/templates/dc/peers/show/index",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"IJMk41B8",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[1,[28,[35,2],[[30,0,["transitionToImported"]]],null]],[1,"\\n"]],[1]]]]]],["route"],false,["route","routeName","did-insert"]]',moduleName:"consul-ui/templates/dc/peers/show/index.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/routing-config",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"ag2giQ2I",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/discovery-chain/${name}",[28,[37,4],null,[["partition","nspace","dc","name"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]],[30,1,["params","name"]]]]]],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,5],null,[["@name"],["error"]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n\\n "],[8,[39,5],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n"],[44,[[30,2,["data"]]],[[[8,[39,8],null,null,[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["breadcrumbs"]],[["default"],[[[[1,"\\n "],[10,"ol"],[12],[1,"\\n "],[10,"li"],[12],[10,3],[15,6,[28,[37,9],["dc.services"],null]],[12],[1,"All Services"],[13],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h1"],[12],[1,"\\n "],[8,[30,1,["Title"]],null,[["@title"],[[30,3,["Chain","ServiceName"]]]],null],[1,"\\n "],[13],[1,"\\n "],[8,[39,10],null,[["@source","@withInfo"],[[28,[37,11],["routes.dc.routing-config.source"],null],true]],null],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["content"]],[["default"],[[[[1,"\\n "],[10,0],[14,0,"container"],[12],[1,"\\n "],[8,[39,12],null,[["@chain"],[[30,3,["Chain"]]]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n"]],[]]]]],[1,"\\n"]],[3]]],[1," "]],[]]]]],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]],[1,"\\n"]],["route","loader","item"],false,["route","routeName","data-loader","uri","hash","block-slot","app-error","let","app-view","href-to","consul/source","t","consul/discovery-chain"]]',moduleName:"consul-ui/templates/dc/routing-config.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/services/index",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 -var n=(0,t.createTemplateFactory)({id:"9rUU3uSO",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/services",[28,[37,4],null,[["partition","nspace","dc"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]]]]]],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,5],null,[["@name"],["error"]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n\\n "],[8,[39,5],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n\\n"],[44,[[28,[37,4],null,[["value","change"],[[28,[37,8],[[33,9],"Status:asc"],null],[28,[37,10],[[30,0],[28,[37,11],[[33,9]],null]],[["value"],["target.selected"]]]]]],[28,[37,4],null,[["status","kind","source","searchproperty"],[[28,[37,4],null,[["value","change"],[[52,[33,13],[28,[37,14],[[33,13],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,13]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change"],[[52,[33,15],[28,[37,14],[[33,15],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,15]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change"],[[52,[33,16],[28,[37,14],[[33,16],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,16]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change","default"],[[52,[28,[37,17],[[33,18],[27]],null],[28,[37,14],[[33,18],","],null],[30,0,["_searchProperties"]]],[28,[37,10],[[30,0],[28,[37,11],[[33,18]],null]],[["value"],["target.selectedItems"]]],[30,0,["_searchProperties"]]]]]]]],[28,[37,19],["Kind","connect-proxy",[30,2,["data"]]],null],[28,[37,8],[[30,1,["params","partition"]],[30,1,["model","user","token","Partition"]],"default"],null],[28,[37,8],[[30,1,["params","nspace"]],[30,1,["model","user","token","Namespace"]],"default"],null]],[[[1,"\\n "],[8,[39,20],null,null,[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h1"],[12],[1,"\\n "],[8,[30,1,["Title"]],null,[["@title"],["Services"]],null],[1," "],[10,"em"],[12],[1,[28,[35,21],[[30,5,["length"]]],null]],[1," total"],[13],[1,"\\n "],[13],[1,"\\n "],[10,"label"],[14,"for","toolbar-toggle"],[12],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["toolbar"]],[["default"],[[[[1,"\\n"],[41,[28,[37,22],[[30,5,["length"]],0],null],[[[44,[[28,[37,23],[[30,5]],null]],[[[1," "],[8,[39,24],null,[["@sources","@partitions","@partition","@search","@onsearch","@sort","@filter"],[[28,[37,25],[[30,8],"ExternalSources"],null],[28,[37,25],[[30,8],"Partitions"],null],[30,6],[99,26,["@search"]],[28,[37,10],[[30,0],[28,[37,11],[[33,26]],null]],[["value"],["target.value"]]],[30,3],[30,4]]],null],[1,"\\n"]],[8]]]],[]],null],[1," "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["content"]],[["default"],[[[[1,"\\n "],[8,[39,27],null,[["@type","@sort","@filters","@search","@items"],["service",[30,3,["value"]],[30,4],[99,26,["@search"]],[30,5]]],[["default"],[[[[1,"\\n "],[8,[30,9,["Collection"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,28],null,[["@items","@partition"],[[30,9,["items"]],[30,6]]],[["default"],[[[[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,9,["Empty"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,29],null,[["@login"],[[30,1,["model","app","login","open"]]]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"\\n "],[1,[28,[35,30],["routes.dc.services.index.empty.header"],[["items"],[[30,5,["length"]]]]]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[1,[28,[35,30],["routes.dc.services.index.empty.body"],[["items","canUseACLs","htmlSafe"],[[30,5,["length"]],[28,[37,31],["use acls"],null],true]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,32],null,[["@text","@href","@icon","@iconPosition","@size"],["Documentation on Services",[29,[[28,[37,33],["CONSUL_DOCS_URL"],null],"/commands/services"]],"docs-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,32],null,[["@text","@href","@icon","@iconPosition","@size"],["Take the tutorial",[29,[[28,[37,33],["CONSUL_DOCS_LEARN_URL"],null],"/consul/getting-started/services"]],"learn-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[9]]]]],[1,"\\n "]],[]]]]],[1,"\\n\\n "]],[]]]]],[1,"\\n\\n"]],[3,4,5,6,7]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]],[1,"\\n"]],["route","api","sort","filters","items","partition","nspace","items","collection"],false,["route","routeName","data-loader","uri","hash","block-slot","app-error","let","or","sortBy","action","mut","if","status","split","kind","source","not-eq","searchproperty","reject-by","app-view","format-number","gt","collection","consul/service/search-bar","get","search","data-collection","consul/service/list","empty-state","t","can","hds/link/standalone","env"]]',moduleName:"consul-ui/templates/dc/services/index.hbs",isStrictMode:!1}) +var n=(0,t.createTemplateFactory)({id:"k/tlhz7S",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/services",[28,[37,4],null,[["partition","nspace","dc"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]]]]]],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,5],null,[["@name"],["error"]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n\\n "],[8,[39,5],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n\\n"],[44,[[28,[37,4],null,[["value","change"],[[28,[37,8],[[33,9],"Status:asc"],null],[28,[37,10],[[30,0],[28,[37,11],[[33,9]],null]],[["value"],["target.selected"]]]]]],[28,[37,4],null,[["status","kind","source","searchproperty"],[[28,[37,4],null,[["value","change"],[[52,[33,13],[28,[37,14],[[33,13],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,13]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change"],[[52,[33,15],[28,[37,14],[[33,15],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,15]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change"],[[52,[33,16],[28,[37,14],[[33,16],","],null],[27]],[28,[37,10],[[30,0],[28,[37,11],[[33,16]],null]],[["value"],["target.selectedItems"]]]]]],[28,[37,4],null,[["value","change","default"],[[52,[28,[37,17],[[33,18],[27]],null],[28,[37,14],[[33,18],","],null],[30,0,["_searchProperties"]]],[28,[37,10],[[30,0],[28,[37,11],[[33,18]],null]],[["value"],["target.selectedItems"]]],[30,0,["_searchProperties"]]]]]]]],[28,[37,19],["Kind","connect-proxy",[30,2,["data"]]],null],[28,[37,8],[[30,1,["params","partition"]],[30,1,["model","user","token","Partition"]],"default"],null],[28,[37,8],[[30,1,["params","nspace"]],[30,1,["model","user","token","Namespace"]],"default"],null]],[[[1,"\\n "],[8,[39,20],null,null,[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h1"],[12],[1,"\\n "],[8,[30,1,["Title"]],null,[["@title"],["Services"]],null],[1," "],[10,"em"],[12],[1,[28,[35,21],[[30,5,["length"]]],null]],[1," total"],[13],[1,"\\n "],[13],[1,"\\n "],[10,"label"],[14,"for","toolbar-toggle"],[12],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["toolbar"]],[["default"],[[[[1,"\\n"],[41,[28,[37,22],[[30,5,["length"]],0],null],[[[44,[[28,[37,23],[[30,5]],null]],[[[1," "],[8,[39,24],null,[["@sources","@partitions","@partition","@search","@onsearch","@sort","@filter"],[[28,[37,25],[[30,8],"ExternalSources"],null],[28,[37,25],[[30,8],"Partitions"],null],[30,6],[99,26,["@search"]],[28,[37,10],[[30,0],[28,[37,11],[[33,26]],null]],[["value"],["target.value"]]],[30,3],[30,4]]],null],[1,"\\n"]],[8]]]],[]],null],[1," "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["content"]],[["default"],[[[[1,"\\n "],[8,[39,27],null,[["@type","@sort","@filters","@search","@items"],["service",[30,3,["value"]],[30,4],[99,26,["@search"]],[30,5]]],[["default"],[[[[1,"\\n "],[8,[30,9,["Collection"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,28],null,[["@items","@partition","@nspace"],[[30,9,["items"]],[30,6],[30,7]]],[["default"],[[[[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[30,9,["Empty"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,29],null,[["@login"],[[30,1,["model","app","login","open"]]]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h2"],[12],[1,"\\n "],[1,[28,[35,30],["routes.dc.services.index.empty.header"],[["items"],[[30,5,["length"]]]]]],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["body"]],[["default"],[[[[1,"\\n "],[1,[28,[35,30],["routes.dc.services.index.empty.body"],[["items","canUseACLs","htmlSafe"],[[30,5,["length"]],[28,[37,31],["use acls"],null],true]]]],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,32],null,[["@text","@href","@icon","@iconPosition","@size"],["Documentation on Services",[29,[[28,[37,33],["CONSUL_DOCS_URL"],null],"/commands/services"]],"docs-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "],[10,"li"],[12],[1,"\\n "],[8,[39,32],null,[["@text","@href","@icon","@iconPosition","@size"],["Take the tutorial",[29,[[28,[37,33],["CONSUL_DOCS_LEARN_URL"],null],"/consul/getting-started/services"]],"learn-link","trailing","small"]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[9]]]]],[1,"\\n "]],[]]]]],[1,"\\n\\n "]],[]]]]],[1,"\\n\\n"]],[3,4,5,6,7]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]],[1,"\\n"]],["route","api","sort","filters","items","partition","nspace","items","collection"],false,["route","routeName","data-loader","uri","hash","block-slot","app-error","let","or","sortBy","action","mut","if","status","split","kind","source","not-eq","searchproperty","reject-by","app-view","format-number","gt","collection","consul/service/search-bar","get","search","data-collection","consul/service/list","empty-state","t","can","hds/link/standalone","env"]]',moduleName:"consul-ui/templates/dc/services/index.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/services/instance",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"UwadJzgF",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["/${partition}/${nspace}/${dc}/service-instance/${id}/${node}/${name}/${peer}",[28,[37,4],null,[["partition","nspace","dc","id","node","name","peer"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]],[30,1,["params","id"]],[30,1,["params","node"]],[30,1,["params","name"]],[30,1,["params","peer"]]]]]],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,5],null,[["@name"],["error"]],[["default"],[[[[1,"\\n "],[8,[39,6],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n\\n "],[8,[39,5],null,[["@name"],["disconnected"]],[["default"],[[[[1,"\\n"],[41,[28,[37,8],[[30,2,["error","status"]],"404"],null],[[[1," "],[8,[39,9],[[4,[38,10],null,[["sticky"],[true]]]],[["@color"],["warning"]],[["default"],[[[[1,"\\n "],[8,[30,4,["Title"]],null,null,[["default"],[[[[1,"Warning!"]],[]]]]],[1,"\\n "],[8,[30,4,["Description"]],null,null,[["default"],[[[[1,"\\n This service has been deregistered and no longer exists in the catalog.\\n "]],[]]]]],[1,"\\n "]],[4]]]]],[1,"\\n"]],[]],[[[41,[28,[37,8],[[30,2,["error","status"]],"403"],null],[[[1," "],[8,[39,9],[[4,[38,10],null,[["sticky"],[true]]]],[["@color"],["critical"]],[["default"],[[[[1,"\\n "],[8,[30,5,["Title"]],null,null,[["default"],[[[[1,"Error!"]],[]]]]],[1,"\\n "],[8,[30,5,["Description"]],null,null,[["default"],[[[[1,"\\n You no longer have access to this service.\\n "]],[]]]]],[1,"\\n "]],[5]]]]],[1,"\\n"]],[]],[[[1," "],[8,[39,9],[[4,[38,10],null,[["sticky"],[true]]]],[["@color"],["critical"]],[["default"],[[[[1,"\\n "],[8,[30,6,["Title"]],null,null,[["default"],[[[[1,"Warning!"]],[]]]]],[1,"\\n "],[8,[30,6,["Description"]],null,null,[["default"],[[[[1,"\\n An error was returned whilst loading this data, refresh to try again.\\n "]],[]]]]],[1,"\\n "]],[6]]]]],[1,"\\n "]],[]]]],[]]],[1," "]],[3]]]]],[1,"\\n\\n "],[8,[39,5],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n"],[44,[[30,2,["data"]]],[[[41,[30,7,["IsOrigin"]],[[[1," "],[8,[39,12],null,[["@src","@onchange"],[[28,[37,3],["/${partition}/${nspace}/${dc}/proxy-instance/${id}/${node}/${name}",[28,[37,4],null,[["partition","nspace","dc","id","node","name"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]],[30,1,["params","id"]],[30,1,["params","node"]],[30,1,["params","name"]]]]]],null],[28,[37,13],[[30,0],[28,[37,14],[[33,15]],null]],[["value"],["data"]]]]],[["default"],[[[[1,"\\n"],[41,[30,8,["data","ServiceID"]],[[[1," "],[8,[39,12],null,[["@src","@onchange"],[[28,[37,3],["/${partition}/${nspace}/${dc}/service-instance/${id}/${node}/${name}/${peer}",[28,[37,4],null,[["partition","nspace","dc","id","node","name","peer"],[[30,1,["params","partition"]],[30,1,["params","nspace"]],[30,1,["params","dc"]],[30,8,["data","ServiceID"]],[30,8,["data","NodeName"]],[30,8,["data","ServiceName"]],[30,1,["params","peer"]]]]]],null],[28,[37,13],[[30,0],[28,[37,14],[[33,16]],null]],[["value"],["data"]]]]],null],[1,"\\n"]],[]],null],[1," "]],[8]]]]],[1,"\\n"]],[]],null],[1," "],[8,[39,17],null,null,[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@name"],["breadcrumbs"]],[["default"],[[[[1,"\\n "],[10,"ol"],[12],[1,"\\n "],[10,"li"],[12],[10,3],[15,6,[28,[37,18],["dc.services"],[["params"],[[28,[37,4],null,[["peer"],[[27]]]]]]]],[12],[1,"All Services"],[13],[13],[1,"\\n "],[10,"li"],[12],[11,3],[16,6,[28,[37,18],["dc.services.show"],null]],[4,[38,19],[[28,[37,20],["Service (",[30,7,["Service","Service"]],")"],null]],null],[12],[1,"\\n Service ("],[1,[30,7,["Service","Service"]]],[1,")\\n "],[13],[13],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h1"],[12],[1,"\\n "],[8,[30,1,["Title"]],null,[["@title"],[[30,7,["Service","ID"]]]],null],[1,"\\n "],[13],[1,"\\n "],[8,[39,21],null,[["@item","@withInfo"],[[30,7],true]],null],[1,"\\n "],[8,[39,22],null,[["@item","@withInfo"],[[30,7],true]],null],[1,"\\n"],[41,[28,[37,8],[[33,15,["ServiceProxy","Mode"]],"transparent"],null],[[[1," "],[8,[39,23],null,null,null],[1,"\\n"]],[]],null],[1," "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["nav"]],[["default"],[[[[1,"\\n "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,"Service Name"],[13],[1,"\\n "],[10,"dd"],[12],[10,3],[15,6,[29,[[28,[37,18],["dc.services.show",[30,7,["Service","Service"]]],null]]]],[12],[1,[30,7,["Service","Service"]]],[13],[13],[1,"\\n "],[13],[1,"\\n"],[41,[51,[30,7,["Node","Meta","synthetic-node"]]],[[[1," "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,"Node Name"],[13],[1,"\\n "],[10,"dd"],[12],[10,3],[15,6,[29,[[28,[37,18],["dc.nodes.show",[30,7,["Node","Node"]]],null]]]],[12],[1,[30,7,["Node","Node"]]],[13],[13],[1,"\\n "],[13],[1,"\\n"]],[]],null],[41,[30,7,["Service","PeerName"]],[[[1," "],[10,"dl"],[12],[1,"\\n "],[10,"dt"],[12],[1,"Peer Name"],[13],[1,"\\n "],[10,"dd"],[12],[10,3],[15,6,[28,[37,18],["dc.peers.show",[30,7,["Service","PeerName"]]],[["params"],[[28,[37,4],null,[["peer"],[[27]]]]]]]],[12],[1,[30,7,["Service","PeerName"]]],[13],[13],[1,"\\n "],[13],[1,"\\n"]],[]],null],[1," "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["actions"]],[["default"],[[[[1,"\\n"],[44,[[28,[37,25],[[30,7,["Service","Address"]],[30,7,["Node","Address"]]],null]],[[[1," "],[8,[39,26],null,[["@value","@name"],[[30,9],"Address"]],[["default"],[[[[1,[30,9]]],[]]]]],[1,"\\n"]],[9]]],[1," "]],[]]]]],[1,"\\n "],[8,[39,5],null,[["@name"],["content"]],[["default"],[[[[1,"\\n "],[8,[39,27],null,[["@items"],[[28,[37,28],[[28,[37,29],[[28,[37,4],null,[["label","href","selected"],["Health Checks",[28,[37,18],["dc.services.instance.healthchecks"],null],[28,[37,30],["dc.services.instance.healthchecks"],null]]]],[52,[28,[37,8],[[30,7,["Service","Kind"]],"mesh-gateway"],null],[28,[37,4],null,[["label","href","selected"],["Addresses",[28,[37,18],["dc.services.instance.addresses"],null],[28,[37,30],["dc.services.instance.addresses"],null]]]]],[52,[33,16],[28,[37,4],null,[["label","href","selected"],["Upstreams",[28,[37,18],["dc.services.instance.upstreams"],null],[28,[37,30],["dc.services.instance.upstreams"],null]]]]],[52,[33,16],[28,[37,4],null,[["label","href","selected"],["Exposed Paths",[28,[37,18],["dc.services.instance.exposedpaths"],null],[28,[37,30],["dc.services.instance.exposedpaths"],null]]]]],[28,[37,4],null,[["label","href","selected"],["Tags & Meta",[28,[37,18],["dc.services.instance.metadata"],null],[28,[37,30],["dc.services.instance.metadata"],null]]]]],null]],null]]],null],[1,"\\n "],[8,[39,31],null,[["@name","@model"],[[99,1,["@name"]],[28,[37,32],[[28,[37,4],null,[["proxy","meta","item"],[[33,16],[33,15],[30,7]]]],[30,1,["model"]]],null]]],[["default"],[[[[1,"\\n "],[46,[28,[37,34],null,null],null,null,null],[1,"\\n "]],[10]]]]],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[7]]],[1," "]],[]]]]],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]]],["route","loader","after","T","T","T","item","meta","address","o"],false,["route","routeName","data-loader","uri","hash","block-slot","app-error","if","eq","hds/toast","notification","let","data-source","action","mut","meta","proxy","app-view","href-to","tooltip","concat","consul/external-source","consul/kind","consul/transparent-proxy","unless","or","consul-copy-button","tab-nav","compact","array","is-href","outlet","assign","component","-outlet"]]',moduleName:"consul-ui/templates/dc/services/instance.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/dc/services/instance/addresses",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 @@ -4029,14 +4043,16 @@ e.default=n})),define("consul-ui/templates/index",["exports","@ember/template-fa var n=(0,t.createTemplateFactory)({id:"CJ8DiXUS",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@name","@model"],[[99,1,["@name"]],[30,1,["model"]]]],[["default"],[[[[1,"\\n "],[46,[28,[37,4],null,null],null,null,null],[1,"\\n "]],[2]]]]],[1,"\\n"]],[1]]]]],[1,"\\n"]],["route","o"],false,["route","routeName","outlet","component","-outlet"]]',moduleName:"consul-ui/templates/index.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/loading",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"5kbK/dah",block:'[[[1,"\\n"]],[],false,[]]',moduleName:"consul-ui/templates/loading.hbs",isStrictMode:!1}) -e.default=n})),define("consul-ui/templates/notfound",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=n})) +define("consul-ui/templates/notfound",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"qt+16XBx",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@login","@error"],[[30,1,["model","app","login","open"]],[28,[37,3],null,[["status","message"],[404,"Unable to find that page"]]]]],null],[1,"\\n"]],[1]]]]],[1,"\\n\\n"]],["route"],false,["route","routeName","app-error","hash"]]',moduleName:"consul-ui/templates/notfound.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/oauth-provider-debug",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"Lxy3wDlI",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n"],[10,0],[14,5,"width: 50%;margin: 0 auto;"],[12],[1,"\\n "],[10,"h1"],[12],[8,[30,1,["Title"]],null,[["@title"],["Mock OAuth Provider"]],null],[13],[1,"\\n "],[10,"main"],[12],[1,"\\n "],[10,"form"],[14,"method","GET"],[15,"action",[36,2]],[12],[1,"\\n"],[44,[[28,[37,4],null,[["state","code"],["state-123456789/abcdefghijklmnopqrstuvwxyz","code-abcdefghijklmnopqrstuvwxyz/123456789"]]]],[[[1," "],[8,[39,5],null,[["@name","@label","@item","@help"],["state","State",[30,2],"The OIDC state value that will get passed through to Consul"]],null],[1,"\\n "],[8,[39,5],null,[["@name","@label","@item","@help"],["code","Code",[30,2],"The OIDC code value that will get passed through to Consul"]],null],[1,"\\n"]],[2]]],[1," "],[8,[39,6],null,[["@type"],["submit"]],[["default"],[[[[1,"\\n Login\\n "]],[]]]]],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n"],[13],[1,"\\n"]],[1]]]]],[1,"\\n"]],["route","item"],false,["route","routeName","redirect_uri","let","hash","text-input","action"]]',moduleName:"consul-ui/templates/oauth-provider-debug.hbs",isStrictMode:!1}) e.default=n})),define("consul-ui/templates/settings",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=(0,t.createTemplateFactory)({id:"GXqC0vCH",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n "],[8,[39,2],null,[["@src"],[[28,[37,3],["settings://consul:client"],null]]],[["default"],[[[[1,"\\n\\n "],[8,[39,4],null,[["@name"],["error"]],[["default"],[[[[1,"\\n "],[8,[39,5],null,[["@error","@login"],[[30,2,["error"]],[30,1,["model","app","login","open"]]]],null],[1,"\\n "]],[]]]]],[1,"\\n\\n "],[8,[39,4],null,[["@name"],["loaded"]],[["default"],[[[[1,"\\n"],[44,[[28,[37,7],[[30,2,["data"]],[28,[37,8],null,[["blocking"],[true]]]],null]],[[[1," "],[8,[39,9],null,null,[["default"],[[[[1,"\\n "],[8,[39,4],null,[["@name"],["header"]],[["default"],[[[[1,"\\n "],[10,"h1"],[12],[1,"\\n "],[8,[30,1,["Title"]],null,[["@title"],["Settings"]],null],[1,"\\n "],[13],[1,"\\n "]],[]]]]],[1,"\\n "],[8,[39,4],null,[["@name"],["content"]],[["default"],[[[[1,"\\n "],[8,[39,10],[[24,0,"mb-3 mt-2"]],[["@type"],["inline"]],[["default"],[[[[1,"\\n "],[8,[30,4,["Title"]],null,null,[["default"],[[[[1,"Local Storage"]],[]]]]],[1,"\\n "],[8,[30,4,["Description"]],null,null,[["default"],[[[[1,"These settings are immediately saved to local storage and persisted through browser usage."]],[]]]]],[1,"\\n "]],[4]]]]],[1,"\\n "],[10,"form"],[12],[1,"\\n"],[41,[28,[37,12],[[28,[37,13],["CONSUL_UI_DISABLE_REALTIME"],null]],null],[[[1," "],[8,[39,14],null,null,[["default"],[[[[1,"\\n "],[8,[30,5,["Details"]],null,null,[["default"],[[[[1,"\\n "],[8,[39,15],null,[["@data","@sink","@onchange"],[[30,3],"settings://consul:client",[28,[37,16],[[30,0],[28,[37,17],[[30,5,["close"]]],null]],null]]],null],[1,"\\n "]],[]]]]],[1,"\\n "],[10,"fieldset"],[12],[1,"\\n "],[10,"h2"],[12],[1,"Blocking Queries"],[13],[1,"\\n "],[10,2],[12],[1,"Keep catalog info up-to-date without refreshing the page. Any changes made to services, nodes and intentions would be reflected in real time."],[13],[1,"\\n "],[10,0],[14,0,"type-toggle"],[12],[1,"\\n "],[10,"label"],[12],[1,"\\n "],[11,"input"],[24,3,"client[blocking]"],[16,"checked",[52,[30,3,["blocking"]],"checked"]],[24,4,"checkbox"],[4,[38,18],["change",[28,[37,19],[[28,[37,20],[[30,3],"blocking",[28,[37,12],[[30,3,["blocking"]]],null]],null],[28,[37,17],[[30,5,["open"]]],null]],null]],null],[12],[13],[1,"\\n "],[10,1],[12],[1,[52,[30,3,["blocking"]],"On","Off"]],[13],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "]],[5]]]]],[1,"\\n"]],[]],null],[1," "],[13],[1,"\\n "]],[]]]]],[1,"\\n "]],[]]]]],[1,"\\n"]],[3]]],[1," "]],[]]]]],[1,"\\n"]],[2]]]]],[1,"\\n"]],[1]]]]],[1,"\\n"]],["route","loader","item","A","disclosure"],false,["route","routeName","data-loader","uri","block-slot","app-error","let","or","hash","app-view","hds/alert","if","not","env","disclosure","data-sink","action","fn","on","queue","set"]]',moduleName:"consul-ui/templates/settings.hbs",isStrictMode:!1}) -e.default=n})) -define("consul-ui/transforms/array",["exports","ember-data-model-fragments/transforms/array"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +e.default=n})),define("consul-ui/templates/unavailable",["exports","@ember/template-factory"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +var n=(0,t.createTemplateFactory)({id:"hP024biE",block:'[[[1,"\\n"],[8,[39,0],null,[["@name"],[[99,1,["@name"]]]],[["default"],[[[[1,"\\n"],[41,[28,[37,3],["CONSUL_V2_CATALOG_ENABLED"],null],[[[1," "],[10,0],[14,0,"h-screen w-full flex flex-col justify-center items-center"],[12],[1,"\\n "],[10,0],[12],[1,"\\n "],[10,"svg"],[14,"width","149"],[14,"height","39"],[14,"viewBox","0 0 149 39"],[14,"fill","none"],[14,"xmlns","http://www.w3.org/2000/svg","http://www.w3.org/2000/xmlns/"],[14,0,"mb-8"],[12],[1,"\\n "],[10,"g"],[14,"clip-path","url(#clip0_2336_115454)"],[12],[1,"\\n "],[10,"path"],[14,"d","M54.6631 13.8631C54.6631 9.36592 57.3284 6.74561 63.5515 6.74561C65.8492 6.7447 68.1385 7.01683 70.3697 7.55608L69.8428 11.578C67.8266 11.2067 65.7822 11.0028 63.7313 10.9686C60.4647 10.9686 59.3924 12.0715 59.3924 14.6919V24.1128C59.3924 26.727 60.4399 27.8361 63.7313 27.8361C65.7822 27.8018 67.8266 27.598 69.8428 27.2267L70.3697 31.2486C68.1385 31.7878 65.8492 32.06 63.5515 32.059C57.3532 32.059 54.6631 29.4387 54.6631 24.9415V13.8631Z"],[14,"fill","black"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M80.4668 32.0408C74.2003 32.0408 72.5205 28.6405 72.5205 24.9843V20.4262C72.5205 16.7394 74.2065 13.3452 80.473 13.3452C86.7395 13.3452 88.4193 16.7394 88.4193 20.4262V24.9843C88.4131 28.6405 86.7271 32.0408 80.4668 32.0408ZM80.4668 17.1782C78.0246 17.1782 77.0887 18.2507 77.0887 20.2799V25.1061C77.0887 27.1354 78.0246 28.2018 80.4668 28.2018C82.9089 28.2018 83.8387 27.1354 83.8387 25.1061V20.2799C83.8387 18.2812 82.9027 17.1782 80.4668 17.1782Z"],[14,"fill","black"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M102.328 31.6877V19.1345C102.328 18.1778 101.913 17.6964 100.865 17.6964C99.8178 17.6964 97.7661 18.3606 96.0988 19.2077V31.6877H91.5244V13.7172H95.0141L95.4542 15.2345C97.7321 14.1126 100.223 13.4712 102.768 13.3516C105.805 13.3516 106.896 15.4539 106.896 18.6592V31.6877H102.328Z"],[14,"fill","black"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M116.312 32.0412C114.172 32.0062 112.05 31.657 110.015 31.0052L110.634 27.5805C112.403 28.0763 114.231 28.3366 116.07 28.3544C118.097 28.3544 118.395 27.9096 118.395 26.5263C118.395 25.4233 118.172 24.8688 115.209 24.168C110.746 23.1016 110.219 21.9926 110.219 18.5252C110.219 14.9116 111.831 13.3272 117.037 13.3272C118.88 13.3221 120.716 13.5265 122.511 13.9366L122.108 17.5502C120.448 17.2493 118.768 17.0762 117.081 17.0322C115.091 17.0322 114.756 17.4771 114.756 18.5801C114.756 20.0182 114.868 20.1279 117.347 20.7555C122.442 22.084 122.926 22.7482 122.926 26.4349C122.913 29.9022 121.829 32.0412 116.312 32.0412Z"],[14,"fill","black"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M130.487 13.7168V26.2516C130.487 27.2084 130.897 27.6898 131.95 27.6898C133.004 27.6898 135.049 27.0255 136.711 26.1785V13.7168H141.285V31.6873H137.802L137.349 30.176C135.071 31.2988 132.58 31.9402 130.035 32.059C126.998 32.059 125.913 29.9566 125.913 26.7452V13.7168H130.487Z"],[14,"fill","black"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M145.221 31.6877V6.38037L149.795 5.771V31.6877H145.221Z"],[14,"fill","black"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M20.1451 38.75C16.9414 38.7517 13.7857 37.9848 10.9519 36.5157C8.1181 35.0466 5.69179 32.9197 3.88359 30.3197C2.0754 27.7198 0.939957 24.7252 0.575821 21.596C0.211686 18.4668 0.629862 15.2975 1.79405 12.3632C2.95824 9.42894 4.83326 6.81831 7.25637 4.75795C9.67949 2.69759 12.5775 1.24976 15.6988 0.540121C18.8201 -0.169518 22.0704 -0.119518 25.1675 0.685782C28.2647 1.49108 31.1152 3.02734 33.4716 5.16123L28.8166 9.9631C26.9587 8.31623 24.655 7.2343 22.1844 6.84834C19.7138 6.46239 17.1824 6.78896 14.8967 7.78851C12.6111 8.78806 10.6693 10.4177 9.30646 12.4801C7.94365 14.5424 7.21832 16.9491 7.21832 19.4084C7.21832 21.8678 7.94365 24.2744 9.30646 26.3367C10.6693 28.3991 12.6111 30.0288 14.8967 31.0283C17.1824 32.0279 19.7138 32.3544 22.1844 31.9685C24.655 31.5825 26.9587 30.5006 28.8166 28.8537L33.4716 33.6373C29.842 36.9293 25.0834 38.7549 20.1451 38.75Z"],[14,"fill","#E03875"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M35.9875 29.0672C35.6688 29.0672 35.3572 28.9743 35.0922 28.8002C34.8272 28.6261 34.6206 28.3786 34.4987 28.0891C34.3767 27.7996 34.3448 27.4811 34.4069 27.1737C34.4691 26.8664 34.6226 26.5841 34.848 26.3625C35.0734 26.1409 35.3605 25.99 35.6731 25.9289C35.9858 25.8677 36.3098 25.8991 36.6043 26.019C36.8987 26.139 37.1504 26.342 37.3275 26.6026C37.5046 26.8631 37.5991 27.1695 37.5991 27.4828C37.5991 27.903 37.4293 28.306 37.1271 28.6031C36.8249 28.9003 36.415 29.0672 35.9875 29.0672Z"],[14,"fill","#E03875"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M20.0203 23.5891C19.1769 23.5891 18.3524 23.3432 17.6511 22.8825C16.9498 22.4219 16.4032 21.7671 16.0805 21.001C15.7577 20.2349 15.6733 19.392 15.8378 18.5787C16.0023 17.7654 16.4085 17.0184 17.0049 16.4321C17.6013 15.8457 18.3611 15.4464 19.1884 15.2847C20.0156 15.1229 20.873 15.2059 21.6523 15.5232C22.4315 15.8406 23.0975 16.3779 23.5661 17.0674C24.0347 17.7568 24.2848 18.5674 24.2848 19.3966C24.2848 20.5085 23.8355 21.5749 23.0358 22.3611C22.236 23.1474 21.1513 23.5891 20.0203 23.5891Z"],[14,"fill","#E03875"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M37.8411 23.6497C37.5223 23.6497 37.2107 23.5568 36.9457 23.3827C36.6807 23.2086 36.4741 22.9612 36.3522 22.6716C36.2302 22.3821 36.1983 22.0636 36.2605 21.7562C36.3226 21.4489 36.4761 21.1666 36.7015 20.945C36.9269 20.7234 37.214 20.5725 37.5267 20.5114C37.8393 20.4503 38.1633 20.4816 38.4578 20.6016C38.7523 20.7215 39.004 20.9246 39.181 21.1851C39.3581 21.4457 39.4526 21.752 39.4526 22.0653C39.451 22.485 39.2807 22.8871 38.9788 23.1839C38.6769 23.4807 38.268 23.6481 37.8411 23.6497Z"],[14,"fill","#E03875"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M33.0432 23.4671C32.7245 23.4671 32.4129 23.3742 32.1479 23.2001C31.8828 23.026 31.6763 22.7785 31.5543 22.489C31.4323 22.1995 31.4004 21.881 31.4626 21.5736C31.5248 21.2663 31.6783 20.984 31.9037 20.7624C32.129 20.5408 32.4162 20.3899 32.7288 20.3288C33.0414 20.2677 33.3655 20.299 33.6599 20.4189C33.9544 20.5389 34.2061 20.7419 34.3832 21.0025C34.5603 21.263 34.6548 21.5694 34.6548 21.8827C34.6548 22.3029 34.485 22.7059 34.1828 23.003C33.8805 23.3002 33.4706 23.4671 33.0432 23.4671Z"],[14,"fill","#E03875"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M37.8411 18.3177C37.5223 18.3177 37.2107 18.2248 36.9457 18.0507C36.6807 17.8766 36.4741 17.6291 36.3522 17.3396C36.2302 17.0501 36.1983 16.7315 36.2605 16.4242C36.3226 16.1169 36.4761 15.8346 36.7015 15.613C36.9269 15.3914 37.214 15.2405 37.5267 15.1794C37.8393 15.1182 38.1633 15.1496 38.4578 15.2695C38.7523 15.3894 39.004 15.5925 39.181 15.8531C39.3581 16.1136 39.4526 16.4199 39.4526 16.7333C39.451 17.153 39.2807 17.5551 38.9788 17.8518C38.6769 18.1486 38.268 18.3161 37.8411 18.3177Z"],[14,"fill","#E03875"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M33.0432 18.5008C32.7245 18.5008 32.4129 18.4079 32.1479 18.2338C31.8828 18.0597 31.6763 17.8122 31.5543 17.5227C31.4323 17.2332 31.4004 16.9146 31.4626 16.6073C31.5248 16.3 31.6783 16.0177 31.9037 15.7961C32.129 15.5745 32.4162 15.4236 32.7288 15.3625C33.0414 15.3013 33.3655 15.3327 33.6599 15.4526C33.9544 15.5726 34.2061 15.7756 34.3832 16.0362C34.5603 16.2967 34.6548 16.603 34.6548 16.9164C34.6548 17.3366 34.485 17.7396 34.1828 18.0367C33.8805 18.3339 33.4706 18.5008 33.0432 18.5008Z"],[14,"fill","#E03875"],[12],[13],[1,"\\n "],[10,"path"],[14,"d","M36.0803 12.9856C35.7616 12.9856 35.45 12.8927 35.185 12.7186C34.92 12.5445 34.7134 12.2971 34.5914 12.0076C34.4694 11.7181 34.4375 11.3995 34.4997 11.0922C34.5619 10.7848 34.7154 10.5025 34.9408 10.2809C35.1662 10.0594 35.4533 9.90847 35.7659 9.84734C36.0785 9.7862 36.4026 9.81758 36.697 9.9375C36.9915 10.0574 37.2432 10.2605 37.4203 10.521C37.5974 10.7816 37.6919 11.0879 37.6919 11.4013C37.6919 11.8215 37.5221 12.2245 37.2199 12.5216C36.9176 12.8187 36.5077 12.9856 36.0803 12.9856Z"],[14,"fill","#E03875"],[12],[13],[1,"\\n "],[13],[1,"\\n "],[10,"defs"],[12],[1,"\\n "],[10,"clipPath"],[14,1,"clip0_2336_115454"],[12],[1,"\\n "],[10,"rect"],[14,"width","150"],[14,"height","39"],[14,"fill","white"],[12],[13],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n\\n "],[8,[39,4],null,null,[["default"],[[[[1,"\\n "],[8,[30,2,["Header"]],null,[["@title"],["User Interface Unavailable"]],null],[1,"\\n "],[8,[30,2,["Body"]],null,[["@text"],["The Consul v2 catalog API (beta) is enabled, and is not yet compatible with the Consul UI."]],null],[1,"\\n "],[8,[30,2,["Footer"]],null,[["@hasDivider"],[true]],[["default"],[[[[1,"\\n "],[8,[30,3,["Link::Standalone"]],null,[["@icon","@iconPosition","@isHrefExternal","@text","@href"],["docs-link","trailing",true,"Learn more","https://developer.hashicorp.com/consul/docs/architecture/catalog/v2#constraints-and-limitations"]],null],[1,"\\n "],[8,[30,3,["Link::Standalone"]],null,[["@icon","@iconPosition","@isHrefExternal","@text","@href"],["external-link","trailing",true,"Provide feedback","https://hashicorp.sjc1.qualtrics.com/jfe/form/SV_cHDaObigIBbQneC"]],null],[1,"\\n "]],[3]]]]],[1,"\\n "]],[2]]]]],[1,"\\n "],[13],[1,"\\n "],[13],[1,"\\n"]],[]],null]],[1]]]]]],["route","A","F"],false,["route","routeName","if","env","hds/application-state"]]',moduleName:"consul-ui/templates/unavailable.hbs",isStrictMode:!1}) +e.default=n})),define("consul-ui/transforms/array",["exports","ember-data-model-fragments/transforms/array"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=t.default e.default=n})),define("consul-ui/transforms/boolean",["exports","@ember-data/serializer/-private"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.BooleanTransform}})})),define("consul-ui/transforms/date",["exports","@ember-data/serializer/-private"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),Object.defineProperty(e,"default",{enumerable:!0,get:function(){return t.DateTransform}})})),define("consul-ui/transforms/fragment-array",["exports","ember-data-model-fragments/transforms/fragment-array"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 var n=t.default @@ -4177,7 +4193,8 @@ return null==e?[]:[e]},get NONE(){return 0},get CAPTURING_PHASE(){return 1},get e.stopped=!0,"function"==typeof e.event.stopPropagation&&e.event.stopPropagation()},stopImmediatePropagation(){const e=l(this) e.stopped=!0,e.immediateStopped=!0,"function"==typeof e.event.stopImmediatePropagation&&e.event.stopImmediatePropagation()},get bubbles(){return Boolean(l(this).event.bubbles)},get cancelable(){return Boolean(l(this).event.cancelable)},preventDefault(){r(l(this))},get defaultPrevented(){return l(this).canceled},get composed(){return Boolean(l(this).event.composed)},get timeStamp(){return l(this).timeStamp},get srcElement(){return l(this).eventTarget},get cancelBubble(){return l(this).stopped},set cancelBubble(e){if(!e)return const t=l(this) -t.stopped=!0,"boolean"==typeof t.event.cancelBubble&&(t.event.cancelBubble=!0)},get returnValue(){return!l(this).canceled},set returnValue(e){e||r(l(this))},initEvent(){}},Object.defineProperty(i.prototype,"constructor",{value:i,configurable:!0,writable:!0}),"undefined"!=typeof window&&void 0!==window.Event&&(Object.setPrototypeOf(i.prototype,window.Event.prototype),n.set(window.Event.prototype,i))})),define("consul-ui/utils/dom/event-target/rsvp",["exports","rsvp","consul-ui/utils/dom/event-target/event-target-shim/event"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +t.stopped=!0,"boolean"==typeof t.event.cancelBubble&&(t.event.cancelBubble=!0)},get returnValue(){return!l(this).canceled},set returnValue(e){e||r(l(this))},initEvent(){}},Object.defineProperty(i.prototype,"constructor",{value:i,configurable:!0,writable:!0}),"undefined"!=typeof window&&void 0!==window.Event&&(Object.setPrototypeOf(i.prototype,window.Event.prototype),n.set(window.Event.prototype,i))})) +define("consul-ui/utils/dom/event-target/rsvp",["exports","rsvp","consul-ui/utils/dom/event-target/event-target-shim/event"],(function(e,t,n){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 const l=function(){} l.prototype=Object.assign(Object.create(Object.prototype,{constructor:{value:l,configurable:!0,writable:!0}}),{dispatchEvent:function(e){const t=(0,n.wrapEvent)(this,e);(0,n.setCurrentTarget)(t,null) const l=e.type,r=t @@ -4201,8 +4218,7 @@ if("object"!=typeof l)return l throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string") return"symbol"==typeof t?t:String(t)}(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e,t){let l=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{} if(void 0!==e.target)return e -return{target:n(n({},l),{name:e,value:t})}}})) -define("consul-ui/utils/dom/qsa-factory",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:document +return{target:n(n({},l),{name:e,value:t})}}})),define("consul-ui/utils/dom/qsa-factory",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:document return function(t){return(arguments.length>1&&void 0!==arguments[1]?arguments[1]:e).querySelectorAll(t)}}})),define("consul-ui/utils/dom/sibling",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e,t){let n=e for(;n=n.nextSibling;)if(1===n.nodeType&&n.nodeName.toLowerCase()===t)return n}})),define("consul-ui/utils/editor/lint",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.createLoader=void 0,e.default=function(e,t){n(e,t,(function(){e.getValue().trim().length&&e.performLint()}))} const t=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:document.getElementsByTagName.bind(document),t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:CodeMirror @@ -4275,7 +4291,7 @@ case"CONSUL_UI_CONFIG":return r={service:void 0},i=t("CONSUL_METRICS_PROVIDER"), case"CONSUL_BASE_UI_URL":return c.split("/").slice(0,-2).join("/") case"CONSUL_HTTP_PROTOCOL":return void 0===d&&(d=function(e){try{return n.performance.getEntriesByType("resource").find((t=>"script"===t.initiatorType&&e===t.name))||{}}catch(t){return{}}}(c)),d.nextHopProtocol||"http/1.1" case"CONSUL_HTTP_MAX_CONNECTIONS":switch(l=t("CONSUL_HTTP_PROTOCOL"),!0){case 0===l.indexOf("h2"):case 0===l.indexOf("hq"):case 0===l.indexOf("spdy"):return -default:return 5}}},f=function(t){let n={} +default:return 5}case"CONSUL_V2_CATALOG_ENABLED":return"undefined"!==a.V2CatalogEnabled&&a.V2CatalogEnabled}},f=function(t){let n={} switch(e.environment){case"development":case"staging":case"coverage":case"test":n=i().reduce((function(e,t){let[n,l]=t switch(n){case"CONSUL_INTL_LOCALE":e.CONSUL_INTL_LOCALE=String(l).toLowerCase() break @@ -4301,6 +4317,8 @@ case"CONSUL_UI_CONFIG":e.CONSUL_UI_CONFIG=JSON.parse(l) break case"TokenSecretID":e.CONSUL_HTTP_TOKEN=l break +case"CONSUL_V2_CATALOG_ENABLE":e.CONSUL_V2_CATALOG_ENABLED=JSON.parse(l) +break default:e[n]=l}return e}),{}) break case"production":n=i().reduce((function(e,t){let[n,l]=t @@ -4308,7 +4326,7 @@ if("TokenSecretID"===n)e.CONSUL_HTTP_TOKEN=l return e}),{})}return void 0!==n[t]?n[t]:e[t]} return function e(t){switch(t){case"CONSUL_UI_DISABLE_REALTIME":case"CONSUL_UI_DISABLE_ANCHOR_SELECTION":return!!JSON.parse(String(o(t)||0).toLowerCase())||f(t) case"CONSUL_UI_REALTIME_RUNNER":return o(t)||f(t) -case"CONSUL_UI_CONFIG":case"CONSUL_DATACENTER_LOCAL":case"CONSUL_DATACENTER_PRIMARY":case"CONSUL_HCP_MANAGED_RUNTIME":case"CONSUL_API_PREFIX":case"CONSUL_HCP_URL":case"CONSUL_ACLS_ENABLED":case"CONSUL_NSPACES_ENABLED":case"CONSUL_PEERINGS_ENABLED":case"CONSUL_AGENTLESS_ENABLED":case"CONSUL_HCP_ENABLED":case"CONSUL_SSO_ENABLED":case"CONSUL_PARTITIONS_ENABLED":case"CONSUL_METRICS_PROVIDER":case"CONSUL_METRICS_PROXY_ENABLE":case"CONSUL_SERVICE_DASHBOARD_URL":case"CONSUL_BASE_UI_URL":case"CONSUL_HTTP_PROTOCOL":case"CONSUL_HTTP_MAX_CONNECTIONS":{const n=f(t) +case"CONSUL_UI_CONFIG":case"CONSUL_DATACENTER_LOCAL":case"CONSUL_DATACENTER_PRIMARY":case"CONSUL_HCP_MANAGED_RUNTIME":case"CONSUL_API_PREFIX":case"CONSUL_HCP_URL":case"CONSUL_ACLS_ENABLED":case"CONSUL_NSPACES_ENABLED":case"CONSUL_PEERINGS_ENABLED":case"CONSUL_AGENTLESS_ENABLED":case"CONSUL_HCP_ENABLED":case"CONSUL_SSO_ENABLED":case"CONSUL_PARTITIONS_ENABLED":case"CONSUL_METRICS_PROVIDER":case"CONSUL_METRICS_PROXY_ENABLE":case"CONSUL_SERVICE_DASHBOARD_URL":case"CONSUL_BASE_UI_URL":case"CONSUL_V2_CATALOG_ENABLED":case"CONSUL_HTTP_PROTOCOL":case"CONSUL_HTTP_MAX_CONNECTIONS":{const n=f(t) return void 0!==n?n:p(t,e)}default:return f(t)}}}})),define("consul-ui/utils/get-form-name-property",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e){if(-1!==e.indexOf("["))return e.match(/(.*)\[(.*)\]/).slice(1) return["",e]}})),define("consul-ui/utils/helpers/call-if-type",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e){return function(t){return function(n){let l=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{} return typeof n[0]!==e?n[0]:t(n[0],l)}}}})),define("consul-ui/utils/http/consul",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.HEADERS_TOKEN=e.HEADERS_SYMBOL=e.HEADERS_PARTITION=e.HEADERS_NAMESPACE=e.HEADERS_INDEX=e.HEADERS_DIGEST=e.HEADERS_DEFAULT_ACL_POLICY=e.HEADERS_DATACENTER=void 0 @@ -4383,14 +4401,14 @@ return 0===e.indexOf(t)?e.substr(t.length):e}})),define("consul-ui/utils/maybe-c e.default=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:[],l=arguments.length>1&&void 0!==arguments[1]&&arguments[1] const r=new(arguments.length>2&&void 0!==arguments[2]?arguments[2]:n.default),i=e.shift().map((e=>(""===e.ServiceName&&r.set(e.Node,e.CheckID),e))).concat(e.reduce(((e,t)=>void 0===t?e:e.concat(t.reduce(((e,t)=>{if(""===t.ServiceName){if((r.get(t.Node)||[]).includes(t.CheckID))return e r.set(t.Node,t.CheckID)}return e.push(t),e}),[]))),[])) -return l&&i.filter((e=>(0,t.get)(e,"Exposable"))).forEach((e=>{(0,t.set)(e,"Exposed",l)})),i}})),define("consul-ui/utils/minimizeModel",["exports","@ember/object"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e){if(Array.isArray(e))return e.filter((function(e){return!(0,t.get)(e,"isNew")})).map((function(e){return{ID:(0,t.get)(e,"ID"),Name:(0,t.get)(e,"Name")}}))}})),define("consul-ui/utils/non-empty-set",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e){return function(t){return null==t||""===t?{}:{[e]:t}}}})),define("consul-ui/utils/path/resolve",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 +return l&&i.filter((e=>(0,t.get)(e,"Exposable"))).forEach((e=>{(0,t.set)(e,"Exposed",l)})),i}})),define("consul-ui/utils/minimizeModel",["exports","@ember/object"],(function(e,t){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e){if(Array.isArray(e))return e.filter((function(e){return!(0,t.get)(e,"isNew")})).map((function(e){return{ID:(0,t.get)(e,"ID"),Name:(0,t.get)(e,"Name")}}))}})) +define("consul-ui/utils/non-empty-set",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e){return function(t){return null==t||""===t?{}:{[e]:t}}}})),define("consul-ui/utils/path/resolve",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 e.default=(e,t)=>0===t.indexOf("/")?t:t.split("/").reduce(((e,t,n,l)=>("."!==t&&(".."===t?e.pop():""===t&&n!==l.length-1||e.push(t)),e)),e.split("/")).join("/")})),define("consul-ui/utils/promisedTimeout",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Promise,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:setTimeout return function(n){let l=arguments.length>1&&void 0!==arguments[1]?arguments[1]:function(){} return new e(((e,r)=>{l(t((function(){e(n)}),n))}))}}})),define("consul-ui/utils/right-trim",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"" const n=e.length-t.length if(n>=0)return e.lastIndexOf(t)===n?e.substr(0,n):e -return e}})) -define("consul-ui/utils/routing/redirect-to",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e,t){return function(t,n){const l=this.routeName.split(".").slice(0,-1).join(".") +return e}})),define("consul-ui/utils/routing/redirect-to",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e,t){return function(t,n){const l=this.routeName.split(".").slice(0,-1).join(".") this.replaceWith(`${l}.${e}`,t)}}})),define("consul-ui/utils/routing/transitionable",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=function(e){let l=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=arguments.length>2?arguments[2]:void 0 null===e&&(e=r.lookup("route:application")) let i,o=n(e,l),a=e @@ -4462,4 +4480,4 @@ let l=e.split("."),r=l.pop(),i=l.join("."),u=(0,t.get)(o,i) return u&&u.hasOwnProperty&&u.hasOwnProperty(r)?n:(0,t.get)(a,e)}return o.hasOwnProperty(e)?(0,t.get)(o,e):(0,t.get)(a,e)}} return!n.call(u,o,a)||e(l,r,i,o,a)}}(e)}})),define("consul-ui/validations/token",["exports"],(function(e){Object.defineProperty(e,"__esModule",{value:!0}),e.default=void 0 e.default={}})),define("consul-ui/config/environment",[],(function(){try{var e="consul-ui/config/environment",t=document.querySelector('meta[name="'+e+'"]').getAttribute("content"),n={default:JSON.parse(decodeURIComponent(t))} -return Object.defineProperty(n,"__esModule",{value:!0}),n}catch(l){throw new Error('Could not read config from meta tag with name "'+e+'".')}})),runningTests||require("consul-ui/app").default.create({name:"consul-ui",version:"2.2.0+85171f50"}) +return Object.defineProperty(n,"__esModule",{value:!0}),n}catch(l){throw new Error('Could not read config from meta tag with name "'+e+'".')}})),runningTests||require("consul-ui/app").default.create({name:"consul-ui",version:"2.2.0+3c24c491"}) diff --git a/agent/uiserver/dist/assets/consul-ui-ebf15e0a88f8aa4b2e353442d718d20d.css b/agent/uiserver/dist/assets/consul-ui-ebf15e0a88f8aa4b2e353442d718d20d.css deleted file mode 100644 index 6e8358ba96..0000000000 --- a/agent/uiserver/dist/assets/consul-ui-ebf15e0a88f8aa4b2e353442d718d20d.css +++ /dev/null @@ -1,2 +0,0 @@ -@charset "UTF-8";/*! tailwindcss v3.2.7 | MIT License | https://tailwindcss.com - */.hds-table,table{border-spacing:0}input[type=checkbox],input[type=radio],progress,sub,sup{vertical-align:baseline}.hover\:scale-125:hover,.scale-100,.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.ease-in-out,.transition{transition-timing-function:cubic-bezier(.4,0,.2,1)}*,::after,::before{border-width:0;border-style:solid;border-color:currentColor}html{line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-feature-settings:normal}a,hr{color:inherit}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto}[hidden]{display:none}*,::after,::backdrop,::before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-scroll-snap-strictness:proximity;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgb(59 130 246 / 0.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000}.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.hds-side-nav,.sticky{position:sticky}.bottom-0{bottom:0}.isolate{isolation:isolate}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-3{margin-bottom:.75rem}.mb-6{margin-bottom:1.5rem}.ml-4{margin-left:1rem}.mr-0{margin-right:0}.mr-0\.5{margin-right:.125rem}.mr-1{margin-right:.25rem}.mr-1\.5{margin-right:.375rem}.mr-2{margin-right:.5rem}.mr-2\.5{margin-right:.625rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.contents{display:contents}.hidden{display:none}.h-12{height:3rem}.h-16{height:4rem}.h-24{height:6rem}.h-4{height:1rem}.h-48{height:12rem}.h-8{height:2rem}.h-full{height:100%}.\!w-80{width:20rem!important}.w-24{width:6rem}.w-4{width:1rem}.w-8{width:2rem}.w-full{width:100%}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.flex-col,.hds-accordion{flex-direction:column}.scale-100{--tw-scale-x:1;--tw-scale-y:1}.cursor-pointer{cursor:pointer}.resize{resize:both}.flex-nowrap{flex-wrap:nowrap}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-1\.5{gap:.375rem}.space-x-12>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(3rem * var(--tw-space-x-reverse));margin-left:calc(3rem * calc(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.overflow-x-auto{overflow-x:auto}.overflow-y-scroll{overflow-y:scroll}.hds-breadcrumb__text,.hds-card__container--overflow-hidden,.truncate{overflow:hidden}.truncate{text-overflow:ellipsis;white-space:nowrap}.rounded{border-radius:.25rem}.border{border-width:1px}.p-6{padding:1.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize,.type-source.popover-select li:not(.partition) button{text-transform:capitalize}.underline{text-decoration-line:underline}.opacity-0{opacity:0}.shadow{--tw-shadow:0 1px 3px 0 rgb(0 0 0 / 0.1),0 1px 2px -1px rgb(0 0 0 / 0.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.hds-elevation-inset,.hds-form-checkbox:not(:checked,:indeterminate){box-shadow:var(--token-elevation-inset-box-shadow)}.outline{outline-style:solid}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:150ms}.consul-surface-nav{background:var(--token-color-palette-neutral-700)}:root{--token-color-palette-blue-500:#1c345f;--token-color-palette-blue-400:#0046d1;--token-color-palette-blue-300:#0c56e9;--token-color-palette-blue-200:#1060ff;--token-color-palette-blue-100:#cce3fe;--token-color-palette-blue-50:#f2f8ff;--token-color-palette-purple-500:#42215b;--token-color-palette-purple-400:#7b00db;--token-color-palette-purple-300:#911ced;--token-color-palette-purple-200:#a737ff;--token-color-palette-purple-100:#ead2fe;--token-color-palette-purple-50:#f9f2ff;--token-color-palette-green-500:#054220;--token-color-palette-green-400:#006619;--token-color-palette-green-300:#00781e;--token-color-palette-green-200:#008a22;--token-color-palette-green-100:#cceeda;--token-color-palette-green-50:#f2fbf6;--token-color-palette-amber-500:#542800;--token-color-palette-amber-400:#803d00;--token-color-palette-amber-300:#9e4b00;--token-color-palette-amber-200:#bb5a00;--token-color-palette-amber-100:#fbeabf;--token-color-palette-amber-50:#fff9e8;--token-color-palette-red-500:#51130a;--token-color-palette-red-400:#940004;--token-color-palette-red-300:#c00005;--token-color-palette-red-200:#e52228;--token-color-palette-red-100:#fbd4d4;--token-color-palette-red-50:#fff5f5;--token-color-palette-neutral-700:#0c0c0e;--token-color-palette-neutral-600:#3b3d45;--token-color-palette-neutral-500:#656a76;--token-color-palette-neutral-400:#8c909c;--token-color-palette-neutral-300:#c2c5cb;--token-color-palette-neutral-200:#dedfe3;--token-color-palette-neutral-100:#f1f2f3;--token-color-palette-neutral-50:#fafafa;--token-color-palette-neutral-0:#ffffff;--token-color-palette-alpha-300:#3b3d4566;--token-color-palette-alpha-200:#656a7633;--token-color-palette-alpha-100:#656a761a;--token-color-border-primary:#656a7633;--token-color-border-faint:#656a761a;--token-color-border-strong:#3b3d4566;--token-color-border-action:#cce3fe;--token-color-border-highlight:#ead2fe;--token-color-border-success:#cceeda;--token-color-border-warning:#fbeabf;--token-color-border-critical:#fbd4d4;--token-color-focus-action-internal:#0c56e9;--token-color-focus-action-external:#5990ff;--token-color-focus-critical-internal:#c00005;--token-color-focus-critical-external:#dd7578;--token-color-foreground-strong:#0c0c0e;--token-color-foreground-primary:#3b3d45;--token-color-foreground-faint:#656a76;--token-color-foreground-high-contrast:#ffffff;--token-color-foreground-disabled:#8c909c;--token-color-foreground-action:#1060ff;--token-color-foreground-action-hover:#0c56e9;--token-color-foreground-action-active:#0046d1;--token-color-foreground-highlight:#a737ff;--token-color-foreground-highlight-on-surface:#911ced;--token-color-foreground-highlight-high-contrast:#42215b;--token-color-foreground-success:#008a22;--token-color-foreground-success-on-surface:#00781e;--token-color-foreground-success-high-contrast:#054220;--token-color-foreground-warning:#bb5a00;--token-color-foreground-warning-on-surface:#9e4b00;--token-color-foreground-warning-high-contrast:#542800;--token-color-foreground-critical:#e52228;--token-color-foreground-critical-on-surface:#c00005;--token-color-foreground-critical-high-contrast:#51130a;--token-color-page-primary:#ffffff;--token-color-page-faint:#fafafa;--token-color-surface-primary:#ffffff;--token-color-surface-faint:#fafafa;--token-color-surface-strong:#f1f2f3;--token-color-surface-interactive:#ffffff;--token-color-surface-interactive-hover:#f1f2f3;--token-color-surface-interactive-active:#dedfe3;--token-color-surface-interactive-disabled:#fafafa;--token-color-surface-action:#f2f8ff;--token-color-surface-highlight:#f9f2ff;--token-color-surface-success:#f2fbf6;--token-color-surface-warning:#fff9e8;--token-color-surface-critical:#fff5f5;--token-color-hashicorp-brand:#000000;--token-color-boundary-brand:#f24c53;--token-color-boundary-foreground:#cf2d32;--token-color-boundary-surface:#ffecec;--token-color-boundary-border:#fbd7d8;--token-color-boundary-gradient-primary-start:#f97076;--token-color-boundary-gradient-primary-stop:#db363b;--token-color-boundary-gradient-faint-start:#fffafa;--token-color-boundary-gradient-faint-stop:#ffecec;--token-color-consul-brand:#e03875;--token-color-consul-foreground:#d01c5b;--token-color-consul-surface:#ffe9f1;--token-color-consul-border:#ffcede;--token-color-consul-gradient-primary-start:#ff99be;--token-color-consul-gradient-primary-stop:#da306e;--token-color-consul-gradient-faint-start:#fff9fb;--token-color-consul-gradient-faint-stop:#ffe9f1;--token-color-hcp-brand:#000000;--token-color-nomad-brand:#06d092;--token-color-nomad-foreground:#008661;--token-color-nomad-surface:#d3fdeb;--token-color-nomad-border:#bff3dd;--token-color-nomad-gradient-primary-start:#bff3dd;--token-color-nomad-gradient-primary-stop:#60dea9;--token-color-nomad-gradient-faint-start:#f3fff9;--token-color-nomad-gradient-faint-stop:#d3fdeb;--token-color-packer-brand:#02a8ef;--token-color-packer-foreground:#007eb4;--token-color-packer-surface:#d4f2ff;--token-color-packer-border:#b4e4ff;--token-color-packer-gradient-primary-start:#b4e4ff;--token-color-packer-gradient-primary-stop:#63d0ff;--token-color-packer-gradient-faint-start:#f3fcff;--token-color-packer-gradient-faint-stop:#d4f2ff;--token-color-terraform-brand:#7b42bc;--token-color-terraform-foreground:#773cb4;--token-color-terraform-surface:#f4ecff;--token-color-terraform-border:#ebdbfc;--token-color-terraform-gradient-primary-start:#bb8deb;--token-color-terraform-gradient-primary-stop:#844fba;--token-color-terraform-gradient-faint-start:#fcfaff;--token-color-terraform-gradient-faint-stop:#f4ecff;--token-color-vagrant-brand:#1868f2;--token-color-vagrant-foreground:#1c61d8;--token-color-vagrant-surface:#d6ebff;--token-color-vagrant-border:#c7dbfc;--token-color-vagrant-gradient-primary-start:#c7dbfc;--token-color-vagrant-gradient-primary-stop:#7dadff;--token-color-vagrant-gradient-faint-start:#f4faff;--token-color-vagrant-gradient-faint-stop:#d6ebff;--token-color-vault-secrets-brand:#ffd814;--token-color-vault-secrets-brand-alt:#000000;--token-color-vault-secrets-foreground:#9a6f00;--token-color-vault-secrets-surface:#fff9cf;--token-color-vault-secrets-border:#feec7b;--token-color-vault-secrets-gradient-primary-start:#feec7b;--token-color-vault-secrets-gradient-primary-stop:#ffe543;--token-color-vault-secrets-gradient-faint-start:#fffdf2;--token-color-vault-secrets-gradient-faint-stop:#fff9cf;--token-color-vault-brand:#ffd814;--token-color-vault-brand-alt:#000000;--token-color-vault-foreground:#9a6f00;--token-color-vault-surface:#fff9cf;--token-color-vault-border:#feec7b;--token-color-vault-gradient-primary-start:#feec7b;--token-color-vault-gradient-primary-stop:#ffe543;--token-color-vault-gradient-faint-start:#fffdf2;--token-color-vault-gradient-faint-stop:#fff9cf;--token-color-waypoint-brand:#14c6cb;--token-color-waypoint-foreground:#008196;--token-color-waypoint-surface:#e0fcff;--token-color-waypoint-border:#cbf1f3;--token-color-waypoint-gradient-primary-start:#cbf1f3;--token-color-waypoint-gradient-primary-stop:#62d4dc;--token-color-waypoint-gradient-faint-start:#f6feff;--token-color-waypoint-gradient-faint-stop:#e0fcff;--token-elevation-inset-box-shadow:inset 0px 1px 2px 1px #656a761a;--token-elevation-low-box-shadow:0px 1px 1px 0px #656a760d,0px 2px 2px 0px #656a760d;--token-elevation-mid-box-shadow:0px 2px 3px 0px #656a761a,0px 8px 16px -10px #656a7633;--token-elevation-high-box-shadow:0px 2px 3px 0px #656a7626,0px 16px 16px -10px #656a7633;--token-elevation-higher-box-shadow:0px 2px 3px 0px #656a761a,0px 12px 28px 0px #656a7640;--token-elevation-overlay-box-shadow:0px 2px 3px 0px #3b3d4540,0px 12px 24px 0px #3b3d4559;--token-surface-inset-box-shadow:inset 0 0 0 1px #656a764d,inset 0px 1px 2px 1px #656a761a;--token-surface-base-box-shadow:0 0 0 1px #656a7633;--token-surface-low-box-shadow:0 0 0 1px #656a7626,0px 1px 1px 0px #656a760d,0px 2px 2px 0px #656a760d;--token-surface-mid-box-shadow:0 0 0 1px #656a7626,0px 2px 3px 0px #656a761a,0px 8px 16px -10px #656a7633;--token-surface-high-box-shadow:0 0 0 1px #656a7640,0px 2px 3px 0px #656a7626,0px 16px 16px -10px #656a7633;--token-surface-higher-box-shadow:0 0 0 1px #656a7633,0px 2px 3px 0px #656a761a,0px 12px 28px 0px #656a7640;--token-surface-overlay-box-shadow:0 0 0 1px #3b3d4540,0px 2px 3px 0px #3b3d4540,0px 12px 24px 0px #3b3d4559;--token-focus-ring-action-box-shadow:inset 0 0 0 1px #0c56e9,0 0 0 3px #5990ff;--token-focus-ring-critical-box-shadow:inset 0 0 0 1px #c00005,0 0 0 3px #dd7578;--token-form-label-color:#0c0c0e;--token-form-legend-color:#0c0c0e;--token-form-helper-text-color:#656a76;--token-form-indicator-optional-color:#656a76;--token-form-error-color:#c00005;--token-form-error-icon-size:14px;--token-form-checkbox-size:16px;--token-form-checkbox-border-radius:3px;--token-form-checkbox-border-width:1px;--token-form-checkbox-background-image-size:12px;--token-form-checkbox-background-image-data-url:url("data:image/svg+xml,%3csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M9.78033 3.21967C10.0732 3.51256 10.0732 3.98744 9.78033 4.28033L5.28033 8.78033C4.98744 9.07322 4.51256 9.07322 4.21967 8.78033L2.21967 6.78033C1.92678 6.48744 1.92678 6.01256 2.21967 5.71967C2.51256 5.42678 2.98744 5.42678 3.28033 5.71967L4.75 7.18934L8.71967 3.21967C9.01256 2.92678 9.48744 2.92678 9.78033 3.21967Z' fill='%23FFF'/%3e%3c/svg%3e");--token-form-checkbox-background-image-data-url-indeterminate:url("data:image/svg+xml,%3csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m2.03125,6a0.66146,0.75 0 0 1 0.66146,-0.75l6.61458,0a0.66146,0.75 0 0 1 0,1.5l-6.61458,0a0.66146,0.75 0 0 1 -0.66146,-0.75z' fill='%23FFF'/%3e%3c/svg%3e");--token-form-checkbox-background-image-data-url-disabled:url("data:image/svg+xml,%3csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M9.78033 3.21967C10.0732 3.51256 10.0732 3.98744 9.78033 4.28033L5.28033 8.78033C4.98744 9.07322 4.51256 9.07322 4.21967 8.78033L2.21967 6.78033C1.92678 6.48744 1.92678 6.01256 2.21967 5.71967C2.51256 5.42678 2.98744 5.42678 3.28033 5.71967L4.75 7.18934L8.71967 3.21967C9.01256 2.92678 9.48744 2.92678 9.78033 3.21967Z' fill='%238C909C'/%3e%3c/svg%3e");--token-form-checkbox-background-image-data-url-indeterminate-disabled:url("data:image/svg+xml,%3csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='m2.03125,6a0.66146,0.75 0 0 1 0.66146,-0.75l6.61458,0a0.66146,0.75 0 0 1 0,1.5l-6.61458,0a0.66146,0.75 0 0 1 -0.66146,-0.75z' fill='%238C909C'/%3e%3c/svg%3e");--token-form-control-base-foreground-value-color:#0c0c0e;--token-form-control-base-foreground-placeholder-color:#656a76;--token-form-control-base-surface-color-default:#ffffff;--token-form-control-base-surface-color-hover:#f1f2f3;--token-form-control-base-border-color-default:#8c909c;--token-form-control-base-border-color-hover:#656a76;--token-form-control-checked-foreground-color:#ffffff;--token-form-control-checked-surface-color-default:#1060ff;--token-form-control-checked-surface-color-hover:#0c56e9;--token-form-control-checked-border-color-default:#0c56e9;--token-form-control-checked-border-color-hover:#0046d1;--token-form-control-invalid-border-color-default:#c00005;--token-form-control-invalid-border-color-hover:#940004;--token-form-control-readonly-foreground-color:#3b3d45;--token-form-control-readonly-surface-color:#f1f2f3;--token-form-control-readonly-border-color:#656a761a;--token-form-control-disabled-foreground-color:#8c909c;--token-form-control-disabled-surface-color:#fafafa;--token-form-control-disabled-border-color:#656a7633;--token-form-control-padding:7px;--token-form-control-border-radius:5px;--token-form-control-border-width:1px;--token-form-radio-size:16px;--token-form-radio-border-width:1px;--token-form-radio-background-image-size:12px;--token-form-radio-background-image-data-url:url("data:image/svg+xml,%3csvg width='12' height='12' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='6' cy='6' r='2.5' fill='%23ffffff'/%3e%3c/svg%3e");--token-form-radio-background-image-data-url-disabled:url("data:image/svg+xml,%3csvg width='12' height='12' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='6' cy='6' r='2.5' fill='%238C909C'/%3e%3c/svg%3e");--token-form-radiocard-group-gap:16px;--token-form-radiocard-border-width:1px;--token-form-radiocard-border-radius:6px;--token-form-radiocard-content-padding:24px;--token-form-radiocard-control-padding:8px;--token-form-radiocard-transition-duration:0.2s;--token-form-select-background-image-size:16px;--token-form-select-background-image-position-right-x:7px;--token-form-select-background-image-position-top-y:9px;--token-form-select-background-image-data-url:url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8.55 2.24a.75.75 0 0 0-1.1 0L4.2 5.74a.75.75 0 1 0 1.1 1.02L8 3.852l2.7 2.908a.75.75 0 1 0 1.1-1.02l-3.25-3.5Zm-1.1 11.52a.75.75 0 0 0 1.1 0l3.25-3.5a.75.75 0 1 0-1.1-1.02L8 12.148 5.3 9.24a.75.75 0 0 0-1.1 1.02l3.25 3.5Z' fill='%23656A76'/%3E%3C/svg%3E");--token-form-select-background-image-data-url-disabled:url("data:image/svg+xml,%3Csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M8.55 2.24a.75.75 0 0 0-1.1 0L4.2 5.74a.75.75 0 1 0 1.1 1.02L8 3.852l2.7 2.908a.75.75 0 1 0 1.1-1.02l-3.25-3.5Zm-1.1 11.52a.75.75 0 0 0 1.1 0l3.25-3.5a.75.75 0 1 0-1.1-1.02L8 12.148 5.3 9.24a.75.75 0 0 0-1.1 1.02l3.25 3.5Z' fill='%238C909C'/%3E%3C/svg%3E");--token-form-text-input-background-image-size:16px;--token-form-text-input-background-image-position-x:7px;--token-form-text-input-background-image-data-url-date:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M11.5.75a.75.75 0 00-1.5 0V1H6V.75a.75.75 0 00-1.5 0V1H3.25A2.25 2.25 0 001 3.25v9.5A2.25 2.25 0 003.25 15h9.5A2.25 2.25 0 0015 12.75v-9.5A2.25 2.25 0 0012.75 1H11.5V.75zm-7 2.5V2.5H3.25a.75.75 0 00-.75.75V5h11V3.25a.75.75 0 00-.75-.75H11.5v.75a.75.75 0 01-1.5 0V2.5H6v.75a.75.75 0 01-1.5 0zm9 3.25h-11v6.25c0 .414.336.75.75.75h9.5a.75.75 0 00.75-.75V6.5z' fill-rule='evenodd' clip-rule='evenodd' fill='%233B3D45'/%3e%3c/svg%3e");--token-form-text-input-background-image-data-url-time:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3e%3cg fill='%233B3D45'%3e%3cpath d='M8.5 3.75a.75.75 0 00-1.5 0V8c0 .284.16.544.415.67l2.5 1.25a.75.75 0 10.67-1.34L8.5 7.535V3.75z'/%3e%3cpath d='M8 0a8 8 0 100 16A8 8 0 008 0zM1.5 8a6.5 6.5 0 1113 0 6.5 6.5 0 01-13 0z' fill-rule='evenodd' clip-rule='evenodd'/%3e%3c/g%3e%3c/svg%3e");--token-form-text-input-background-image-data-url-search:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3e%3cg fill='%23656A76'%3e%3cpath d='M7.25 2a5.25 5.25 0 103.144 9.455l2.326 2.325a.75.75 0 101.06-1.06l-2.325-2.326A5.25 5.25 0 007.25 2zM3.5 7.25a3.75 3.75 0 117.5 0 3.75 3.75 0 01-7.5 0z' fill-rule='evenodd' clip-rule='evenodd'/%3e%3c/g%3e%3c/svg%3e");--token-form-text-input-background-image-data-url-search-cancel:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.78 4.28a.75.75 0 00-1.06-1.06L8 6.94 4.28 3.22a.75.75 0 00-1.06 1.06L6.94 8l-3.72 3.72a.75.75 0 101.06 1.06L8 9.06l3.72 3.72a.75.75 0 101.06-1.06L9.06 8l3.72-3.72z'/%3e%3c/svg%3e");--token-form-text-input-background-image-data-url-search-loading:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' xmlns='http://www.w3.org/2000/svg' %3e%3cg fill='%23656A76' fill-rule='evenodd' clip-rule='evenodd'%3e%3canimateTransform attributeName='transform' type='rotate' dur='0.9s' from='0 8 8' to='360 8 8' repeatCount='indefinite'/%3e%3cpath d='M8 1.5a6.5 6.5 0 100 13 6.5 6.5 0 000-13zM0 8a8 8 0 1116 0A8 8 0 010 8z' opacity='.2'/%3e%3cpath d='M7.25.75A.75.75 0 018 0a8 8 0 018 8 .75.75 0 01-1.5 0A6.5 6.5 0 008 1.5a.75.75 0 01-.75-.75z'/%3e%3c/g%3e%3c/svg%3e");--token-form-toggle-width:32px;--token-form-toggle-height:16px;--token-form-toggle-base-surface-color-default:#f1f2f3;--token-form-toggle-border-radius:3px;--token-form-toggle-border-width:1px;--token-form-toggle-background-image-size:12px;--token-form-toggle-background-image-position-x:2px;--token-form-toggle-background-image-data-url:url("data:image/svg+xml,%3csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M9.78033 3.21967C10.0732 3.51256 10.0732 3.98744 9.78033 4.28033L5.28033 8.78033C4.98744 9.07322 4.51256 9.07322 4.21967 8.78033L2.21967 6.78033C1.92678 6.48744 1.92678 6.01256 2.21967 5.71967C2.51256 5.42678 2.98744 5.42678 3.28033 5.71967L4.75 7.18934L8.71967 3.21967C9.01256 2.92678 9.48744 2.92678 9.78033 3.21967Z' fill='%23FFF'/%3e%3c/svg%3e");--token-form-toggle-background-image-data-url-disabled:url("data:image/svg+xml,%3csvg viewBox='0 0 12 12' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M9.78033 3.21967C10.0732 3.51256 10.0732 3.98744 9.78033 4.28033L5.28033 8.78033C4.98744 9.07322 4.51256 9.07322 4.21967 8.78033L2.21967 6.78033C1.92678 6.48744 1.92678 6.01256 2.21967 5.71967C2.51256 5.42678 2.98744 5.42678 3.28033 5.71967L4.75 7.18934L8.71967 3.21967C9.01256 2.92678 9.48744 2.92678 9.78033 3.21967Z' fill='%238C909C'/%3e%3c/svg%3e");--token-form-toggle-transition-duration:0.2s;--token-form-toggle-transition-timing-function:cubic-bezier(0.68, -0.2, 0.265, 1.15);--token-form-toggle-thumb-size:16px;--token-pagination-nav-control-height:36px;--token-pagination-nav-control-padding-horizontal:12px;--token-pagination-nav-control-focus-inset:4px;--token-pagination-nav-control-icon-spacing:6px;--token-pagination-nav-indicator-height:2px;--token-pagination-nav-indicator-spacing:6px;--token-pagination-child-spacing-vertical:8px;--token-pagination-child-spacing-horizontal:20px;--token-side-nav-wrapper-border-width:1px;--token-side-nav-wrapper-border-color:#656a76;--token-side-nav-wrapper-padding-horizontal:16px;--token-side-nav-wrapper-padding-vertical:16px;--token-side-nav-wrapper-padding-horizontal-minimized:8px;--token-side-nav-wrapper-padding-vertical-minimized:22px;--token-side-nav-toggle-button-border-radius:5px;--token-side-nav-header-home-link-padding:4px;--token-side-nav-header-home-link-logo-size:48px;--token-side-nav-header-home-link-logo-size-minimized:32px;--token-side-nav-header-actions-spacing:8px;--token-side-nav-body-list-margin-vertical:24px;--token-side-nav-body-list-item-height:36px;--token-side-nav-body-list-item-padding-horizontal:8px;--token-side-nav-body-list-item-padding-vertical:4px;--token-side-nav-body-list-item-spacing-vertical:2px;--token-side-nav-body-list-item-content-spacing-horizontal:8px;--token-side-nav-body-list-item-border-radius:5px;--token-side-nav-color-foreground-primary:#dedfe3;--token-side-nav-color-foreground-strong:#fff;--token-side-nav-color-foreground-faint:#8c909c;--token-side-nav-color-surface-primary:#0c0c0e;--token-side-nav-color-surface-interactive-hover:#3b3d45;--token-side-nav-color-surface-interactive-active:#656a76;--token-tabs-tab-height:36px;--token-tabs-tab-padding-horizontal:12px;--token-tabs-tab-padding-vertical:0px;--token-tabs-tab-border-radius:5px;--token-tabs-tab-focus-inset:6px;--token-tabs-tab-gutter:6px;--token-tabs-indicator-height:3px;--token-tabs-indicator-transition-function:cubic-bezier(0.5, 1, 0.89, 1);--token-tabs-indicator-transition-duration:0.6s;--token-tabs-divider-height:1px;--token-tooltip-border-radius:5px;--token-tooltip-color-foreground-primary:var(--token-color-foreground-high-contrast);--token-tooltip-color-surface-primary:var(--token-color-palette-neutral-700);--token-tooltip-focus-offset:-2px;--token-tooltip-max-width:280px;--token-tooltip-padding-horizontal:12px;--token-tooltip-padding-vertical:8px;--token-tooltip-transition-function:cubic-bezier(0.54, 1.5, 0.38, 1.11);--token-typography-font-stack-display:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-font-stack-text:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-font-stack-code:ui-monospace,Menlo,Consolas,monospace;--token-typography-font-weight-regular:400;--token-typography-font-weight-medium:500;--token-typography-font-weight-semibold:600;--token-typography-font-weight-bold:700;--token-typography-display-500-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-display-500-font-size:1.875rem;--token-typography-display-500-line-height:1.2666;--token-typography-display-400-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-display-400-font-size:1.5rem;--token-typography-display-400-line-height:1.3333;--token-typography-display-300-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-display-300-font-size:1.125rem;--token-typography-display-300-line-height:1.3333;--token-typography-display-300-letter-spacing:-0.5px;--token-typography-display-200-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-display-200-font-size:1rem;--token-typography-display-200-line-height:1.5;--token-typography-display-200-letter-spacing:-0.5px;--token-typography-display-100-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-display-100-font-size:0.8125rem;--token-typography-display-100-line-height:1.3846;--token-typography-body-300-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-body-300-font-size:1rem;--token-typography-body-300-line-height:1.5;--token-typography-body-200-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-body-200-font-size:0.875rem;--token-typography-body-200-line-height:1.4286;--token-typography-body-100-font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;--token-typography-body-100-font-size:0.8125rem;--token-typography-body-100-line-height:1.3846;--token-typography-code-100-font-family:ui-monospace,Menlo,Consolas,monospace;--token-typography-code-100-font-size:0.8125rem;--token-typography-code-100-line-height:1.23;--token-typography-code-200-font-family:ui-monospace,Menlo,Consolas,monospace;--token-typography-code-200-font-size:0.875rem;--token-typography-code-200-line-height:1.125;--token-typography-code-300-font-family:ui-monospace,Menlo,Consolas,monospace;--token-typography-code-300-font-size:1rem;--token-typography-code-300-line-height:1.25;--hds-app-desktop-breakpoint:1088px;--hds-app-sidenav-width-minimized:48px;--hds-app-sidenav-width-expanded:280px;--hds-app-sidenav-width-fixed:var(--hds-app-sidenav-width-expanded);--hds-app-sidenav-animation-duration:200ms;--hds-app-sidenav-animation-delay:var(--hds-app-sidenav-animation-duration);--hds-app-sidenav-animation-easing:cubic-bezier(0.65, 0, 0.35, 1);--decor-radius-000:0;--decor-radius-100:2px;--decor-radius-200:4px;--decor-radius-250:6px;--decor-radius-300:7px;--decor-radius-999:9999px;--decor-radius-full:100%;--decor-border-000:none;--decor-border-100:1px solid;--decor-border-200:2px solid;--decor-border-300:3px solid;--decor-border-400:4px solid;--icon-alert-triangle-16:url('data:image/svg+xml;charset=UTF-8,');--icon-alert-triangle-24:url('data:image/svg+xml;charset=UTF-8,');--icon-arrow-left-16:url('data:image/svg+xml;charset=UTF-8,');--icon-arrow-left-24:url('data:image/svg+xml;charset=UTF-8,');--icon-arrow-right-16:url('data:image/svg+xml;charset=UTF-8,');--icon-arrow-right-24:url('data:image/svg+xml;charset=UTF-8,');--icon-x-square-fill-16:url('data:image/svg+xml;charset=UTF-8,');--icon-x-square-fill-24:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-down-16:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-down-24:url('data:image/svg+xml;charset=UTF-8,');--icon-clipboard-copy-16:url('data:image/svg+xml;charset=UTF-8,');--icon-clipboard-copy-24:url('data:image/svg+xml;charset=UTF-8,');--icon-docs-16:url('data:image/svg+xml;charset=UTF-8,');--icon-docs-24:url('data:image/svg+xml;charset=UTF-8,');--icon-external-link-16:url('data:image/svg+xml;charset=UTF-8,');--icon-external-link-24:url('data:image/svg+xml;charset=UTF-8,');--icon-file-16:url('data:image/svg+xml;charset=UTF-8,');--icon-file-24:url('data:image/svg+xml;charset=UTF-8,');--icon-folder-16:url('data:image/svg+xml;charset=UTF-8,');--icon-folder-24:url('data:image/svg+xml;charset=UTF-8,');--icon-activity-16:url('data:image/svg+xml;charset=UTF-8,');--icon-activity-24:url('data:image/svg+xml;charset=UTF-8,');--icon-help-16:url('data:image/svg+xml;charset=UTF-8,');--icon-help-24:url('data:image/svg+xml;charset=UTF-8,');--icon-learn-16:url('data:image/svg+xml;charset=UTF-8,');--icon-learn-24:url('data:image/svg+xml;charset=UTF-8,');--icon-github-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-github-color-24:url('data:image/svg+xml;charset=UTF-8,');--icon-google-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-google-color-24:url('data:image/svg+xml;charset=UTF-8,');--icon-kubernetes-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-kubernetes-color-24:url('data:image/svg+xml;charset=UTF-8,');--icon-menu-16:url('data:image/svg+xml;charset=UTF-8,');--icon-menu-24:url('data:image/svg+xml;charset=UTF-8,');--icon-minus-square-16:url('data:image/svg+xml;charset=UTF-8,');--icon-minus-square-24:url('data:image/svg+xml;charset=UTF-8,');--icon-more-horizontal-16:url('data:image/svg+xml;charset=UTF-8,');--icon-more-horizontal-24:url('data:image/svg+xml;charset=UTF-8,');--icon-globe-16:url('data:image/svg+xml;charset=UTF-8,');--icon-globe-24:url('data:image/svg+xml;charset=UTF-8,');--icon-search-16:url('data:image/svg+xml;charset=UTF-8,');--icon-search-24:url('data:image/svg+xml;charset=UTF-8,');--icon-star-16:url('data:image/svg+xml;charset=UTF-8,');--icon-star-24:url('data:image/svg+xml;charset=UTF-8,');--icon-org-16:url('data:image/svg+xml;charset=UTF-8,');--icon-org-24:url('data:image/svg+xml;charset=UTF-8,');--icon-user-16:url('data:image/svg+xml;charset=UTF-8,');--icon-user-24:url('data:image/svg+xml;charset=UTF-8,');--icon-users-16:url('data:image/svg+xml;charset=UTF-8,');--icon-users-24:url('data:image/svg+xml;charset=UTF-8,');--icon-alert-circle-16:url('data:image/svg+xml;charset=UTF-8,');--icon-alert-circle-24:url('data:image/svg+xml;charset=UTF-8,');--icon-check-16:url('data:image/svg+xml;charset=UTF-8,');--icon-check-24:url('data:image/svg+xml;charset=UTF-8,');--icon-check-circle-16:url('data:image/svg+xml;charset=UTF-8,');--icon-check-circle-24:url('data:image/svg+xml;charset=UTF-8,');--icon-check-circle-fill-16:url('data:image/svg+xml;charset=UTF-8,');--icon-check-circle-fill-24:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-left-16:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-left-24:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-right-16:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-right-24:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-up-16:url('data:image/svg+xml;charset=UTF-8,');--icon-chevron-up-24:url('data:image/svg+xml;charset=UTF-8,');--icon-delay-16:url('data:image/svg+xml;charset=UTF-8,');--icon-delay-24:url('data:image/svg+xml;charset=UTF-8,');--icon-docs-link-16:url('data:image/svg+xml;charset=UTF-8,');--icon-docs-link-24:url('data:image/svg+xml;charset=UTF-8,');--icon-eye-16:url('data:image/svg+xml;charset=UTF-8,');--icon-eye-24:url('data:image/svg+xml;charset=UTF-8,');--icon-eye-off-16:url('data:image/svg+xml;charset=UTF-8,');--icon-eye-off-24:url('data:image/svg+xml;charset=UTF-8,');--icon-file-text-16:url('data:image/svg+xml;charset=UTF-8,');--icon-file-text-24:url('data:image/svg+xml;charset=UTF-8,');--icon-gateway-16:url('data:image/svg+xml;charset=UTF-8,');--icon-gateway-24:url('data:image/svg+xml;charset=UTF-8,');--icon-git-commit-16:url('data:image/svg+xml;charset=UTF-8,');--icon-git-commit-24:url('data:image/svg+xml;charset=UTF-8,');--icon-hexagon-16:url('data:image/svg+xml;charset=UTF-8,');--icon-hexagon-24:url('data:image/svg+xml;charset=UTF-8,');--icon-history-16:url('data:image/svg+xml;charset=UTF-8,');--icon-history-24:url('data:image/svg+xml;charset=UTF-8,');--icon-info-16:url('data:image/svg+xml;charset=UTF-8,');--icon-info-24:url('data:image/svg+xml;charset=UTF-8,');--icon-layers-16:url('data:image/svg+xml;charset=UTF-8,');--icon-layers-24:url('data:image/svg+xml;charset=UTF-8,');--icon-loading-16:url('data:image/svg+xml;charset=UTF-8,');--icon-loading-24:url('data:image/svg+xml;charset=UTF-8,');--icon-network-alt-16:url('data:image/svg+xml;charset=UTF-8,');--icon-network-alt-24:url('data:image/svg+xml;charset=UTF-8,');--icon-path-16:url('data:image/svg+xml;charset=UTF-8,');--icon-path-24:url('data:image/svg+xml;charset=UTF-8,');--icon-running-16:url('data:image/svg+xml;charset=UTF-8,');--icon-running-24:url('data:image/svg+xml;charset=UTF-8,');--icon-skip-16:url('data:image/svg+xml;charset=UTF-8,');--icon-skip-24:url('data:image/svg+xml;charset=UTF-8,');--icon-socket-16:url('data:image/svg+xml;charset=UTF-8,');--icon-socket-24:url('data:image/svg+xml;charset=UTF-8,');--icon-star-circle-16:url('data:image/svg+xml;charset=UTF-8,');--icon-star-circle-24:url('data:image/svg+xml;charset=UTF-8,');--icon-star-fill-16:url('data:image/svg+xml;charset=UTF-8,');--icon-star-fill-24:url('data:image/svg+xml;charset=UTF-8,');--icon-tag-16:url('data:image/svg+xml;charset=UTF-8,');--icon-tag-24:url('data:image/svg+xml;charset=UTF-8,');--icon-x-16:url('data:image/svg+xml;charset=UTF-8,');--icon-x-24:url('data:image/svg+xml;charset=UTF-8,');--icon-x-circle-16:url('data:image/svg+xml;charset=UTF-8,');--icon-x-circle-24:url('data:image/svg+xml;charset=UTF-8,');--icon-x-square-16:url('data:image/svg+xml;charset=UTF-8,');--icon-x-square-24:url('data:image/svg+xml;charset=UTF-8,');--icon-cloud-cross-16:url('data:image/svg+xml;charset=UTF-8,');--icon-loading-motion-16:url('data:image/svg+xml;charset=UTF-8,');--icon-auth0-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-auth0-color-24:url('data:image/svg+xml;charset=UTF-8,');--icon-logo-ember-circle-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-logo-glimmer-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-logo-jwt-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-microsoft-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-microsoft-color-24:url('data:image/svg+xml;charset=UTF-8,');--icon-logo-oidc-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-okta-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-okta-color-24:url('data:image/svg+xml;charset=UTF-8,');--icon-mesh-16:url('data:image/svg+xml;charset=UTF-8,');--icon-mesh-24:url('data:image/svg+xml;charset=UTF-8,');--icon-port-16:url('data:image/svg+xml;charset=UTF-8,');--icon-protocol-16:url('data:image/svg+xml;charset=UTF-8,');--icon-redirect-16:url('data:image/svg+xml;charset=UTF-8,');--icon-redirect-24:url('data:image/svg+xml;charset=UTF-8,');--icon-search-color-16:url('data:image/svg+xml;charset=UTF-8,');--icon-sort-desc-16:url('data:image/svg+xml;charset=UTF-8,');--icon-sort-desc-24:url('data:image/svg+xml;charset=UTF-8,');--icon-union-16:url('data:image/svg+xml;charset=UTF-8,');--chrome-width:280px;--chrome-height:64px;--typo-action:var(--token-color-foreground-action);--decor-error:var(--token-color-foreground-critical);--typo-contrast:var(--token-color-hashicorp-brand);--syntax-light-grey:#dde3e7;--syntax-light-gray:#a4a4a4;--syntax-light-grey-blue:#6c7b81;--syntax-dark-grey:#788290;--syntax-faded-gray:#eaeaea;--syntax-atlas:#127eff;--syntax-vagrant:#2f88f7;--syntax-consul:#69499a;--syntax-terraform:#822ff7;--syntax-serf:#dd4e58;--syntax-packer:#1ddba3;--syntax-gray:lighten(#000, 89%);--syntax-red:#ff3d3d;--syntax-green:#39b54a;--syntax-dark-gray:#535f73;--syntax-gutter-grey:#2a2f36;--syntax-yellow:var(--token-color-vault-brand);--horizontal-kv-list-separator-width:18px;--horizontal-kv-list-key-separator:":";--horizontal-kv-list-key-wrapper-start:"(";--horizontal-kv-list-key-wrapper-end:")";--csv-list-separator:",";--icon-loading:icon-loading-motion;--color-info:var(--token-color-foreground-action);--color-alert:var(--token-color-palette-amber-200)}.hds-border-primary{border:1px solid var(--token-color-border-primary)}.hds-border-faint{border:1px solid var(--token-color-border-faint)}.hds-border-strong{border:1px solid var(--token-color-border-strong)}.hds-border-action{border:1px solid var(--token-color-border-action)}.hds-border-highlight{border:1px solid var(--token-color-border-highlight)}.hds-border-success{border:1px solid var(--token-color-border-success)}.hds-border-warning{border:1px solid var(--token-color-border-warning)}.hds-border-critical{border:1px solid var(--token-color-border-critical)}.hds-foreground-strong{color:var(--token-color-foreground-strong)}.hds-foreground-primary{color:var(--token-color-foreground-primary)}.hds-foreground-faint{color:var(--token-color-foreground-faint)}.hds-foreground-high-contrast{color:var(--token-color-foreground-high-contrast)}.hds-foreground-disabled{color:var(--token-color-foreground-disabled)}.hds-foreground-action{color:var(--token-color-foreground-action)}.hds-foreground-action-hover{color:var(--token-color-foreground-action-hover)}.hds-foreground-action-active{color:var(--token-color-foreground-action-active)}.hds-foreground-highlight{color:var(--token-color-foreground-highlight)}.hds-foreground-highlight-on-surface{color:var(--token-color-foreground-highlight-on-surface)}.hds-foreground-highlight-high-contrast{color:var(--token-color-foreground-highlight-high-contrast)}.hds-foreground-success{color:var(--token-color-foreground-success)}.hds-foreground-success-on-surface{color:var(--token-color-foreground-success-on-surface)}.hds-foreground-success-high-contrast{color:var(--token-color-foreground-success-high-contrast)}.hds-foreground-warning{color:var(--token-color-foreground-warning)}.hds-foreground-warning-on-surface{color:var(--token-color-foreground-warning-on-surface)}.hds-foreground-warning-high-contrast{color:var(--token-color-foreground-warning-high-contrast)}.hds-foreground-critical{color:var(--token-color-foreground-critical)}.hds-foreground-critical-on-surface{color:var(--token-color-foreground-critical-on-surface)}.hds-foreground-critical-high-contrast{color:var(--token-color-foreground-critical-high-contrast)}.hds-page-primary{background-color:var(--token-color-page-primary)}.hds-page-faint{background-color:var(--token-color-page-faint)}.hds-surface-primary{background-color:var(--token-color-surface-primary)}.hds-surface-faint{background-color:var(--token-color-surface-faint)}.hds-badge--color-neutral.hds-badge--type-filled,.hds-surface-strong{background-color:var(--token-color-surface-strong)}.hds-surface-interactive{background-color:var(--token-color-surface-interactive)}.hds-surface-interactive-hover{background-color:var(--token-color-surface-interactive-hover)}.hds-surface-interactive-active{background-color:var(--token-color-surface-interactive-active)}.hds-surface-interactive-disabled{background-color:var(--token-color-surface-interactive-disabled)}.hds-surface-action{background-color:var(--token-color-surface-action)}.hds-surface-highlight{background-color:var(--token-color-surface-highlight)}.hds-surface-success{background-color:var(--token-color-surface-success)}.hds-surface-warning{background-color:var(--token-color-surface-warning)}.hds-surface-critical{background-color:var(--token-color-surface-critical)}.hds-elevation-low{box-shadow:var(--token-elevation-low-box-shadow)}.hds-elevation-mid{box-shadow:var(--token-elevation-mid-box-shadow)}.hds-elevation-high{box-shadow:var(--token-elevation-high-box-shadow)}.hds-elevation-higher{box-shadow:var(--token-elevation-higher-box-shadow)}.consul-server-list a:hover div,.hds-elevation-overlay,.modal-dialog [role=document]{box-shadow:var(--token-elevation-overlay-box-shadow)}.hds-surface-inset{box-shadow:var(--token-surface-inset-box-shadow)}.hds-surface-base{box-shadow:var(--token-surface-base-box-shadow)}.hds-surface-low{box-shadow:var(--token-surface-low-box-shadow)}.hds-accordion-item.hds-accordion-item--does-not-contain-interactive,.hds-surface-mid{box-shadow:var(--token-surface-mid-box-shadow)}.hds-surface-high{box-shadow:var(--token-surface-high-box-shadow)}.hds-surface-higher{box-shadow:var(--token-surface-higher-box-shadow)}.hds-surface-overlay{box-shadow:var(--token-surface-overlay-box-shadow)}.hds-focus-ring-action-box-shadow{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-focus-ring-critical-box-shadow{box-shadow:var(--token-focus-ring-critical-box-shadow)}.hds-font-family-sans-display{font-family:var(--token-typography-font-stack-display)}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive,.hds-badge-count,.hds-badge__text,.hds-breadcrumb__text,.hds-button,.hds-font-family-sans-text{font-family:var(--token-typography-font-stack-text)}.hds-font-family-mono-code{font-family:var(--token-typography-font-stack-code)}#metrics-container .sparkline-wrapper .tooltip,.app-view h1 span.kind-proxy,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password],.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text],.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea,.app-view>div form:not(.filter-bar) [role=radiogroup] label>em,.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.app-view>div form:not(.filter-bar) [role=radiogroup] label>strong,.auth-form em,.consul-auth-method-type,.consul-auth-method-view section,.consul-external-source,.consul-health-check-list .health-check-output dd em,.consul-health-check-list .health-check-output dl>dd,.consul-intention-fieldsets .permissions>button,.consul-intention-list td strong,.consul-intention-list td.destination em,.consul-intention-list td.permissions,.consul-intention-list td.source em,.consul-intention-permission-header-list>ul>li dd,.consul-intention-permission-list strong,.consul-intention-permission-list>ul>li dd,.consul-intention-search-bar li button span,.consul-kind,.consul-peer-search-bar li button span,.consul-server-card .health-status+dd,.consul-source,.consul-transparent-proxy,.disclosure-menu [aria-expanded]~*>div,.discovery-chain .resolvers>header>*,.discovery-chain .route-card header dt,.discovery-chain .route-card>header ul li,.discovery-chain .routes>header>*,.discovery-chain .splitters>header>*,.empty-state header :nth-child(2),.empty-state p,.empty-state>ul>li>*,.empty-state>ul>li>::before,.empty-state>ul>li>label>button,.empty-state>ul>li>label>button::before,.has-error>strong,.has-error>strong::before,.hds-font-weight-regular,.informed-action p,.leader,.menu-panel>div,.modal-dialog [role=document] .type-password [type=password],.modal-dialog [role=document] .type-password [type=text],.modal-dialog [role=document] .type-password textarea,.modal-dialog [role=document] .type-password>em,.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-password>strong,.modal-dialog [role=document] .type-select [type=password],.modal-dialog [role=document] .type-select [type=text],.modal-dialog [role=document] .type-select textarea,.modal-dialog [role=document] .type-select>em,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] .type-select>strong,.modal-dialog [role=document] .type-text [type=password],.modal-dialog [role=document] .type-text [type=text],.modal-dialog [role=document] .type-text textarea,.modal-dialog [role=document] .type-text>em,.modal-dialog [role=document] .type-text>span,.modal-dialog [role=document] .type-text>strong,.modal-dialog [role=document] [role=radiogroup] label [type=password],.modal-dialog [role=document] [role=radiogroup] label [type=text],.modal-dialog [role=document] [role=radiogroup] label textarea,.modal-dialog [role=document] [role=radiogroup] label>em,.modal-dialog [role=document] [role=radiogroup] label>span,.modal-dialog [role=document] [role=radiogroup] label>strong,.modal-dialog [role=document] form button+em,.modal-dialog [role=document] label a[rel*=help],.modal-dialog [role=document] p,.modal-dialog [role=document] table td,.modal-dialog [role=document] table td p,.modal-dialog [role=document] table th em,.more-popover-menu>[type=checkbox]+label+div>div,.oidc-select button.reset,.oidc-select label [type=password],.oidc-select label [type=text],.oidc-select label textarea,.oidc-select label>em,.oidc-select label>span,.oidc-select label>strong,.popover-menu>[type=checkbox]+label+div>div,.search-bar-status li:not(.remove-all),.tippy-box[data-theme~=tooltip] .tippy-content,.topology-metrics-source-type,.topology-metrics-status-error,.topology-metrics-status-loader,.type-toggle [type=password],.type-toggle [type=text],.type-toggle textarea,.type-toggle>em,.type-toggle>span,.type-toggle>strong,body,html[data-route^="dc.acls.index"] main td strong,main .type-password [type=password],main .type-password [type=text],main .type-password textarea,main .type-password>em,main .type-password>span,main .type-password>strong,main .type-select [type=password],main .type-select [type=text],main .type-select textarea,main .type-select>em,main .type-select>span,main .type-select>strong,main .type-text [type=password],main .type-text [type=text],main .type-text textarea,main .type-text>em,main .type-text>span,main .type-text>strong,main form button+em,main label a[rel*=help],main p,main table td,main table td p,main table th em,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl,section[data-route="dc.show.serverstatus"] .server-failure-tolerance dt,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em,section[data-route="dc.show.license"] .validity dl,span.label,span.policy-node-identity,span.policy-service-identity,table.has-actions tr>.actions>[type=checkbox]+label+div>div,table.with-details tr>.actions>[type=checkbox]+label+div>div{font-weight:400}.app-view h1 em,.consul-exposed-path-list>ul>li .copy-button button,.consul-exposed-path-list>ul>li>.header,.consul-lock-session-list ul>li:not(:first-child) .copy-button button,.consul-lock-session-list ul>li:not(:first-child)>.header,.consul-peer-search-bar .value-active span,.consul-peer-search-bar .value-deleting span,.consul-peer-search-bar .value-establishing span,.consul-peer-search-bar .value-failing span,.consul-peer-search-bar .value-pending span,.consul-peer-search-bar .value-terminated span,.consul-upstream-instance-list li .copy-button button,.consul-upstream-instance-list li>.header,.disclosure-menu [aria-expanded]~* [role=separator],.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.hds-font-weight-medium,.informed-action>ul>li>*,.list-collection>ul>li:not(:first-child) .copy-button button,.list-collection>ul>li:not(:first-child)>.header,.menu-panel [role=separator],.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.more-popover-menu>[type=checkbox]+label+div [role=separator],.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.peerings-badge .peerings-badge__text,.popover-menu>[type=checkbox]+label+div [role=separator],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.tab-nav,main header nav:first-child ol li>*,table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div [role=separator],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{font-weight:500}#downstream-container .topology-metrics-card p,#metrics-container .sparkline-wrapper .tooltip .sparkline-time,#metrics-container div:first-child,#upstream-container .topology-metrics-card p,.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.code-editor .toolbar-container .toolbar .title,.consul-auth-method-binding-list h2,.consul-auth-method-nspace-list thead td,.consul-auth-method-view section h2,.consul-auth-method-view section table thead td,.consul-bucket-list .service+dd,.consul-health-check-list .health-check-output dt,.consul-health-check-list .health-check-output header>*,.consul-intention-action-warn-modal button.dangerous,.consul-intention-list td.destination,.consul-intention-list td.source,.consul-intention-permission-form h2,.consul-intention-view h2,.consul-peer-form-generate li::after,.consul-server-card .name+dd,.copy-button button,.definition-table dt,.empty-state header :first-child,.hds-font-weight-semibold,.informed-action header,.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] .type-text>span,.modal-dialog [role=document] [role=radiogroup] label>span,.modal-dialog [role=document] form h2,.modal-dialog [role=document] table caption,.modal-dialog [role=document] table td:first-child,.modal-dialog [role=document] table td:first-child p,.modal-dialog [role=document] table th,.modal-dialog [role=document]>header>:not(button),.modal-dialog-body h2,.oidc-select label>span,.popover-select label>*,.radio-card header,.sparkline-key h3,.topology-notices button,.type-sort.popover-select label>*,.type-toggle label span,.type-toggle>span,.warning.modal-dialog header>:not(label),fieldset>header,html[data-route^="dc.services.instance.metadata"] .tab-section section h2,html[data-route^="dc.kv.edit"] h2,main .type-password>span,main .type-select>span,main .type-text>span,main form h2,main table caption,main table td:first-child,main table td:first-child p,main table th,section[data-route="dc.show.serverstatus"] .redundancy-zones h3,section[data-route="dc.show.serverstatus"] .server-failure-tolerance dd,section[data-route="dc.show.serverstatus"] h2,section[data-route="dc.show.serverstatus"] h3,section[data-route="dc.show.license"] aside header>:first-child,section[data-route="dc.show.license"] h2,span.label,strong{font-weight:600}.discovery-chain .route-card header:not(.short) dd,.discovery-chain .route-card section dt,.discovery-chain .splitter-card>header,.hds-font-weight-bold,h1{font-weight:700}.hds-typography-display-500,h1{font-family:var(--token-typography-display-500-font-family);font-size:var(--token-typography-display-500-font-size);line-height:var(--token-typography-display-500-line-height);margin:0;padding:0}.consul-auth-method-binding-list h2,.consul-auth-method-view section h2,.consul-intention-permission-form h2,.consul-intention-view h2,.empty-state header :first-child,.hds-typography-display-400,.modal-dialog [role=document] form h2,.modal-dialog [role=document]>header>:not(button),.modal-dialog-body h2,html[data-route^="dc.kv.edit"] h2,main form h2,section[data-route="dc.show.license"] h2{font-family:var(--token-typography-display-400-font-family);font-size:var(--token-typography-display-400-font-size);line-height:var(--token-typography-display-400-line-height);margin:0;padding:0}#downstream-container .topology-metrics-card p,#upstream-container .topology-metrics-card p,.consul-exposed-path-list>ul>li>.header,.consul-health-check-list .health-check-output header>*,.consul-lock-session-list ul>li:not(:first-child)>.header,.consul-upstream-instance-list li>.header,.hds-typography-display-300,.list-collection>ul>li:not(:first-child)>.header,.sparkline-key h3,.warning.modal-dialog header>:not(label),html[data-route^="dc.services.instance.metadata"] .tab-section section h2,section[data-route="dc.show.serverstatus"] .redundancy-zones h3,section[data-route="dc.show.serverstatus"] .server-failure-tolerance dd,section[data-route="dc.show.serverstatus"] h2,section[data-route="dc.show.serverstatus"] h3,section[data-route="dc.show.license"] aside header>:first-child{font-family:var(--token-typography-display-300-font-family);font-size:var(--token-typography-display-300-font-size);line-height:var(--token-typography-display-300-line-height);margin:0;padding:0}.consul-server-card .name+dd,.hds-side-nav .ember-a11y-refocus-skip-link,.hds-typography-display-200{font-size:var(--token-typography-display-200-font-size);line-height:var(--token-typography-display-200-line-height)}.consul-server-card .name+dd,.hds-typography-display-200{font-family:var(--token-typography-display-200-font-family);margin:0;padding:0}.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.consul-intention-list td.destination,.consul-intention-list td.source,.definition-table dt,.hds-typography-display-100,.informed-action header,.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] .type-text>span,.modal-dialog [role=document] [role=radiogroup] label>span,.modal-dialog [role=document] table caption,.modal-dialog [role=document] table th,.oidc-select label>span,.radio-card header,.type-toggle>span,fieldset>header,main .type-password>span,main .type-select>span,main .type-text>span,main table caption,main table th{font-family:var(--token-typography-display-100-font-family);font-size:var(--token-typography-display-100-font-size);line-height:var(--token-typography-display-100-line-height);margin:0;padding:0}#metrics-container div:first-child,.hds-typography-body-300,section[data-route="dc.show.license"] .validity dl{font-family:var(--token-typography-body-300-font-family);font-size:var(--token-typography-body-300-font-size);line-height:var(--token-typography-body-300-line-height);margin:0;padding:0}#metrics-container .sparkline-wrapper .tooltip,#metrics-container .sparkline-wrapper .tooltip .sparkline-time,.app-view h1 em,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password],.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text],.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea,.code-editor .toolbar-container .toolbar .title,.consul-auth-method-nspace-list thead td,.consul-auth-method-view section,.consul-auth-method-view section table thead td,.consul-external-source,.consul-health-check-list .health-check-output dl>dd,.consul-health-check-list .health-check-output dt,.consul-kind,.consul-peer-form-generate li::after,.consul-source,.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.empty-state>ul>li>::before,.empty-state>ul>li>label>button::before,.has-error>strong::before,.hds-typography-body-200,.informed-action>ul>li>*,.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.modal-dialog [role=document] .type-password [type=password],.modal-dialog [role=document] .type-password [type=text],.modal-dialog [role=document] .type-password textarea,.modal-dialog [role=document] .type-select [type=password],.modal-dialog [role=document] .type-select [type=text],.modal-dialog [role=document] .type-select textarea,.modal-dialog [role=document] .type-text [type=password],.modal-dialog [role=document] .type-text [type=text],.modal-dialog [role=document] .type-text textarea,.modal-dialog [role=document] [role=radiogroup] label [type=password],.modal-dialog [role=document] [role=radiogroup] label [type=text],.modal-dialog [role=document] [role=radiogroup] label textarea,.modal-dialog [role=document] table td,.modal-dialog [role=document] table td p,.modal-dialog [role=document] table td:first-child,.modal-dialog [role=document] table td:first-child p,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.oidc-select label [type=password],.oidc-select label [type=text],.oidc-select label textarea,.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.tab-nav,.topology-metrics-status-error,.topology-metrics-status-loader,.type-toggle [type=password],.type-toggle [type=text],.type-toggle label span,.type-toggle textarea,body,main .type-password [type=password],main .type-password [type=text],main .type-password textarea,main .type-select [type=password],main .type-select [type=text],main .type-select textarea,main .type-text [type=password],main .type-text [type=text],main .type-text textarea,main header nav:first-child ol li>*,main table td,main table td p,main table td:first-child,main table td:first-child p,strong,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{font-family:var(--token-typography-body-200-font-family);font-size:var(--token-typography-body-200-font-size);line-height:var(--token-typography-body-200-line-height);margin:0;padding:0}.app-view h1 span.kind-proxy,.app-view>div form:not(.filter-bar) [role=radiogroup] label>em,.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.auth-form em,.consul-exposed-path-list>ul>li .copy-button button,.consul-intention-action-warn-modal button.dangerous,.consul-intention-fieldsets .permissions>button,.consul-intention-list td.permissions,.consul-intention-permission-header-list>ul>li dd,.consul-intention-permission-list>ul>li dd,.consul-lock-session-list ul>li:not(:first-child) .copy-button button,.consul-server-card .health-status+dd,.consul-upstream-instance-list li .copy-button button,.copy-button button,.disclosure-menu [aria-expanded]~* [role=separator],.disclosure-menu [aria-expanded]~*>div,.discovery-chain .resolvers>header>*,.discovery-chain .routes>header>*,.discovery-chain .splitters>header>*,.empty-state header :nth-child(2),.empty-state p,.empty-state>ul>li>*,.empty-state>ul>li>label>button,.has-error>strong,.hds-typography-body-100,.informed-action p,.list-collection>ul>li:not(:first-child) .copy-button button,.menu-panel [role=separator],.menu-panel>div,.modal-dialog [role=document] .type-password>em,.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-select>em,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] .type-text>em,.modal-dialog [role=document] .type-text>span,.modal-dialog [role=document] [role=radiogroup] label>em,.modal-dialog [role=document] [role=radiogroup] label>span,.modal-dialog [role=document] form button+em,.modal-dialog [role=document] p,.more-popover-menu>[type=checkbox]+label+div [role=separator],.more-popover-menu>[type=checkbox]+label+div>div,.oidc-select button.reset,.oidc-select label>em,.oidc-select label>span,.peerings-badge .peerings-badge__text,.popover-menu>[type=checkbox]+label+div [role=separator],.popover-menu>[type=checkbox]+label+div>div,.popover-select label>*,.tippy-box[data-theme~=tooltip] .tippy-content,.topology-notices button,.type-sort.popover-select label>*,.type-toggle>em,.type-toggle>span,main .type-password>em,main .type-password>span,main .type-select>em,main .type-select>span,main .type-text>em,main .type-text>span,main form button+em,main p,section[data-route="dc.show.serverstatus"] .server-failure-tolerance dt,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em,span.label,table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator],table.has-actions tr>.actions>[type=checkbox]+label+div>div,table.with-details tr>.actions>[type=checkbox]+label+div [role=separator],table.with-details tr>.actions>[type=checkbox]+label+div>div{font-family:var(--token-typography-body-100-font-family);font-size:var(--token-typography-body-100-font-size);line-height:var(--token-typography-body-100-line-height);margin:0;padding:0}.hds-typography-code-100{font-family:var(--token-typography-code-100-font-family);font-size:var(--token-typography-code-100-font-size);line-height:var(--token-typography-code-100-line-height);margin:0;padding:0}.hds-typography-code-200{font-family:var(--token-typography-code-200-font-family);font-size:var(--token-typography-code-200-font-size);line-height:var(--token-typography-code-200-line-height);margin:0;padding:0}.hds-typography-code-300{font-family:var(--token-typography-code-300-font-family);font-size:var(--token-typography-code-300-font-size);line-height:var(--token-typography-code-300-line-height);margin:0;padding:0}.hds-accordion{display:flex;gap:12px}.hds-accordion-item{background:var(--token-color-surface-primary);border-radius:6px}.hds-accordion-item.hds-accordion-item--does-not-contain-interactive.mock-hover,.hds-accordion-item.hds-accordion-item--does-not-contain-interactive:hover{box-shadow:var(--token-surface-high-box-shadow)}.hds-accordion-item.hds-accordion-item--contains-interactive{box-shadow:var(--token-surface-base-box-shadow)}.hds-accordion-item__toggle{position:relative;display:flex;gap:12px;align-items:center;padding:16px 16px 16px 12px}.hds-accordion-item__button{padding:0}.hds-accordion-item__button:hover{cursor:pointer}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive{outline-style:solid;outline-color:transparent;isolation:isolate;position:static;margin:-1px 0;color:var(--token-color-foreground-primary);background:0 0;border:1px solid transparent}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive::before{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;border-radius:5px;content:""}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive.mock-focus::before,.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive:focus:not(:focus-visible)::before{box-shadow:none}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive:focus-visible::before,.hds-breadcrumb__link.mock-focus,.hds-breadcrumb__link:focus{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive.mock-focus.mock-active::before,.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive:focus:active::before{box-shadow:none}.hds-accordion-item__button.hds-accordion-item__button--parent-does-not-contain-interactive::after{position:absolute;display:block;border-radius:6px;content:"";inset:0}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive{position:relative;display:flex;gap:.375rem;align-items:center;justify-content:center;font-weight:var(--token-typography-font-weight-regular);text-decoration:none;border:1px solid transparent;border-radius:5px;outline-style:solid;outline-color:transparent;isolation:isolate;width:24px;height:24px;color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-strong);box-shadow:var(--token-elevation-low-box-shadow)}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive.mock-focus::before,.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive:focus::before{position:absolute;top:-4px;right:-4px;bottom:-4px;left:-4px;z-index:-1;border:3px solid transparent;border-radius:8px;content:""}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive.mock-hover,.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive:hover{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-primary);border-color:var(--token-color-border-strong);cursor:pointer}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive.mock-focus,.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive:focus{box-shadow:none;color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-focus-action-internal)}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive.mock-focus::before,.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive:focus::before{border-color:var(--token-color-focus-action-external)}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive.mock-active,.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive:active{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong);box-shadow:none}.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive.mock-active::before,.hds-accordion-item__button.hds-accordion-item__button--parent-contains-interactive:active::before{border-color:transparent}.hds-accordion-item__button.hds-accordion-item__button--is-open .flight-icon-chevron-down{transform:rotate(-180deg)}.hds-accordion-item__toggle-content{flex:1}.hds-accordion-item__content{padding:4px 16px 16px}.hds-alert{display:flex;align-items:flex-start}.hds-alert__icon{flex:none;width:20px;height:20px;margin-right:12px}.hds-alert__content{flex:1 1 auto}.hds-alert__text{display:flex;flex-direction:column;gap:4px;justify-content:center;color:var(--token-color-foreground-warning-on-surface)}.hds-alert__description{word-break:break-word}.hds-alert__description strong{font-weight:var(--token-typography-font-weight-semibold)}.hds-alert__description code,.hds-alert__description pre{display:inline;padding:1px 5px;font-size:.9em;font-family:var(--token-typography-code-100-font-family);line-height:1em;background-color:var(--token-color-surface-primary);border:1px solid var(--token-color-palette-neutral-200);border-radius:5px}.hds-alert__description a:not([class*=hds-]){color:var(--token-color-foreground-strong)}.hds-alert__description a:not([class*=hds-]):focus,.hds-alert__description a:not([class*=hds-]):focus-visible{text-decoration:none;outline:var(--token-color-focus-action-internal) solid 2px;outline-offset:1px}.hds-alert__description a:not([class*=hds-]):hover{color:var(--token-color-foreground-primary)}.hds-alert--color-neutral .hds-alert__icon,.hds-alert__description a:not([class*=hds-]):active{color:var(--token-color-foreground-faint)}.hds-alert__actions{display:flex;gap:16px;align-items:center}.hds-alert__actions>*{margin-top:16px}.hds-alert__dismiss{margin-top:2px;margin-left:16px}.hds-alert--type-compact .hds-alert__dismiss{margin-top:1px}.hds-alert--type-page{padding:16px 48px}.hds-alert--type-inline{padding:16px;border-style:solid;border-width:1px;border-radius:6px}.hds-alert--type-compact .hds-alert__icon{width:14px;height:14px;margin-top:2px;margin-right:8px}.hds-alert--type-compact .hds-alert__title{display:none}.hds-alert--type-compact .hds-alert__title+.hds-alert__description{margin-top:0}.hds-alert--color-neutral.hds-alert--type-page{background-color:var(--token-color-surface-faint);box-shadow:0 1px 0 0 var(--token-color-palette-alpha-300)}.hds-alert--color-neutral.hds-alert--type-inline{background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-strong)}.hds-alert--color-neutral .hds-alert__title{color:var(--token-color-foreground-primary)}.hds-alert--color-highlight.hds-alert--type-page{background-color:var(--token-color-surface-highlight);box-shadow:0 1px 0 0 var(--token-color-border-highlight)}.hds-alert--color-highlight.hds-alert--type-inline{background-color:var(--token-color-surface-highlight);border-color:var(--token-color-border-highlight)}.hds-alert--color-highlight .hds-alert__icon,.hds-alert--color-highlight .hds-alert__title{color:var(--token-color-foreground-highlight-on-surface)}.hds-alert--color-success.hds-alert--type-page{background-color:var(--token-color-surface-success);box-shadow:0 1px 0 0 var(--token-color-border-success)}.hds-alert--color-success.hds-alert--type-inline{background-color:var(--token-color-surface-success);border-color:var(--token-color-border-success)}.hds-alert--color-success .hds-alert__icon,.hds-alert--color-success .hds-alert__title{color:var(--token-color-foreground-success-on-surface)}.hds-alert--color-warning.hds-alert--type-page{background-color:var(--token-color-surface-warning);box-shadow:0 1px 0 0 var(--token-color-border-warning)}.hds-alert--color-warning.hds-alert--type-inline{background-color:var(--token-color-surface-warning);border-color:var(--token-color-border-warning)}.hds-alert--color-warning .hds-alert__icon,.hds-alert--color-warning .hds-alert__title{color:var(--token-color-foreground-warning-on-surface)}.hds-alert--color-critical.hds-alert--type-page{background-color:var(--token-color-surface-critical);box-shadow:0 1px 0 0 var(--token-color-border-critical)}.hds-alert--color-critical.hds-alert--type-inline{background-color:var(--token-color-surface-critical);border-color:var(--token-color-border-critical)}.hds-alert--color-critical .hds-alert__icon,.hds-alert--color-critical .hds-alert__title{color:var(--token-color-foreground-critical-on-surface)}.hds-app-footer{display:flex;flex-wrap:wrap;gap:24px;justify-content:flex-end;padding:24px;color:var(--app-footer-foreground-color);border-top:1px solid var(--app-footer-border-top-color)}.hds-app-footer__list:not(:has(li)){display:none}.hds-app-footer__legal-links,.hds-app-footer__list{display:flex;flex-wrap:wrap;gap:24px;align-items:center;justify-content:flex-end;width:-moz-fit-content;width:fit-content;min-width:0;margin:0;padding:0;list-style-type:none}.hds-app-footer__status-link.hds-link-inline--icon-leading>.hds-link-inline__icon{margin-right:6px}.hds-app-footer__status-link .flight-icon{fill:var(--hds-app-footer-status-icon-color,currentColor)}.hds-app-footer__status-link--operational .flight-icon{fill:var(--app-footer-status-link-icon-operational-color)}.hds-app-footer__status-link--degraded .flight-icon{fill:var(--app-footer-status-link-icon-degraded-color)}.hds-app-footer__status-link--maintenance .flight-icon{fill:var(--app-footer-status-link-icon-maintenance-color)}.hds-app-footer__status-link--critical .flight-icon{fill:var(--app-footer-status-link-icon-critical-color)}.hds-app-footer__link.hds-link-inline--color-secondary,.hds-app-footer__status-link{color:var(--app-footer-link-default-color);text-align:right}.hds-app-footer__link.hds-link-inline--color-secondary.mock-hover,.hds-app-footer__link.hds-link-inline--color-secondary:hover,.hds-app-footer__status-link.mock-hover,.hds-app-footer__status-link:hover{color:var(--app-footer-link-hover-color)}.hds-app-footer__link.hds-link-inline--color-secondary.mock-active,.hds-app-footer__link.hds-link-inline--color-secondary:active,.hds-app-footer__status-link.mock-active,.hds-app-footer__status-link:active{color:var(--app-footer-link-active-color)}.hds-app-footer__link.hds-link-inline--color-secondary.mock-focus,.hds-app-footer__link.hds-link-inline--color-secondary:focus,.hds-app-footer__link.hds-link-inline--color-secondary:focus-visible,.hds-app-footer__status-link.mock-focus,.hds-app-footer__status-link:focus,.hds-app-footer__status-link:focus-visible{color:var(--app-footer-link-focus-color);outline-color:var(--app-footer-link-focus-outline-color)}.hds-app-footer__list-item{display:flex;align-items:center}.hds-app-footer__copyright{display:flex;gap:6px;align-items:center;color:var(--app-footer-copyright-text-color)}.hds-app-footer__copyright .flight-icon{fill:var(--app-footer-copyright-icon-color)}.hds-app-footer--theme-light{--app-footer-foreground-color:var(--token-color-foreground-primary);--app-footer-border-top-color:var(--token-color-border-primary);--app-footer-link-default-color:var(--token-color-foreground-faint);--app-footer-link-hover-color:var(--token-color-palette-neutral-600);--app-footer-link-active-color:var(--token-color-palette-neutral-700);--app-footer-link-focus-color:var(--token-color-foreground-faint);--app-footer-link-focus-outline-color:var(--token-color-focus-action-internal);--app-footer-copyright-text-color:var(--token-color-foreground-primary);--app-footer-copyright-icon-color:var(--token-color-hashicorp-brand);--app-footer-status-link-icon-operational-color:var(--token-color-foreground-success);--app-footer-status-link-icon-degraded-color:var(--token-color-foreground-warning);--app-footer-status-link-icon-maintenance-color:var(--token-color-foreground-warning);--app-footer-status-link-icon-critical-color:var(--token-color-foreground-critical)}.hds-app-footer--theme-dark{--app-footer-foreground-color:#b2b6bd;--app-footer-border-top-color:#b2b6bd66;--app-footer-link-default-color:#b2b6bd;--app-footer-link-hover-color:#d5d7db;--app-footer-link-active-color:#efeff1;--app-footer-link-focus-color:#b2b6bd;--app-footer-link-focus-outline-color:#389aff;--app-footer-copyright-text-color:#b2b6bd;--app-footer-copyright-icon-color:#fff;--app-footer-status-link-icon-operational-color:#009241;--app-footer-status-link-icon-degraded-color:#e88c03;--app-footer-status-link-icon-maintenance-color:#e88c03;--app-footer-status-link-icon-critical-color:#ef3016}.hds-app-frame{display:grid;grid-template-areas:"header header" "sidebar main" "sidebar footer";grid-template-rows:auto 1fr auto;grid-template-columns:auto 1fr;min-height:100vh}.hds-app-frame__modals:empty,button.hds-button[href] .hds-button__text,button.hds-button[href]::before{display:none}.hds-app-frame__header{z-index:7;grid-area:header}.hds-app-frame__sidebar{z-index:6;grid-area:sidebar}.hds-app-frame__main{grid-area:main}.hds-app-frame__footer{grid-area:footer}.hds-app-frame__modals{position:fixed;top:0;left:0;z-index:100;width:100vw;height:100vh;pointer-events:none}.hds-application-state{width:19.5rem;max-width:100%;margin:0 auto}.hds-application-state__header{display:grid;grid-template-columns:min-content 1fr;align-items:start;color:var(--token-color-foreground-faint)}.hds-application-state__icon{margin-right:8px;padding-top:4px}.hds-application-state__error-code,.hds-application-state__title{grid-column-start:2}.hds-application-state__body{padding:12px 0;color:var(--token-color-foreground-faint)}.hds-application-state__footer{display:flex;gap:8px;justify-content:space-between}.hds-application-state__footer.hds-application-state__footer--has-divider{padding:12px 0;border-top:1px solid var(--token-color-border-strong)}.hds-avatar{display:inline-flex;align-items:center;justify-content:center;box-sizing:border-box;width:32px;height:32px}.hds-avatar svg,.hds-dropdown--is-inline,.hds-form-toggle{display:inline-block}.hds-avatar img{width:inherit;height:inherit;border-radius:2px}.hds-badge{display:inline-flex;align-items:center;max-width:100%;vertical-align:middle;border:1px solid transparent;border-radius:5px}.hds-badge__icon{display:block;flex:0 0 auto}.hds-badge__text{flex:1 0 0;font-weight:var(--token-typography-font-weight-medium)}.hds-badge--size-small{gap:.25rem;min-height:1.25rem;padding:calc(.125rem - 1px) calc(.375rem - 1px)}.hds-badge--size-small .hds-badge__icon{width:.75rem;height:.75rem}.hds-badge--size-large .hds-badge__icon,.hds-badge--size-medium .hds-badge__icon{width:1rem;height:1rem}.hds-badge--size-small .hds-badge__text{font-size:.8125rem;line-height:1.2308}.hds-badge--size-medium{gap:.25rem;min-height:1.5rem;padding:calc(.25rem - 1px) calc(.5rem - 1px)}.hds-badge--size-medium .hds-badge__text{font-size:.8125rem;line-height:1.2308}.hds-badge--size-large{gap:.375rem;min-height:2rem;padding:calc(.25rem - 1px) calc(.5rem - 1px)}.hds-badge--size-large .hds-badge__text{font-size:1rem;line-height:1.5}.hds-badge--color-neutral.hds-badge--type-filled{color:var(--token-color-foreground-primary)}.hds-badge--color-neutral.hds-badge--type-inverted{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-faint)}.hds-badge--color-neutral.hds-badge--type-outlined{color:var(--token-color-foreground-primary);background-color:transparent;border-color:var(--token-color-foreground-faint)}.hds-badge--color-neutral-dark-mode.hds-badge--type-filled{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-faint)}.hds-badge--color-neutral-dark-mode.hds-badge--type-inverted{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint)}.hds-badge--color-neutral-dark-mode.hds-badge--type-outlined{color:var(--token-color-foreground-high-contrast);background-color:transparent;border-color:var(--token-color-palette-neutral-100)}.hds-badge--color-highlight.hds-badge--type-filled{color:var(--token-color-foreground-highlight-on-surface);background-color:var(--token-color-surface-highlight)}.hds-badge--color-highlight.hds-badge--type-inverted{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-highlight)}.hds-badge--color-highlight.hds-badge--type-outlined{color:var(--token-color-foreground-highlight);background-color:transparent;border-color:currentColor}.hds-badge--color-success.hds-badge--type-filled{color:var(--token-color-foreground-success-on-surface);background-color:var(--token-color-surface-success)}.hds-badge--color-success.hds-badge--type-inverted{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-success)}.hds-badge--color-success.hds-badge--type-outlined{color:var(--token-color-foreground-success);background-color:transparent;border-color:currentColor}.hds-badge--color-warning.hds-badge--type-filled{color:var(--token-color-foreground-warning-on-surface);background-color:var(--token-color-surface-warning)}.hds-badge--color-warning.hds-badge--type-inverted{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-warning)}.hds-badge--color-warning.hds-badge--type-outlined{color:var(--token-color-foreground-warning);background-color:transparent;border-color:currentColor}.hds-badge--color-critical.hds-badge--type-filled{color:var(--token-color-foreground-critical-on-surface);background-color:var(--token-color-surface-critical)}.hds-badge--color-critical.hds-badge--type-inverted{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-critical)}.hds-badge--color-critical.hds-badge--type-outlined{color:var(--token-color-foreground-critical);background-color:transparent;border-color:currentColor}.hds-badge-count{display:inline-flex;align-items:center;max-width:100%;font-weight:var(--token-typography-font-weight-medium);border:1px solid transparent}.hds-button,.hds-dropdown-toggle-button{font-weight:var(--token-typography-font-weight-regular)}.hds-badge-count--size-small{min-height:1.25rem;padding:calc(.125rem - 1px) calc(.5rem - 1px);font-size:.8125rem;line-height:1.2308;border-radius:.625rem}.hds-badge-count--size-medium{min-height:1.5rem;padding:calc(.25rem - 1px) calc(.75rem - 1px);font-size:.8125rem;line-height:1.2308;border-radius:.75rem}.hds-badge-count--size-large{min-height:2rem;padding:calc(.25rem - 1px) calc(.875rem - 1px);font-size:1rem;line-height:1.5;border-radius:1rem}.hds-breadcrumb__list,.hds-breadcrumb__sublist{margin:0;padding:0;list-style:none}.hds-badge-count--color-neutral.hds-badge-count--type-filled{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-strong)}.hds-badge-count--color-neutral.hds-badge-count--type-inverted{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-faint)}.hds-badge-count--color-neutral.hds-badge-count--type-outlined{color:var(--token-color-foreground-primary);background-color:transparent;border-color:var(--token-color-foreground-faint)}.hds-badge-count--color-neutral-dark-mode.hds-badge-count--type-filled{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-foreground-faint)}.hds-badge-count--color-neutral-dark-mode.hds-badge-count--type-inverted{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint)}.hds-badge-count--color-neutral-dark-mode.hds-badge-count--type-outlined{color:var(--token-color-foreground-high-contrast);background-color:transparent;border-color:var(--token-color-palette-neutral-100)}.hds-breadcrumb{position:relative}.hds-breadcrumb__list{display:flex}.hds-breadcrumb--items-can-wrap .hds-breadcrumb__list{flex-wrap:wrap}.hds-breadcrumb__item{position:relative;display:flex;flex-direction:row;align-items:center;min-width:0}.hds-breadcrumb__list>.hds-breadcrumb__item:not(:last-child)::after{padding:0 8px;color:var(--token-color-palette-neutral-300);content:"/"}.hds-breadcrumb__sublist>.hds-breadcrumb__item+.hds-breadcrumb__item{margin-top:4px}.hds-breadcrumb__current,.hds-breadcrumb__link{margin:0 -4px;padding:0 4px;display:flex;min-width:0}.hds-breadcrumb__item--is-truncation{flex:none}.hds-breadcrumb__link{flex-direction:row;align-items:center;color:var(--token-color-foreground-faint);border-radius:5px;text-decoration-color:transparent;outline-style:solid;outline-color:transparent}.hds-breadcrumb__link.mock-active>.hds-breadcrumb__text,.hds-breadcrumb__link.mock-hover>.hds-breadcrumb__text,.hds-breadcrumb__link:active>.hds-breadcrumb__text,.hds-breadcrumb__link:hover>.hds-breadcrumb__text{text-decoration-color:currentColor}.hds-breadcrumb__link.mock-hover,.hds-breadcrumb__link:hover{color:var(--token-color-palette-neutral-600)}.hds-breadcrumb__link:focus:not(:focus-visible){box-shadow:none}.hds-breadcrumb__link:focus-visible{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-breadcrumb__link.mock-focus.mock-active,.hds-breadcrumb__link:focus:active{box-shadow:none}.hds-breadcrumb__link.mock-active,.hds-breadcrumb__link:active{color:var(--token-color-foreground-secondary)}.hds-breadcrumb__current{flex-direction:row;align-items:center;color:var(--token-color-foreground-strong)}.hds-breadcrumb__icon{flex:none;width:13px;height:13px;margin-right:6px}.hds-breadcrumb__text{padding:calc((28px - 1rem)/ 2) 0;font-size:.8125rem;line-height:1rem;white-space:nowrap;text-decoration:underline;text-overflow:ellipsis;text-decoration-color:transparent}.hds-breadcrumb__sublist .hds-breadcrumb__text{white-space:normal}.hds-breadcrumb__truncation-toggle{display:flex;flex:none;align-items:center;justify-content:center;width:28px;height:28px;margin:0 -4px;padding:0;color:var(--token-color-foreground-faint);background-color:transparent;border:1px solid transparent;border-radius:5px;outline:transparent solid 0;cursor:pointer}.hds-button,.hds-copy-snippet{align-items:center;isolation:isolate}.hds-button,.hds-copy-snippet,.hds-dismiss-button,.hds-dropdown-toggle-button,.hds-dropdown-toggle-icon{outline-style:solid;outline-color:transparent}.hds-breadcrumb__truncation-toggle.mock-hover,.hds-breadcrumb__truncation-toggle:hover{color:var(--token-color-foreground-faint);border-color:var(--token-color-border-strong)}.hds-breadcrumb__truncation-toggle.mock-focus,.hds-breadcrumb__truncation-toggle:focus{box-shadow:var(--token-focus-ring-action-box-shadow);background-color:transparent;border:none}.hds-breadcrumb__truncation-toggle:focus:not(:focus-visible){box-shadow:none}.hds-breadcrumb__truncation-toggle:focus-visible,.hds-copy-snippet.mock-focus::before,.hds-copy-snippet:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-breadcrumb__truncation-toggle.mock-focus.mock-active,.hds-breadcrumb__truncation-toggle:focus:active{box-shadow:none}.hds-breadcrumb__truncation-toggle.mock-active,.hds-breadcrumb__truncation-toggle:active{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong)}.hds-breadcrumb__truncation-content{position:absolute;top:100%;left:-4px;z-index:300;width:-moz-max-content;width:max-content;max-width:200px;margin-top:4px;padding:6px 12px;background-color:var(--token-color-surface-primary);border-radius:6px;box-shadow:var(--token-surface-high-box-shadow)}.hds-button.hds-button--width-full,.hds-copy-snippet--is-truncated,.hds-copy-snippet--width-full,.hds-dropdown-toggle-button--width-full{max-width:100%;width:100%}.hds-button--size-small,.hds-dropdown-toggle-button--size-small{padding:.375rem .6875rem;min-height:1.75rem}.hds-button{position:relative;display:flex;gap:.375rem;justify-content:center;width:auto;text-decoration:none;border:1px solid transparent;border-radius:5px}a.hds-button{width:-moz-fit-content;width:fit-content}a.hds-button.mock-active,a.hds-button.mock-focus,a.hds-button.mock-hover,a.hds-button:active,a.hds-button:focus,a.hds-button:hover{text-decoration:underline}.hds-button.mock-disabled,.hds-button.mock-disabled:focus,.hds-button.mock-disabled:hover,.hds-button:disabled,.hds-button:disabled:focus,.hds-button:disabled:hover,.hds-button[disabled],.hds-button[disabled]:focus,.hds-button[disabled]:hover{color:var(--token-color-foreground-disabled);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary);box-shadow:none;cursor:not-allowed}.hds-button.mock-disabled::before,.hds-button.mock-disabled:focus::before,.hds-button.mock-disabled:hover::before,.hds-button:disabled::before,.hds-button:disabled:focus::before,.hds-button:disabled:hover::before,.hds-button[disabled]::before,.hds-button[disabled]:focus::before,.hds-button[disabled]:hover::before{border-color:transparent}.hds-button.hds-button--width-full .hds-button__text{flex:0 0 auto}.hds-button__text,.hds-copy-snippet__text,.hds-form-group--radio-cards .hds-form-radio-card--has-fluid-width{flex:1 0 0}.hds-button.mock-focus,.hds-button:focus{box-shadow:none}.hds-button.mock-focus::before,.hds-button:focus::before{position:absolute;top:-4px;right:-4px;bottom:-4px;left:-4px;z-index:-1;border:3px solid transparent;border-radius:8px;content:""}.hds-button__text{text-align:center}.hds-button--size-small .hds-button__icon{width:.75rem;height:.75rem}.hds-button--size-small .hds-button__text{font-size:.8125rem;line-height:.875rem}.hds-button--size-small.hds-button--is-icon-only{min-width:1.75rem;padding-right:.375rem;padding-left:.375rem}.hds-button--size-medium{min-height:2.25rem;padding:.5625rem .9375rem}.hds-button--size-medium .hds-button__icon{width:1rem;height:1rem}.hds-button--size-medium .hds-button__text{font-size:.875rem;line-height:1rem}.hds-button--size-medium.hds-button--is-icon-only{min-width:2.25rem;padding-right:.5625rem;padding-left:.5625rem}.hds-button--size-large{min-height:3rem;padding:.6875rem 1.1875rem}.hds-button--size-large .hds-button__icon{width:1.5rem;height:1.5rem}.hds-button--size-large .hds-button__text{font-size:1rem;line-height:1.5rem}.hds-button--size-large.hds-button--is-icon-only{min-width:3rem;padding-right:.6875rem;padding-left:.6875rem}.hds-button--color-primary{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-200);border-color:var(--token-color-palette-blue-300);box-shadow:var(--token-elevation-low-box-shadow)}.hds-button--color-primary.mock-hover,.hds-button--color-primary:hover{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-300);border-color:var(--token-color-palette-blue-400);cursor:pointer}.hds-button--color-primary.mock-focus,.hds-button--color-primary:focus{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-200);border-color:var(--token-color-focus-action-internal)}.hds-button--color-primary.mock-focus::before,.hds-button--color-primary:focus::before{top:-6px;right:-6px;bottom:-6px;left:-6px;border-color:var(--token-color-focus-action-external);border-radius:10px}.hds-button--color-primary.mock-active,.hds-button--color-primary:active{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-400);border-color:var(--token-color-palette-blue-400);box-shadow:none}.hds-button--color-primary.mock-active::before,.hds-button--color-primary:active::before{border-color:transparent}.hds-button--color-secondary{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-strong);box-shadow:var(--token-elevation-low-box-shadow)}.hds-button--color-secondary.mock-hover,.hds-button--color-secondary:hover{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-primary);border-color:var(--token-color-border-strong);cursor:pointer}.hds-button--color-secondary.mock-focus,.hds-button--color-secondary:focus{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-focus-action-internal)}.hds-button--color-secondary.mock-focus::before,.hds-button--color-secondary:focus::before{border-color:var(--token-color-focus-action-external)}.hds-button--color-secondary.mock-active,.hds-button--color-secondary:active{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong);box-shadow:none}.hds-button--color-secondary.mock-active::before,.hds-button--color-secondary:active::before{border-color:transparent}.hds-button--color-tertiary{color:var(--token-color-foreground-action);background-color:transparent;border-color:transparent}.hds-button--color-tertiary.mock-hover,.hds-button--color-tertiary:hover{color:var(--token-color-foreground-action-hover);background-color:var(--token-color-surface-primary);border-color:var(--token-color-border-strong);cursor:pointer}.hds-button--color-tertiary.mock-focus,.hds-button--color-tertiary:focus{color:var(--token-color-foreground-action);border-color:var(--token-color-focus-action-internal)}.hds-button--color-tertiary.mock-focus::before,.hds-button--color-tertiary:focus::before{border-color:var(--token-color-focus-action-external)}.hds-button--color-tertiary.mock-active,.hds-button--color-tertiary:active{color:var(--token-color-foreground-action-active);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong);box-shadow:none}.hds-button--color-tertiary.mock-active::before,.hds-button--color-tertiary:active::before{border-color:transparent}.hds-button--color-tertiary.mock-disabled,.hds-button--color-tertiary.mock-disabled:focus,.hds-button--color-tertiary.mock-disabled:hover,.hds-button--color-tertiary:disabled,.hds-button--color-tertiary:disabled:focus,.hds-button--color-tertiary:disabled:hover,.hds-button--color-tertiary[disabled],.hds-button--color-tertiary[disabled]:focus,.hds-button--color-tertiary[disabled]:hover{background-color:transparent;border-color:transparent}.hds-button--color-tertiary.mock-disabled::before,.hds-button--color-tertiary.mock-disabled:focus::before,.hds-button--color-tertiary.mock-disabled:hover::before,.hds-button--color-tertiary:disabled::before,.hds-button--color-tertiary:disabled:focus::before,.hds-button--color-tertiary:disabled:hover::before,.hds-button--color-tertiary[disabled]::before,.hds-button--color-tertiary[disabled]:focus::before,.hds-button--color-tertiary[disabled]:hover::before{border-color:transparent}.hds-button--color-critical{color:var(--token-color-foreground-critical-on-surface);background-color:var(--token-color-surface-critical);border-color:var(--token-color-foreground-critical-on-surface);box-shadow:var(--token-elevation-low-box-shadow)}.hds-button--color-critical.mock-hover,.hds-button--color-critical:hover{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-red-300);border-color:var(--token-color-palette-red-400);cursor:pointer}.hds-button--color-critical.mock-focus,.hds-button--color-critical:focus{color:var(--token-color-foreground-critical-on-surface);background-color:var(--token-color-surface-critical);border-color:var(--token-color-focus-critical-internal)}.hds-button--color-critical.mock-focus::before,.hds-button--color-critical:focus::before{border-color:var(--token-color-focus-critical-external)}.hds-button--color-critical.mock-active,.hds-button--color-critical:active{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-red-400);border-color:var(--token-color-palette-red-400);box-shadow:none}.hds-button--color-critical.mock-active::before,.hds-button--color-critical:active::before{border-color:transparent}button.hds-button[href]{color:#fff!important;background-color:red!important;border:none}button.hds-button[href]::after{content:' Attention: you’re passing a "href" attribute to the "Hds::Button" component, you should use an "@href" argument.'}.hds-button-set{display:flex;gap:16px}.hds-card__container{position:relative;background-color:#fff;border-radius:6px}.hds-card__container--level-surface-base{box-shadow:var(--token-surface-base-box-shadow)}.hds-card__container--level-surface-mid{box-shadow:var(--token-surface-mid-box-shadow)}.hds-card__container--level-surface-high{box-shadow:var(--token-surface-high-box-shadow)}.hds-card__container--hover-level-surface-base.mock-hover,.hds-card__container--hover-level-surface-base:hover{box-shadow:var(--token-surface-base-box-shadow)}.hds-card__container--hover-level-surface-mid.mock-hover,.hds-card__container--hover-level-surface-mid:hover{box-shadow:var(--token-surface-mid-box-shadow)}.hds-card__container--hover-level-surface-high.mock-hover,.hds-card__container--hover-level-surface-high:hover{box-shadow:var(--token-surface-high-box-shadow)}.hds-card__container--active-level-surface-base.mock-active,.hds-card__container--active-level-surface-base:active{box-shadow:var(--token-surface-base-box-shadow)}.hds-card__container--active-level-surface-mid.mock-active,.hds-card__container--active-level-surface-mid:active{box-shadow:var(--token-surface-mid-box-shadow)}.hds-card__container--active-level-surface-high.mock-active,.hds-card__container--active-level-surface-high:active{box-shadow:var(--token-surface-high-box-shadow)}.hds-card__container--level-elevation-base{box-shadow:var(--token-elevation-base-box-shadow)}.hds-card__container--level-elevation-mid{box-shadow:var(--token-elevation-mid-box-shadow)}.hds-card__container--level-elevation-high{box-shadow:var(--token-elevation-high-box-shadow)}.hds-card__container--hover-level-elevation-base.mock-hover,.hds-card__container--hover-level-elevation-base:hover{box-shadow:var(--token-elevation-base-box-shadow)}.hds-card__container--hover-level-elevation-mid.mock-hover,.hds-card__container--hover-level-elevation-mid:hover{box-shadow:var(--token-elevation-mid-box-shadow)}.hds-card__container--hover-level-elevation-high.mock-hover,.hds-card__container--hover-level-elevation-high:hover{box-shadow:var(--token-elevation-high-box-shadow)}.hds-card__container--active-level-elevation-base.mock-active,.hds-card__container--active-level-elevation-base:active{box-shadow:var(--token-elevation-base-box-shadow)}.hds-card__container--active-level-elevation-mid.mock-active,.hds-card__container--active-level-elevation-mid:active{box-shadow:var(--token-elevation-mid-box-shadow)}.hds-card__container--active-level-elevation-high.mock-active,.hds-card__container--active-level-elevation-high:active{box-shadow:var(--token-elevation-high-box-shadow)}.hds-card__container--background-neutral-primary{background-color:var(--token-color-surface-primary)}.hds-card__container--background-neutral-secondary{background-color:var(--token-color-surface-faint)}.hds-card__container--overflow-visible{overflow:visible}.hds-copy-button{cursor:pointer}.hds-copy-button .hds-button__icon{color:var(--token-color-foreground-action)}.hds-copy-button.hds-copy-button--status-success .hds-button__icon{color:var(--token-color-foreground-success)}.hds-copy-button.hds-copy-button--status-error .hds-button__icon{color:var(--token-color-foreground-critical)}.hds-copy-snippet{position:relative;display:flex;gap:8px;justify-content:space-between;padding:6px 4px;text-align:left;border:1px solid transparent;border-radius:5px;cursor:pointer}.hds-copy-snippet::before,.hds-dismiss-button::before{position:absolute;z-index:-1;content:""}.hds-copy-snippet::before{top:0;right:0;bottom:0;left:0;border-radius:5px}.hds-copy-snippet:focus:not(:focus-visible)::before{box-shadow:none}.hds-copy-snippet:focus-visible::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-copy-snippet.mock-focus.mock-active::before,.hds-copy-snippet:focus:active::before{box-shadow:none}.hds-copy-snippet--color-primary{color:var(--token-color-foreground-action);background-color:transparent}.hds-copy-snippet--color-primary.mock-hover,.hds-copy-snippet--color-primary:hover{color:var(--token-color-foreground-action-hover);background-color:var(--token-color-surface-interactive);border-color:var(--token-color-border-strong)}.hds-copy-snippet--color-primary.mock-active,.hds-copy-snippet--color-primary:active{color:var(--token-color-foreground-action-active);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong)}.hds-copy-snippet--color-primary:focus{background-color:transparent}.hds-copy-snippet--color-secondary{color:var(--token-color-foreground-primary);background-color:transparent}.hds-copy-snippet--color-secondary.mock-hover,.hds-copy-snippet--color-secondary:hover{background-color:var(--token-color-surface-interactive);border-color:var(--token-color-border-strong)}.hds-copy-snippet--color-secondary.mock-active,.hds-copy-snippet--color-secondary:active{background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong)}.hds-copy-snippet--status-error,.hds-copy-snippet--status-success{background-color:var(--token-color-surface-interactive)}.hds-copy-snippet--color-secondary .hds-copy-snippet__icon{color:var(--token-color-foreground-action)}.hds-copy-snippet--color-secondary .hds-copy-snippet__icon:hover{color:var(--token-color-foreground-action-hover)}.hds-copy-snippet--color-secondary .hds-copy-snippet__icon:active{color:var(--token-color-foreground-action-active)}.hds-copy-snippet--color-secondary .hds-copy-snippet__icon:focus{color:var(--token-color-foreground-action)}.hds-copy-snippet--status-success .hds-copy-snippet__icon{color:var(--token-color-foreground-success)}.hds-copy-snippet--status-error .hds-copy-snippet__icon{color:var(--token-color-foreground-critical)}.hds-copy-snippet__icon{flex:none}.hds-copy-snippet--is-truncated .hds-copy-snippet__text{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.hds-disclosure-primitive{position:relative}.hds-dismiss-button{flex:none;padding:0;color:var(--token-color-foreground-faint);background-color:transparent;border:none;cursor:pointer;position:relative;isolation:isolate}.hds-dismiss-button.mock-hover::before,.hds-dismiss-button:hover::before{background-color:rgba(222,223,227,.4)}.hds-dismiss-button::before{top:-4px;right:-4px;bottom:-4px;left:-4px;border-radius:5px}.hds-dismiss-button.mock-focus::before,.hds-dismiss-button:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-dismiss-button:focus:not(:focus-visible)::before{box-shadow:none}.hds-dismiss-button:focus-visible::before,.hds-dropdown-toggle-icon.mock-focus::before,.hds-dropdown-toggle-icon:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-dismiss-button.mock-focus.mock-active::before,.hds-dismiss-button:focus:active::before{box-shadow:none}.hds-dismiss-button.mock-active,.hds-dismiss-button:active{color:var(--token-color-foreground-secondary)}.hds-dismiss-button.mock-active::before,.hds-dismiss-button:active::before{background-color:rgba(222,223,227,.4);border:1px solid var(--token-color-border-strong)}.hds-dropdown--is-inline .hds-dropdown-toggle-button,.hds-dropdown--is-inline .hds-dropdown-toggle-icon{display:inline-flex}.hds-dropdown-toggle-icon{display:flex;gap:2px;align-items:center;justify-content:center;padding:1px;background-color:var(--token-color-surface-faint);border:1px solid var(--token-color-border-strong);border-radius:5px;position:relative;isolation:isolate}.hds-dropdown-toggle-button,.hds-link-standalone{gap:.375rem;font-family:var(--token-typography-font-stack-text);isolation:isolate}.hds-dropdown-toggle-icon.mock-hover,.hds-dropdown-toggle-icon:hover{background-color:var(--token-color-surface-interactive);cursor:pointer}.hds-dropdown-toggle-icon::before{position:absolute;top:-1px;right:-1px;bottom:-1px;left:-1px;z-index:-1;border-radius:5px;content:""}.hds-dropdown-toggle-icon:focus:not(:focus-visible)::before{box-shadow:none}.hds-dropdown-toggle-icon:focus-visible::before,.hds-link-standalone.mock-focus::before,.hds-link-standalone:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-dropdown-toggle-icon.mock-focus.mock-active::before,.hds-dropdown-toggle-icon:focus:active::before{box-shadow:none}.hds-dropdown-toggle-icon.mock-active,.hds-dropdown-toggle-icon:active{background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong)}.hds-dropdown-toggle-icon.mock-disabled,.hds-dropdown-toggle-icon:disabled{color:var(--token-color-foreground-disabled);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary);box-shadow:none;cursor:not-allowed}.hds-dropdown-toggle-icon.mock-disabled::before,.hds-dropdown-toggle-icon:disabled::before{border-color:transparent}.hds-dropdown-toggle-icon__wrapper{display:flex;align-items:center;justify-content:center;border-radius:3px}.hds-dropdown-toggle-icon__wrapper img{width:100%;height:100%;-o-object-fit:cover;object-fit:cover;border-radius:inherit}.hds-dropdown-toggle-icon--size-small .hds-dropdown-toggle-icon__wrapper{width:24px;height:24px}.hds-dropdown-toggle-icon--size-medium .hds-dropdown-toggle-icon__wrapper{width:32px;height:32px}.hds-dropdown-toggle-button{position:relative;display:flex;align-items:center;justify-content:center;width:auto;text-decoration:none;border:1px solid transparent;border-radius:5px}.hds-dropdown-toggle-button.mock-focus,.hds-dropdown-toggle-button:focus{box-shadow:none}.hds-dropdown-toggle-button.mock-focus::before,.hds-dropdown-toggle-button:focus::before{position:absolute;top:-4px;right:-4px;bottom:-4px;left:-4px;z-index:-1;border:3px solid transparent;border-radius:8px;content:""}.hds-dropdown-toggle-button.mock-disabled,.hds-dropdown-toggle-button.mock-disabled:focus,.hds-dropdown-toggle-button.mock-disabled:hover,.hds-dropdown-toggle-button:disabled,.hds-dropdown-toggle-button:disabled:focus,.hds-dropdown-toggle-button:disabled:hover{color:var(--token-color-foreground-disabled);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary);box-shadow:none;cursor:not-allowed}.hds-dropdown-toggle-button.mock-disabled::before,.hds-dropdown-toggle-button.mock-disabled:focus::before,.hds-dropdown-toggle-button.mock-disabled:hover::before,.hds-dropdown-toggle-button:disabled::before,.hds-dropdown-toggle-button:disabled:focus::before,.hds-dropdown-toggle-button:disabled:hover::before{border-color:transparent}.hds-dropdown-toggle-button.mock-disabled .hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button.mock-disabled .hds-dropdown-toggle-button__count,.hds-dropdown-toggle-button.mock-disabled:focus .hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button.mock-disabled:focus .hds-dropdown-toggle-button__count,.hds-dropdown-toggle-button.mock-disabled:hover .hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button.mock-disabled:hover .hds-dropdown-toggle-button__count,.hds-dropdown-toggle-button:disabled .hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button:disabled .hds-dropdown-toggle-button__count,.hds-dropdown-toggle-button:disabled:focus .hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button:disabled:focus .hds-dropdown-toggle-button__count,.hds-dropdown-toggle-button:disabled:hover .hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button:disabled:hover .hds-dropdown-toggle-button__count{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-strong)}.hds-dropdown-toggle-button--size-small .hds-dropdown-toggle-button__icon{width:.75rem;height:.75rem}.hds-dropdown-toggle-button--size-small .hds-dropdown-toggle-button__text{font-size:.8125rem;line-height:.875rem}.hds-dropdown-toggle-button--size-small.hds-dropdown-toggle-button--is-icon-only{min-width:1.75rem;padding-right:.375rem;padding-left:.375rem}.hds-dropdown-toggle-button--size-medium{min-height:2.25rem;padding:.5625rem .9375rem}.hds-dropdown-toggle-button--size-medium .hds-dropdown-toggle-button__icon{width:1rem;height:1rem}.hds-dropdown-toggle-button--size-medium .hds-dropdown-toggle-button__text{font-size:.875rem;line-height:1rem}.hds-dropdown-toggle-button--size-medium.hds-dropdown-toggle-button--is-icon-only{min-width:2.25rem;padding-right:.5625rem;padding-left:.5625rem}.hds-dropdown-toggle-button--size-large{min-height:3rem;padding:.6875rem 1.1875rem}.hds-dropdown-toggle-button--size-large .hds-dropdown-toggle-button__icon{width:1.5rem;height:1.5rem}.hds-dropdown-toggle-button--size-large .hds-dropdown-toggle-button__text{font-size:1rem;line-height:1.5rem}.hds-dropdown-toggle-button--size-large.hds-dropdown-toggle-button--is-icon-only{min-width:3rem;padding-right:.6875rem;padding-left:.6875rem}.hds-dropdown-toggle-button--size-small{padding-right:.375rem}.hds-dropdown-toggle-button--size-medium{padding-right:.5625rem}.hds-dropdown-toggle-button--color-primary{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-200);border-color:var(--token-color-palette-blue-300);box-shadow:var(--token-elevation-low-box-shadow)}.hds-dropdown-toggle-button--color-primary.mock-hover,.hds-dropdown-toggle-button--color-primary:hover{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-300);border-color:var(--token-color-palette-blue-400);cursor:pointer}.hds-dropdown-toggle-button--color-primary.mock-focus,.hds-dropdown-toggle-button--color-primary:focus{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-200);border-color:var(--token-color-focus-action-internal)}.hds-dropdown-toggle-button--color-primary.mock-focus::before,.hds-dropdown-toggle-button--color-primary:focus::before{top:-6px;right:-6px;bottom:-6px;left:-6px;border-color:var(--token-color-focus-action-external);border-radius:10px}.hds-dropdown-toggle-button--color-primary.mock-active,.hds-dropdown-toggle-button--color-primary:active{color:var(--token-color-foreground-high-contrast);background-color:var(--token-color-palette-blue-400);border-color:var(--token-color-palette-blue-400);box-shadow:none}.hds-dropdown-toggle-button--color-primary.mock-active::before,.hds-dropdown-toggle-button--color-primary:active::before{border-color:transparent}.hds-dropdown-toggle-button--color-secondary{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-strong);box-shadow:var(--token-elevation-low-box-shadow)}.hds-dropdown-toggle-button--color-secondary.mock-hover,.hds-dropdown-toggle-button--color-secondary:hover{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-primary);border-color:var(--token-color-border-strong);cursor:pointer}.hds-dropdown-toggle-button--color-secondary.mock-focus,.hds-dropdown-toggle-button--color-secondary:focus{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-focus-action-internal)}.hds-dropdown-toggle-button--color-secondary.mock-focus::before,.hds-dropdown-toggle-button--color-secondary:focus::before{border-color:var(--token-color-focus-action-external)}.hds-dropdown-toggle-button--color-secondary.mock-active,.hds-dropdown-toggle-button--color-secondary:active{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong);box-shadow:none}.hds-dropdown-toggle-button--color-secondary.mock-active::before,.hds-dropdown-toggle-button--color-secondary:active::before{border-color:transparent}.hds-dropdown-list-item--variant-separator::before,.hds-dropdown__header--with-divider{border-bottom:1px solid var(--token-color-border-primary)}.hds-dropdown-toggle-button--width-full{justify-content:space-between}.hds-dropdown-toggle-button__text{text-align:left}.hds-dropdown-toggle-button__icon{flex:none}.hds-dropdown-toggle-button__badge,.hds-dropdown-toggle-button__count{margin:-3px 0}.hds-dropdown-toggle-chevron{margin-left:auto;padding-left:2px}@media (prefers-reduced-motion:no-preference){.hds-accordion-item__button .flight-icon-chevron-down,.hds-dropdown-toggle-chevron .flight-icon-chevron-down{transition:transform .3s}}.hds-dropdown-toggle-button--is-open .hds-dropdown-toggle-chevron .flight-icon-chevron-down,.hds-dropdown-toggle-icon--is-open .hds-dropdown-toggle-chevron .flight-icon-chevron-down{transform:rotate(-180deg)}.hds-dropdown__content{display:flex;flex-direction:column;width:-moz-max-content;width:max-content;min-width:200px;max-width:400px;background-color:var(--token-color-surface-primary);border-radius:6px;box-shadow:var(--token-surface-high-box-shadow)}.hds-dropdown__content--fixed-width{min-width:initial;max-width:initial}.hds-dropdown__content--position-bottom-right{position:absolute;top:calc(100% + 4px);right:0;z-index:2}.hds-dropdown__content--position-bottom-left{position:absolute;top:calc(100% + 4px);left:0;z-index:2}.hds-dropdown__content--position-top-right{position:absolute;right:0;bottom:calc(100% + 4px);z-index:2}.hds-dropdown__content--position-top-left{position:absolute;bottom:calc(100% + 4px);left:0;z-index:2}.hds-dropdown__list{flex:1 1 auto;margin:0;padding:4px 0;overflow-y:auto;list-style:none;overscroll-behavior:contain}.hds-dropdown__footer,.hds-dropdown__header{position:relative;flex:none;padding:0 8px}.hds-dropdown__footer>.hds-link-standalone,.hds-dropdown__header>.hds-link-standalone{width:initial;margin:4px 0;padding:7px 8px}.hds-dropdown__footer>.hds-link-standalone::before,.hds-dropdown__header>.hds-link-standalone::before{top:0;bottom:0}.hds-dropdown__footer>.hds-button,.hds-dropdown__footer>.hds-form-text-input,.hds-dropdown__header>.hds-button,.hds-dropdown__header>.hds-form-text-input{margin:8px 0}.hds-dropdown__footer>.hds-button-set,.hds-dropdown__header>.hds-button-set{gap:8px;margin:8px 0}.hds-dropdown__footer--with-divider{border-top:1px solid var(--token-color-border-primary)}.hds-dropdown-list-item__copy-item-title{padding:2px 0 4px;color:var(--token-color-foreground-faint)}.hds-dropdown-list-item--variant-copy-item{width:100%;padding:10px 16px 12px}.hds-dropdown-list-item--variant-description{padding:2px 16px 4px;color:var(--token-color-foreground-faint)}.hds-dropdown-list-item--variant-generic{padding-right:16px;padding-left:16px}.hds-dropdown-list-item--variant-checkmark,.hds-dropdown-list-item--variant-interactive{position:relative;min-height:36px;isolation:isolate}.hds-dropdown-list-item--variant-checkmark button,.hds-dropdown-list-item--variant-interactive button{width:100%;background-color:transparent}.hds-dropdown-list-item--variant-checkmark button:hover,.hds-dropdown-list-item--variant-interactive button:hover{cursor:pointer}.hds-dropdown-list-item--variant-checkmark a,.hds-dropdown-list-item--variant-checkmark button,.hds-dropdown-list-item--variant-interactive a,.hds-dropdown-list-item--variant-interactive button{display:flex;align-items:flex-start;padding:7px 9px 7px 15px;text-decoration:none;border:1px solid transparent;outline-style:solid;outline-color:transparent}.hds-dropdown-list-item--variant-checkmark a::before,.hds-dropdown-list-item--variant-checkmark button::before,.hds-dropdown-list-item--variant-interactive a::before,.hds-dropdown-list-item--variant-interactive button::before{position:absolute;top:6px;bottom:6px;left:4px;z-index:-1;width:2px;border-radius:1px;content:""}.hds-dropdown-list-item--variant-checkmark a::after,.hds-dropdown-list-item--variant-checkmark button::after,.hds-dropdown-list-item--variant-interactive a::after,.hds-dropdown-list-item--variant-interactive button::after{position:absolute;top:0;right:4px;bottom:0;left:10px;z-index:-1;border-radius:5px;content:""}.hds-dropdown-list-item--variant-checkmark a.mock-hover,.hds-dropdown-list-item--variant-checkmark a:hover,.hds-dropdown-list-item--variant-checkmark button.mock-hover,.hds-dropdown-list-item--variant-checkmark button:hover,.hds-dropdown-list-item--variant-interactive a.mock-hover,.hds-dropdown-list-item--variant-interactive a:hover,.hds-dropdown-list-item--variant-interactive button.mock-hover,.hds-dropdown-list-item--variant-interactive button:hover{color:var(--current-color-hover)}.hds-dropdown-list-item--variant-checkmark a.mock-focus,.hds-dropdown-list-item--variant-checkmark a:focus,.hds-dropdown-list-item--variant-checkmark a:focus-visible,.hds-dropdown-list-item--variant-checkmark button.mock-focus,.hds-dropdown-list-item--variant-checkmark button:focus,.hds-dropdown-list-item--variant-checkmark button:focus-visible,.hds-dropdown-list-item--variant-interactive a.mock-focus,.hds-dropdown-list-item--variant-interactive a:focus,.hds-dropdown-list-item--variant-interactive a:focus-visible,.hds-dropdown-list-item--variant-interactive button.mock-focus,.hds-dropdown-list-item--variant-interactive button:focus,.hds-dropdown-list-item--variant-interactive button:focus-visible{color:var(--current-color-focus)}.hds-dropdown-list-item--variant-checkmark a.mock-hover::before,.hds-dropdown-list-item--variant-checkmark a:hover::before,.hds-dropdown-list-item--variant-checkmark button.mock-hover::before,.hds-dropdown-list-item--variant-checkmark button:hover::before,.hds-dropdown-list-item--variant-interactive a.mock-hover::before,.hds-dropdown-list-item--variant-interactive a:hover::before,.hds-dropdown-list-item--variant-interactive button.mock-hover::before,.hds-dropdown-list-item--variant-interactive button:hover::before{background-color:currentColor}.hds-dropdown-list-item--variant-checkmark a.mock-focus::after,.hds-dropdown-list-item--variant-checkmark a:focus::after,.hds-dropdown-list-item--variant-checkmark button.mock-focus::after,.hds-dropdown-list-item--variant-checkmark button:focus::after,.hds-dropdown-list-item--variant-interactive a.mock-focus::after,.hds-dropdown-list-item--variant-interactive a:focus::after,.hds-dropdown-list-item--variant-interactive button.mock-focus::after,.hds-dropdown-list-item--variant-interactive button:focus::after{left:4px;box-shadow:var(--current-focus-ring-box-shadow)}.hds-dropdown-list-item--variant-checkmark a:focus:not(:focus-visible)::after,.hds-dropdown-list-item--variant-checkmark button:focus:not(:focus-visible)::after,.hds-dropdown-list-item--variant-interactive a:focus:not(:focus-visible)::after,.hds-dropdown-list-item--variant-interactive button:focus:not(:focus-visible)::after{background-color:transparent;box-shadow:none}.hds-dropdown-list-item--variant-checkmark a:focus-visible::after,.hds-dropdown-list-item--variant-checkmark button:focus-visible::after,.hds-dropdown-list-item--variant-interactive a:focus-visible::after,.hds-dropdown-list-item--variant-interactive button:focus-visible::after{left:4px;box-shadow:var(--current-focus-ring-box-shadow)}.hds-dropdown-list-item--variant-checkmark a.mock-focus.mock-active::after,.hds-dropdown-list-item--variant-checkmark a:focus-visible:active::after,.hds-dropdown-list-item--variant-checkmark a:focus:active::after,.hds-dropdown-list-item--variant-checkmark button.mock-focus.mock-active::after,.hds-dropdown-list-item--variant-checkmark button:focus-visible:active::after,.hds-dropdown-list-item--variant-checkmark button:focus:active::after,.hds-dropdown-list-item--variant-interactive a.mock-focus.mock-active::after,.hds-dropdown-list-item--variant-interactive a:focus-visible:active::after,.hds-dropdown-list-item--variant-interactive a:focus:active::after,.hds-dropdown-list-item--variant-interactive button.mock-focus.mock-active::after,.hds-dropdown-list-item--variant-interactive button:focus-visible:active::after,.hds-dropdown-list-item--variant-interactive button:focus:active::after{left:10px;background-color:var(--current-background-color);box-shadow:none}.hds-dropdown-list-item--variant-checkmark a.mock-active,.hds-dropdown-list-item--variant-checkmark a:active,.hds-dropdown-list-item--variant-checkmark button.mock-active,.hds-dropdown-list-item--variant-checkmark button:active,.hds-dropdown-list-item--variant-interactive a.mock-active,.hds-dropdown-list-item--variant-interactive a:active,.hds-dropdown-list-item--variant-interactive button.mock-active,.hds-dropdown-list-item--variant-interactive button:active{color:var(--current-color-active)}.hds-dropdown-list-item--variant-checkmark a.mock-active::before,.hds-dropdown-list-item--variant-checkmark a:active::before,.hds-dropdown-list-item--variant-checkmark button.mock-active::before,.hds-dropdown-list-item--variant-checkmark button:active::before,.hds-dropdown-list-item--variant-interactive a.mock-active::before,.hds-dropdown-list-item--variant-interactive a:active::before,.hds-dropdown-list-item--variant-interactive button.mock-active::before,.hds-dropdown-list-item--variant-interactive button:active::before{background-color:currentColor}.hds-dropdown-list-item__interactive-icon{margin-top:2px;margin-right:8px}.hds-dropdown-list-item__interactive-text{flex:1;text-align:left}.hds-dropdown-list-item--color-action a,.hds-dropdown-list-item--color-action button{color:var(--token-color-foreground-primary);--current-color-hover:var(--token-color-foreground-action-hover);--current-color-focus:var(--token-color-foreground-action-active);--current-color-active:var(--token-color-foreground-action-active)}.hds-dropdown-list-item--color-action a::after,.hds-dropdown-list-item--color-action button::after{--current-focus-ring-box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-dropdown-list-item--color-critical a,.hds-dropdown-list-item--color-critical button{color:var(--token-color-foreground-critical);--current-color-hover:var(--token-color-palette-red-300);--current-color-focus:var(--token-color-palette-red-400);--current-color-active:var(--token-color-palette-red-400)}.hds-dropdown-list-item--color-critical a::after,.hds-dropdown-list-item--color-critical button::after{--current-background-color:var(--token-color-surface-critical);--current-focus-ring-box-shadow:var(--token-focus-ring-critical-box-shadow)}.hds-dropdown-list-item__interactive-loading-wrapper{display:flex;align-items:center;padding:8px 10px 8px 16px}.hds-dropdown-list-item__interactive-loading-wrapper .hds-dropdown-list-item__interactive-text{color:var(--token-color-foreground-faint)}.hds-dropdown-list-item__interactive-loading-wrapper .hds-dropdown-list-item__interactive-icon{color:var(--token-color-foreground-primary)}.hds-dropdown-list-item--variant-separator{position:relative;width:100%;height:4px}.hds-dropdown-list-item--variant-separator::before{position:absolute;right:6px;bottom:0;left:6px;content:""}.hds-dropdown-list-item--variant-title{padding:10px 16px 4px;color:var(--token-color-foreground-strong)}.hds-dropdown-list-item--variant-checkmark-selected .hds-dropdown-list-item__interactive{color:var(--token-color-foreground-action)}.hds-dropdown-list-item__checkmark{display:flex;width:16px;height:20px;margin-left:8px}.hds-dropdown-list-item__checkmark-icon{align-self:center}.hds-dropdown-list-item__interactive[disabled],.hds-dropdown-list-item__interactive[disabled]:hover{color:var(--token-color-foreground-disabled);cursor:not-allowed}.hds-dropdown-list-item--variant-checkbox,.hds-dropdown-list-item--variant-radio{display:flex;align-items:self-start;padding:8px 16px}.hds-dropdown-list-item--variant-checkbox .hds-dropdown-list-item__control,.hds-dropdown-list-item--variant-radio .hds-dropdown-list-item__control{flex-shrink:0;margin-top:2px;margin-right:8px}.hds-dropdown-list-item--variant-checkbox .hds-dropdown-list-item__control[disabled]~.hds-dropdown-list-item__count,.hds-dropdown-list-item--variant-checkbox .hds-dropdown-list-item__control[disabled]~.hds-dropdown-list-item__icon,.hds-dropdown-list-item--variant-checkbox .hds-dropdown-list-item__control[disabled]~.hds-dropdown-list-item__text-content,.hds-dropdown-list-item--variant-radio .hds-dropdown-list-item__control[disabled]~.hds-dropdown-list-item__count,.hds-dropdown-list-item--variant-radio .hds-dropdown-list-item__control[disabled]~.hds-dropdown-list-item__icon,.hds-dropdown-list-item--variant-radio .hds-dropdown-list-item__control[disabled]~.hds-dropdown-list-item__text-content{color:var(--token-color-foreground-disabled)}.hds-dropdown-list-item--variant-checkbox .hds-dropdown-list-item__label,.hds-dropdown-list-item--variant-radio .hds-dropdown-list-item__label{display:flex;flex-grow:1;align-items:flex-start;color:var(--token-color-foreground-primary)}.hds-dropdown-list-item--variant-checkbox .hds-dropdown-list-item__icon,.hds-dropdown-list-item--variant-radio .hds-dropdown-list-item__icon{margin-top:2px;margin-right:4px}.hds-dropdown-list-item__count{margin-left:auto;padding-left:8px;color:var(--token-color-foreground-faint);line-height:20px}.hds-dropdown-list-item--variant-checkbox .hds-badge,.hds-dropdown-list-item--variant-checkmark .hds-badge,.hds-dropdown-list-item--variant-radio .hds-badge{vertical-align:bottom}.hds-flyout{z-index:49;flex-direction:column;height:100vh;max-height:100vh;margin:0;padding:0;background:var(--token-color-surface-primary);border:none;box-shadow:0 2px 3px 0 rgba(59,61,69,.2509803922),0 12px 24px 0 rgba(59,61,69,.3490196078)}.hds-flyout__footer .hds-button-set .hds-button--color-tertiary,.hds-modal__footer .hds-button-set .hds-button--color-tertiary{margin-left:auto}.hds-flyout__body,.hds-flyout__footer{border-top:1px solid var(--token-color-border-primary)}.hds-flyout[open]{position:fixed;display:flex}.hds-flyout::backdrop{display:none}.hds-flyout__overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:49;background:var(--token-color-palette-neutral-700);opacity:.5}.hds-form-masked-input__toggle-button,.hds-form-text-input__visibility-toggle{right:calc(var(--token-form-control-padding) - var(--token-form-control-border-width))}.hds-flyout__header{display:flex;flex:none;gap:16px;align-items:flex-start;padding:16px 24px;color:var(--token-color-foreground-strong)}.hds-flyout__icon{flex:none;align-self:center}.hds-flyout__title{flex-grow:1}.hds-flyout__tagline{margin-bottom:4px}.hds-flyout__dismiss{align-self:center}.hds-flyout__description{padding:0 24px 16px}.hds-flyout__body{flex:1 1 auto;padding:24px;overflow-y:auto;overscroll-behavior:contain}.hds-flyout__footer{flex:none;padding:16px 24px;background:var(--token-color-surface-faint)}.hds-flyout--size-medium{width:min(480px,100vw - 40px);max-width:calc(100vw - 40px)}.hds-flyout--size-medium[open]{margin-left:calc(100% - min(480px,100vw - 40px))}.hds-flyout--size-large{width:min(720px,100vw - 40px);max-width:calc(100vw - 40px)}.hds-flyout--size-large[open]{margin-left:calc(100% - min(720px,100vw - 40px))}.hds-form-label{display:block;width:-moz-max-content;width:max-content;max-width:100%;color:var(--token-form-label-color)}.hds-form-label .hds-badge{vertical-align:initial}.hds-form-helper-text{display:block;color:var(--token-form-helper-text-color)}.hds-form-error{display:flex;gap:8px;align-items:flex-start;color:var(--token-form-error-color)}.hds-form-error__icon{flex:none;width:var(--token-form-error-icon-size);height:var(--token-form-error-icon-size);margin:2px 0}.hds-form-error__content{flex:1 1 auto}.hds-form-error__message{margin:0}.hds-form-field--layout-vertical{display:grid;justify-items:start;width:100%}.hds-form-field--layout-vertical .hds-form-field__label{width:-moz-fit-content;width:fit-content}.hds-form-field--layout-vertical .hds-form-field__helper-text:not(:first-child){margin-top:4px}.hds-form-field--layout-vertical .hds-form-field__helper-text+.hds-form-helper-text{margin-top:2px}.hds-form-field--layout-vertical .hds-form-field__control{display:flex;justify-self:stretch}.hds-form-field--layout-vertical .hds-form-field__control:not(:first-child){margin-top:8px}.hds-form-field--layout-vertical .hds-form-field__control:not(:last-child){margin-bottom:8px}.hds-form-field--layout-flag{display:grid;grid-auto-flow:row;grid-template-areas:"control label" "control helper-text" "control error";grid-template-rows:auto auto auto;grid-template-columns:auto 1fr;justify-items:start}.hds-form-field--layout-flag .hds-form-field__label{grid-area:label;width:-moz-fit-content;width:fit-content}.hds-form-field--layout-flag .hds-form-field__helper-text{grid-area:helper-text;margin-top:4px}.hds-form-field--layout-flag .hds-form-field__control{display:flex;grid-area:control}.hds-form-field--layout-flag .hds-form-field__control:not(:only-child){margin-top:2px;margin-right:8px}.hds-form-field--layout-flag .hds-form-field__error{grid-area:error;margin-top:4px}.hds-form-file-input{margin:-4px 0 -4px -4px;padding:3px 0 3px 3px;color:var(--token-color-foreground-primary)}.hds-form-file-input:focus,.hds-form-file-input:focus-visible{outline:0}.hds-form-file-input::file-selector-button{min-height:36px;margin-right:16px;padding:7px 16px 7px 37px;color:var(--token-color-foreground-primary);font:inherit;background-color:var(--token-color-surface-faint);background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' viewBox='0 0 16 16'%3E%3Cg fill='%233b3d45'%3E%3Cpath d='M4.24 5.8a.75.75 0 001.06-.04l1.95-2.1v6.59a.75.75 0 001.5 0V3.66l1.95 2.1a.75.75 0 101.1-1.02l-3.25-3.5a.75.75 0 00-1.101.001L4.2 4.74a.75.75 0 00.04 1.06z'/%3E%3Cpath d='M1.75 9a.75.75 0 01.75.75v3c0 .414.336.75.75.75h9.5a.75.75 0 00.75-.75v-3a.75.75 0 011.5 0v3A2.25 2.25 0 0112.75 15h-9.5A2.25 2.25 0 011 12.75v-3A.75.75 0 011.75 9z'/%3E%3C/g%3E%3C/svg%3E");background-repeat:no-repeat;background-position:15px 50%;background-size:var(--token-form-text-input-background-image-size);border:1px solid var(--token-color-border-strong);border-radius:5px;box-shadow:var(--token-elevation-low-box-shadow);cursor:pointer}.hds-form-group__legend~.hds-form-group__control-fields-wrapper .hds-form-label,.hds-link-standalone{font-weight:var(--token-typography-font-weight-regular)}.hds-form-file-input.mock-hover::file-selector-button,.hds-form-file-input::file-selector-button:hover{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-primary);border-color:var(--token-color-border-strong)}.hds-form-file-input.mock-focus::file-selector-button,.hds-form-file-input:focus-within::file-selector-button{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-faint);border-color:var(--token-color-focus-action-internal);outline:var(--token-color-focus-action-external) solid 3px}.hds-form-file-input:not(:focus,.mock-focus)::file-selector-button{border-color:var(--token-color-border-strong);outline:0}.hds-form-file-input.mock-active::file-selector-button,.hds-form-file-input::file-selector-button:active{color:var(--token-color-foreground-primary);background-color:var(--token-color-surface-interactive-active);border-color:var(--token-color-border-strong);box-shadow:none}.hds-form-file-input.mock-disabled,.hds-form-file-input.mock-disabled:focus,.hds-form-file-input.mock-disabled:hover,.hds-form-file-input:disabled,.hds-form-file-input:disabled:focus,.hds-form-file-input:disabled:hover,.hds-form-file-input[disabled],.hds-form-file-input[disabled]:focus,.hds-form-file-input[disabled]:hover{color:var(--token-color-foreground-disabled)}.hds-form-file-input.mock-disabled::file-selector-button,.hds-form-file-input.mock-disabled:focus::file-selector-button,.hds-form-file-input.mock-disabled:hover::file-selector-button,.hds-form-file-input:disabled::file-selector-button,.hds-form-file-input:disabled:focus::file-selector-button,.hds-form-file-input:disabled:hover::file-selector-button,.hds-form-file-input[disabled]::file-selector-button,.hds-form-file-input[disabled]:focus::file-selector-button,.hds-form-file-input[disabled]:hover::file-selector-button{color:var(--token-color-foreground-disabled);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary);box-shadow:none;cursor:not-allowed;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='none' viewBox='0 0 16 16'%3E%3Cg fill='%238c909c'%3E%3Cpath d='M4.24 5.8a.75.75 0 001.06-.04l1.95-2.1v6.59a.75.75 0 001.5 0V3.66l1.95 2.1a.75.75 0 101.1-1.02l-3.25-3.5a.75.75 0 00-1.101.001L4.2 4.74a.75.75 0 00.04 1.06z'/%3E%3Cpath d='M1.75 9a.75.75 0 01.75.75v3c0 .414.336.75.75.75h9.5a.75.75 0 00.75-.75v-3a.75.75 0 011.5 0v3A2.25 2.25 0 0112.75 15h-9.5A2.25 2.25 0 011 12.75v-3A.75.75 0 011.75 9z'/%3E%3C/g%3E%3C/svg%3E")}.hds-form-file-input.mock-disabled::file-selector-button::before,.hds-form-file-input.mock-disabled:focus::file-selector-button::before,.hds-form-file-input.mock-disabled:hover::file-selector-button::before,.hds-form-file-input:disabled::file-selector-button::before,.hds-form-file-input:disabled:focus::file-selector-button::before,.hds-form-file-input:disabled:hover::file-selector-button::before,.hds-form-file-input[disabled]::file-selector-button::before,.hds-form-file-input[disabled]:focus::file-selector-button::before,.hds-form-file-input[disabled]:hover::file-selector-button::before{border-color:transparent}.hds-form-legend{display:block;color:var(--token-form-legend-color)}.hds-form-legend .hds-badge{vertical-align:initial}.hds-form-group{display:block;margin:0;padding:0;border:none}.hds-form-checkbox,.hds-form-radio{background-position:center center;border-style:solid;-moz-appearance:none}.hds-form-group__legend{margin:0 0 4px;padding:0}.hds-form-group--layout-vertical .hds-form-group__control-fields-wrapper{display:flex;flex-direction:column}.hds-form-group--layout-vertical .hds-form-group__control-field+.hds-form-group__control-field{margin-top:12px}.hds-form-group--layout-horizontal .hds-form-group__control-fields-wrapper{display:flex;flex-wrap:wrap;margin-bottom:-4px}.hds-form-group--layout-horizontal .hds-form-group__control-field{margin-right:16px;margin-bottom:4px}.hds-form-group__helper-text{margin-bottom:8px}.hds-form-group__error{margin-top:8px}.hds-form-indicator--optional{color:var(--token-form-indicator-optional-color)}.hds-form-checkbox{width:var(--token-form-checkbox-size);height:var(--token-form-checkbox-size);margin:0;padding:0;background-size:var(--token-form-checkbox-background-image-size) var(--token-form-checkbox-background-image-size);border-width:var(--token-form-checkbox-border-width);border-radius:var(--token-form-checkbox-border-radius);cursor:pointer;-webkit-appearance:none;appearance:none}.hds-form-checkbox:not(:checked,:indeterminate){background-color:var(--token-form-control-base-surface-color-default);border-color:var(--token-form-control-base-border-color-default)}.hds-form-checkbox:checked,.hds-form-checkbox:indeterminate{background-color:var(--token-form-control-checked-surface-color-default);border-color:var(--token-form-control-checked-border-color-default)}.hds-form-checkbox:checked{background-image:var(--token-form-checkbox-background-image-data-url)}.hds-form-checkbox:indeterminate{background-image:var(--token-form-checkbox-background-image-data-url-indeterminate)}.hds-form-checkbox.mock-hover:not(:checked,:indeterminate),.hds-form-checkbox:hover:not(:checked,:indeterminate){background-color:var(--token-form-control-base-surface-color-hover);border-color:var(--token-form-control-base-border-color-hover)}.hds-form-checkbox.mock-hover:checked,.hds-form-checkbox.mock-hover:indeterminate,.hds-form-checkbox:hover:checked,.hds-form-checkbox:hover:indeterminate{background-color:var(--token-form-control-checked-border-color-default);border-color:var(--token-form-control-checked-border-color-hover)}.hds-form-checkbox:disabled:checked,.hds-form-checkbox:disabled:indeterminate,.hds-form-checkbox:disabled:not(:checked,:indeterminate){background-color:var(--token-form-control-disabled-surface-color);border-color:var(--token-form-control-disabled-border-color);box-shadow:none;cursor:not-allowed}.hds-form-checkbox.mock-focus,.hds-form-checkbox:focus{outline:var(--token-color-focus-action-external) solid 3px;outline-offset:1px}.hds-form-checkbox:disabled:checked{background-image:var(--token-form-checkbox-background-image-data-url-disabled)}.hds-form-checkbox:disabled:indeterminate{background-image:var(--token-form-checkbox-background-image-data-url-indeterminate-disabled);background-repeat:no-repeat}.hds-form-masked-input{position:relative;display:grid;grid-template-areas:"input copy-button";grid-template-columns:1fr auto;width:100%}.hds-form-masked-input .hds-form-masked-input__control{grid-area:input;padding-right:calc(var(--token-form-control-padding) + 24px)}.hds-form-masked-input--is-masked .hds-form-masked-input__control{-webkit-text-security:disc}.hds-form-masked-input--is-not-masked .hds-form-masked-input__control{-webkit-text-security:none}.hds-form-masked-input__toggle-button{position:absolute;top:calc(var(--token-form-control-padding) - var(--token-form-control-border-width));grid-area:input}.hds-form-masked-input__copy-button{grid-area:copy-button;align-self:flex-start;margin-left:8px}.hds-form-radio{width:var(--token-form-radio-size);height:var(--token-form-radio-size);margin:0;padding:0;background-size:var(--token-form-radio-background-image-size) var(--token-form-radio-background-image-size);border-width:var(--token-form-radio-border-width);border-radius:50%;cursor:pointer;-webkit-appearance:none;appearance:none}.hds-form-radio:not(:checked){background-color:var(--token-form-control-base-surface-color-default);border-color:var(--token-form-control-base-border-color-default);box-shadow:var(--token-elevation-inset-box-shadow)}.hds-form-radio:checked{background-color:var(--token-form-control-checked-surface-color-default);background-image:var(--token-form-radio-background-image-data-url);border-color:var(--token-form-control-checked-border-color-default)}.hds-form-radio.mock-hover:not(:checked),.hds-form-radio:hover:not(:checked){background-color:var(--token-form-control-base-surface-color-hover);border-color:var(--token-form-control-base-border-color-hover)}.hds-form-radio.mock-hover:checked,.hds-form-radio:hover:checked{background-color:var(--token-form-control-checked-border-color-default);border-color:var(--token-form-control-checked-border-color-hover)}.hds-form-radio:disabled:checked,.hds-form-radio:disabled:not(:checked){background-color:var(--token-form-control-disabled-surface-color);border-color:var(--token-form-control-disabled-border-color);box-shadow:none;cursor:not-allowed}.hds-form-radio.mock-focus,.hds-form-radio:focus{outline:var(--token-color-focus-action-external) solid 3px;outline-offset:1px}.hds-form-radio:disabled:checked{background-image:var(--token-form-radio-background-image-data-url-disabled)}.hds-form-group--radio-cards .hds-form-group__control-fields-wrapper{margin:calc(-1 * var(--token-form-radiocard-group-gap)/ 2)}.hds-form-group--radio-cards .hds-form-group__legend{margin-bottom:12px}.hds-form-group--radio-cards .hds-form-radio-card{margin:calc(var(--token-form-radiocard-group-gap)/ 2)}.hds-form-group--radio-cards .hds-form-radio-card--has-fixed-width{flex:1 0 100%}.hds-form-radio-card{display:flex;flex-direction:column;background-color:var(--token-color-surface-primary);border:var(--token-form-radiocard-border-width) solid var(--token-color-border-primary);border-radius:var(--token-form-radiocard-border-radius);box-shadow:var(--token-elevation-mid-box-shadow);cursor:pointer}.hds-form-radio-card .hds-form-radio-card__control{outline-color:transparent}.hds-form-radio-card.mock-hover,.hds-form-radio-card:hover{box-shadow:var(--token-elevation-high-box-shadow);transition:var(--token-form-radiocard-transition-duration)}.hds-form-radio-card.mock-focus,.hds-form-radio-card:focus-within{border-color:var(--token-color-focus-action-internal);box-shadow:0 0 0 3px var(--token-color-focus-action-external)}.hds-form-radio-card--checked,.hds-form-radio-card.mock-checked{border-color:var(--token-color-focus-action-internal)}.hds-form-radio-card--checked .hds-form-radio-card__control-wrapper,.hds-form-radio-card.mock-checked .hds-form-radio-card__control-wrapper{background-color:var(--token-color-surface-action);border-color:var(--token-color-border-action)}.hds-form-radio-card--disabled,.hds-form-radio-card--disabled .hds-form-radio-card__control-wrapper,.hds-form-radio-card.mock-disabled,.hds-form-radio-card.mock-disabled .hds-form-radio-card__control-wrapper{background-color:var(--token-color-surface-interactive-disabled);border-color:var(--token-color-border-primary)}.hds-form-radio-card--disabled,.hds-form-radio-card.mock-disabled{box-shadow:none;cursor:not-allowed}.hds-form-radio-card--align-left{text-align:left}.hds-form-radio-card--align-center{text-align:center}.hds-form-radio-card--align-center .flight-icon{margin:auto}.hds-form-radio-card--control-bottom .hds-form-radio-card__control-wrapper{border-top-width:var(--token-form-radiocard-border-width);border-top-style:solid;border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.hds-form-radio-card--control-left{flex-direction:row-reverse}.hds-form-radio-card--control-left .hds-form-radio-card__control-wrapper{display:flex;align-items:center;border-right-width:var(--token-form-radiocard-border-width);border-right-style:solid;border-top-left-radius:inherit;border-bottom-left-radius:inherit}.hds-form-radio-card__content{flex:1;padding:var(--token-form-radiocard-content-padding)}.hds-form-radio-card__content .hds-badge{margin-bottom:12px}.hds-form-radio-card__label{display:block;margin:8px 0;color:var(--token-form-label-color);overflow-wrap:break-word}.hds-form-radio-card__label:first-child{margin-top:0}.hds-form-radio-card__description{display:block;color:var(--token-color-foreground-primary)}.hds-form-radio-card__control-wrapper{padding:var(--token-form-radiocard-control-padding);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary)}.hds-form-select,.hds-form-text-input{border:var(--token-form-control-border-width) solid var(--token-form-control-base-border-color-default)}.hds-form-radio-card__control{display:block;margin:auto}.hds-form-select{max-width:100%;padding:var(--token-form-control-padding);padding-right:calc(var(--token-form-control-padding) + 24px);color:var(--token-form-control-base-foreground-value-color);background-color:var(--token-form-control-base-surface-color-default);background-image:var(--token-form-select-background-image-data-url);background-repeat:no-repeat;background-position:right var(--token-form-select-background-image-position-right-x) top var(--token-form-select-background-image-position-top-y);background-size:var(--token-form-select-background-image-size) var(--token-form-select-background-image-size);border-radius:var(--token-form-control-border-radius);box-shadow:var(--token-elevation-low-box-shadow);-webkit-appearance:none;-moz-appearance:none;appearance:none}.hds-form-select.mock-hover,.hds-form-select:hover{border-color:var(--token-form-control-base-border-color-hover)}.hds-form-select.mock-focus,.hds-form-select:focus{border-color:var(--token-color-focus-action-internal);outline:var(--token-color-focus-action-external) solid 3px;outline-offset:0}.hds-form-select:disabled{color:var(--token-form-control-disabled-foreground-color);background-color:var(--token-form-control-disabled-surface-color);background-image:var(--token-form-select-background-image-data-url-disabled);border-color:var(--token-form-control-disabled-border-color);box-shadow:none;cursor:not-allowed}.hds-form-select.hds-form-select--is-invalid{border-color:var(--token-form-control-invalid-border-color-default)}.hds-form-select.hds-form-select--is-invalid.mock-hover,.hds-form-select.hds-form-select--is-invalid:hover{border-color:var(--token-form-control-invalid-border-color-hover)}.hds-form-select.hds-form-select--is-invalid.mock-focus,.hds-form-select.hds-form-select--is-invalid:focus{border-color:var(--token-color-focus-critical-internal);outline-color:var(--token-color-focus-critical-external)}.hds-form-select[multiple],.hds-form-select[size]{background:0 0}.hds-form-select[multiple] option,.hds-form-select[size] option{margin:2px auto;border-radius:3px}.hds-form-select[multiple] option:hover,.hds-form-select[size] option:hover{color:var(--token-color-foreground-action)}.hds-form-select[multiple] option:disabled,.hds-form-select[size] option:disabled{color:var(--token-color-foreground-disabled)}.hds-form-select[multiple] option:checked,.hds-form-select[size] option:checked{color:var(--token-color-foreground-high-contrast);background:var(--token-color-palette-blue-200)}.hds-form-text-input,.hds-form-textarea{background-color:var(--token-form-control-base-surface-color-default);max-width:100%}.hds-form-select[multiple] optgroup,.hds-form-select[size] optgroup{color:var(--token-color-foreground-strong);font-weight:var(--token-typography-font-weight-semibold);font-style:normal}.hds-form-text-input{width:100%;padding:var(--token-form-control-padding);color:var(--token-form-control-base-foreground-value-color);border-radius:var(--token-form-control-border-radius);box-shadow:var(--token-elevation-inset-box-shadow)}.hds-form-text-input ::-moz-placeholder{color:var(--token-form-control-base-foreground-placeholder-color)}.hds-form-text-input ::placeholder{color:var(--token-form-control-base-foreground-placeholder-color)}.hds-form-text-input.mock-hover,.hds-form-text-input:hover{border-color:var(--token-form-control-base-border-color-hover)}.hds-form-text-input.mock-focus,.hds-form-text-input:focus{border-color:var(--token-color-focus-action-internal);outline:var(--token-color-focus-action-external) solid 3px;outline-offset:0}.hds-form-text-input:-moz-read-only{color:var(--token-form-control-readonly-foreground-color);background-color:var(--token-form-control-readonly-surface-color);border-color:var(--token-form-control-readonly-border-color);box-shadow:none}.hds-form-text-input:read-only{color:var(--token-form-control-readonly-foreground-color);background-color:var(--token-form-control-readonly-surface-color);border-color:var(--token-form-control-readonly-border-color);box-shadow:none}.hds-form-text-input:disabled{color:var(--token-form-control-disabled-foreground-color);background-color:var(--token-form-control-disabled-surface-color);border-color:var(--token-form-control-disabled-border-color);box-shadow:none;cursor:not-allowed}.hds-form-text-input.hds-form-text-input--is-invalid{border-color:var(--token-form-control-invalid-border-color-default)}.hds-form-text-input.hds-form-text-input--is-invalid.mock-hover,.hds-form-text-input.hds-form-text-input--is-invalid:hover{border-color:var(--token-form-control-invalid-border-color-hover)}.hds-form-text-input.hds-form-text-input--is-invalid.mock-focus,.hds-form-text-input.hds-form-text-input--is-invalid:focus{border-color:var(--token-color-focus-critical-internal);outline-color:var(--token-color-focus-critical-external)}.hds-form-text-input__wrapper{position:relative;width:100%}.hds-form-text-input--has-visibility-toggle{padding-right:calc(var(--token-form-control-padding) + 24px)}.hds-form-text-input__visibility-toggle{position:absolute;top:calc(var(--token-form-control-padding) - var(--token-form-control-border-width))}.hds-form-text-input[type=date],.hds-form-text-input[type=datetime-local],.hds-form-text-input[type=time]{width:initial}.hds-form-text-input[type=date]:disabled::-webkit-calendar-picker-indicator,.hds-form-text-input[type=datetime-local]:disabled::-webkit-calendar-picker-indicator,.hds-form-text-input[type=time]:disabled::-webkit-calendar-picker-indicator{visibility:visible;opacity:.5}.hds-form-text-input[type=date][readonly]::-webkit-calendar-picker-indicator,.hds-form-text-input[type=datetime-local][readonly]::-webkit-calendar-picker-indicator,.hds-form-text-input[type=time][readonly]::-webkit-calendar-picker-indicator{visibility:visible}.hds-form-text-input[type=date]::-webkit-calendar-picker-indicator,.hds-form-text-input[type=datetime-local]::-webkit-calendar-picker-indicator{background-image:var(--token-form-text-input-background-image-data-url-date);background-position:center center;background-size:var(--token-form-text-input-background-image-size)}.hds-form-text-input[type=time]::-webkit-calendar-picker-indicator{background-image:var(--token-form-text-input-background-image-data-url-time);background-position:center center;background-size:var(--token-form-text-input-background-image-size)}.hds-form-text-input[type=search]{padding-left:calc(var(--token-form-control-padding) + 24px);background-image:var(--token-form-text-input-background-image-data-url-search);background-repeat:no-repeat;background-position:var(--token-form-text-input-background-image-position-x) 50%;background-size:var(--token-form-text-input-background-image-size)}.hds-form-text-input[type=search]::-webkit-search-cancel-button{width:var(--token-form-text-input-background-image-size);height:var(--token-form-text-input-background-image-size);background-image:var(--token-form-text-input-background-image-data-url-search-cancel);background-position:center center;background-size:var(--token-form-text-input-background-image-size);-webkit-appearance:none}.hds-form-text-input[type=search].hds-form-text-input--is-loading{background-image:var(--token-form-text-input-background-image-data-url-search-loading)}.hds-form-textarea{width:100%;min-height:36px;padding:var(--token-form-control-padding);color:var(--token-form-control-base-foreground-value-color);border:var(--token-form-control-border-width) solid var(--token-form-control-base-border-color-default);border-radius:var(--token-form-control-border-radius);box-shadow:var(--token-elevation-inset-box-shadow);resize:vertical}.hds-form-textarea ::-moz-placeholder{color:var(--token-form-control-base-foreground-placeholder-color)}.hds-form-textarea ::placeholder{color:var(--token-form-control-base-foreground-placeholder-color)}.hds-form-textarea.mock-hover,.hds-form-textarea:hover{border-color:var(--token-form-control-base-border-color-hover)}.hds-form-textarea.mock-focus,.hds-form-textarea:focus{border-color:var(--token-color-focus-action-internal);outline:var(--token-color-focus-action-external) solid 3px;outline-offset:0}.hds-form-textarea:-moz-read-only{color:var(--token-form-control-readonly-foreground-color);background-color:var(--token-form-control-readonly-surface-color);border-color:var(--token-form-control-readonly-border-color);box-shadow:none}.hds-form-textarea:read-only{color:var(--token-form-control-readonly-foreground-color);background-color:var(--token-form-control-readonly-surface-color);border-color:var(--token-form-control-readonly-border-color);box-shadow:none}.hds-form-textarea:disabled{color:var(--token-form-control-disabled-foreground-color);background-color:var(--token-form-control-disabled-surface-color);border-color:var(--token-form-control-disabled-border-color);box-shadow:none;cursor:not-allowed}.hds-form-textarea.hds-form-textarea--is-invalid{border-color:var(--token-form-control-invalid-border-color-default)}.hds-form-textarea.hds-form-textarea--is-invalid.mock-hover,.hds-form-textarea.hds-form-textarea--is-invalid:hover{border-color:var(--token-form-control-invalid-border-color-hover)}.hds-form-textarea.hds-form-textarea--is-invalid.mock-focus,.hds-form-textarea.hds-form-textarea--is-invalid:focus{border-color:var(--token-color-focus-critical-internal);outline-color:var(--token-color-focus-critical-external)}.hds-form-toggle{position:relative;isolation:isolate}.hds-form-toggle__control{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;display:block;height:100%;margin:0;padding:0;color:transparent;background-color:transparent;border:none;outline:0;cursor:pointer;opacity:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.hds-form-toggle__control:disabled{cursor:not-allowed}.hds-form-toggle__facade{position:relative;display:block;width:var(--token-form-toggle-width);height:var(--token-form-toggle-height);background-image:var(--token-form-toggle-background-image-data-url);background-repeat:no-repeat;background-position:var(--token-form-toggle-background-image-position-x) 50%;background-size:var(--token-form-toggle-background-image-size) var(--token-form-toggle-background-image-size);border:var(--token-form-radio-border-width) solid var(--border-color);border-radius:calc(var(--token-form-toggle-height)/ 2)}.hds-form-toggle__facade::after{position:absolute;top:calc(var(--token-form-radio-border-width) * -1);left:calc(var(--token-form-radio-border-width) * -1);width:var(--token-form-toggle-thumb-size);height:var(--token-form-toggle-thumb-size);background-color:var(--token-form-control-base-surface-color-default);border:var(--token-form-radio-border-width) solid var(--border-color);border-radius:50%;transform:translate3d(0,0,0);content:""}@media (prefers-reduced-motion:no-preference){.hds-form-toggle__facade,.hds-form-toggle__facade::after{transition-timing-function:var(--token-form-toggle-transition-timing-function);transition-duration:var(--token-form-toggle-transition-duration);transition-property:all}}.hds-form-toggle__facade::before{position:absolute;top:-5px;right:-5px;bottom:-5px;left:-5px;margin:auto;border-width:3px;border-radius:calc(var(--token-form-toggle-height)/ 2 + 3px + 1px);content:""}:not(:checked)+.hds-form-toggle__facade{--border-color:var(--token-form-control-base-border-color-default);background-color:var(--token-form-toggle-base-surface-color-default)}:checked+.hds-form-toggle__facade{--border-color:var(--token-form-control-checked-border-color-default);background-color:var(--token-form-control-checked-surface-color-default)}:checked+.hds-form-toggle__facade::after{transform:translate3d(calc(var(--token-form-toggle-width) - var(--token-form-toggle-thumb-size)),0,0)}.mock-hover:not(:checked)+.hds-form-toggle__facade,:hover:not(:checked)+.hds-form-toggle__facade{--border-color:var(--token-form-control-base-border-color-hover)}.mock-hover:checked+.hds-form-toggle__facade,:hover:checked+.hds-form-toggle__facade{--border-color:var(--token-form-control-checked-border-color-hover);background-color:var(--token-form-control-checked-border-color-default)}.mock-focus+.hds-form-toggle__facade::before,:focus+.hds-form-toggle__facade::before{border-color:var(--token-color-focus-action-external);border-style:solid}:disabled:checked+.hds-form-toggle__facade,:disabled:not(:checked)+.hds-form-toggle__facade{--border-color:var(--token-form-control-disabled-border-color);background-color:var(--token-form-control-disabled-surface-color);background-image:var(--token-form-toggle-background-image-data-url-disabled)}.hds-form-visibility-toggle{width:24px;height:24px;padding:2px;color:var(--token-color-foreground-primary);background-color:transparent;border-color:transparent;cursor:pointer}.hds-icon-tile--logo,.hds-icon-tile__extra{background-color:var(--token-color-surface-primary)}.hds-icon-tile{position:relative;display:flex;border:1px solid transparent;border-radius:4px;box-shadow:0 1px 1px rgba(101,106,118,.05)}.hds-icon-tile__icon,.hds-icon-tile__logo{display:flex;margin:auto}.hds-icon-tile__extra{position:absolute;right:-6px;bottom:-6px;display:flex;box-sizing:content-box;border:1px solid var(--token-color-border-primary);box-shadow:0 1px 1px rgba(101,106,118,.05)}.hds-icon-tile__extra-icon{display:flex;margin:auto;color:var(--token-color-foreground-strong)}.hds-icon-tile--size-small{width:1.75rem;height:1.75rem;border-radius:5px}.hds-icon-tile--size-small .hds-icon-tile__icon{width:1rem;height:1rem}.hds-icon-tile--size-small .hds-icon-tile__logo{width:1.125rem;height:1.125rem}.hds-icon-tile--size-small .hds-icon-tile__extra{width:1.125rem;height:1.125rem;border-radius:4px}.hds-icon-tile--size-small .hds-icon-tile__extra-icon{width:.75rem;height:.75rem}.hds-icon-tile--size-medium{width:2.5rem;height:2.5rem;border-radius:6px}.hds-icon-tile--size-medium .hds-icon-tile__icon{width:1.5rem;height:1.5rem}.hds-icon-tile--size-medium .hds-icon-tile__logo{width:1.75rem;height:1.75rem}.hds-icon-tile--size-medium .hds-icon-tile__extra{width:1.5rem;height:1.5rem;border-radius:5px}.hds-icon-tile--size-medium .hds-icon-tile__extra-icon{width:1rem;height:1rem}.hds-icon-tile--size-large{width:3rem;height:3rem;border-radius:6px}.hds-icon-tile--size-large .hds-icon-tile__icon{width:1.5rem;height:1.5rem}.hds-icon-tile--size-large .hds-icon-tile__logo{width:2rem;height:2rem}.hds-icon-tile--size-large .hds-icon-tile__extra{width:1.5rem;height:1.5rem;border-radius:5px}.hds-icon-tile--size-large .hds-icon-tile__extra-icon{width:1rem;height:1rem}.hds-icon-tile--logo{border-color:var(--token-color-border-primary)}.hds-icon-tile--icon.hds-icon-tile--color-neutral{color:var(--token-color-foreground-faint);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary)}.hds-icon-tile--icon.hds-icon-tile--color-boundary{color:var(--token-color-boundary-foreground);background:linear-gradient(135deg,var(--token-color-boundary-gradient-faint-start) 0,var(--token-color-boundary-gradient-faint-stop) 100%);border-color:var(--token-color-boundary-border)}.hds-icon-tile--icon.hds-icon-tile--color-consul{color:var(--token-color-consul-foreground);background:linear-gradient(135deg,var(--token-color-consul-gradient-faint-start) 0,var(--token-color-consul-gradient-faint-stop) 100%);border-color:var(--token-color-consul-border)}.hds-icon-tile--icon.hds-icon-tile--color-hcp{color:var(--token-color-palette-hcp-brand);background-color:var(--token-color-surface-faint);border-color:var(--token-color-border-primary)}.hds-icon-tile--icon.hds-icon-tile--color-nomad{color:var(--token-color-nomad-foreground);background:linear-gradient(135deg,var(--token-color-nomad-gradient-faint-start) 0,var(--token-color-nomad-gradient-faint-stop) 100%);border-color:var(--token-color-nomad-border)}.hds-icon-tile--icon.hds-icon-tile--color-packer{color:var(--token-color-packer-foreground);background:linear-gradient(135deg,var(--token-color-packer-gradient-faint-start) 0,var(--token-color-packer-gradient-faint-stop) 100%);border-color:var(--token-color-packer-border)}.hds-icon-tile--icon.hds-icon-tile--color-terraform{color:var(--token-color-terraform-foreground);background:linear-gradient(135deg,var(--token-color-terraform-gradient-faint-start) 0,var(--token-color-terraform-gradient-faint-stop) 100%);border-color:var(--token-color-terraform-border)}.hds-icon-tile--icon.hds-icon-tile--color-vagrant{color:var(--token-color-vagrant-foreground);background:linear-gradient(135deg,var(--token-color-vagrant-gradient-faint-start) 0,var(--token-color-vagrant-gradient-faint-stop) 100%);border-color:var(--token-color-vagrant-border)}.hds-icon-tile--icon.hds-icon-tile--color-vault{color:var(--token-color-vault-foreground);background:linear-gradient(135deg,var(--token-color-vault-gradient-faint-start) 0,var(--token-color-vault-gradient-faint-stop) 100%);border-color:var(--token-color-vault-border)}.hds-icon-tile--icon.hds-icon-tile--color-vault-secrets{color:var(--token-color-vault-secrets-foreground);background:linear-gradient(135deg,var(--token-color-vault-secrets-gradient-faint-start) 0,var(--token-color-vault-secrets-gradient-faint-stop) 100%);border-color:var(--token-color-vault-secrets-border)}.hds-icon-tile--icon.hds-icon-tile--color-waypoint{color:var(--token-color-waypoint-foreground);background:linear-gradient(135deg,var(--token-color-waypoint-gradient-faint-start) 0,var(--token-color-waypoint-gradient-faint-stop) 100%);border-color:var(--token-color-waypoint-border)}.hds-link-inline{border-radius:2px}.hds-link-inline.mock-focus,.hds-link-inline:focus,.hds-link-inline:focus-visible{text-decoration:none;outline:var(--token-color-focus-action-internal) solid 2px;outline-offset:1px}.hds-link-inline__icon{display:inline-block;width:1em;height:1em;vertical-align:text-bottom}.hds-link-inline--icon-leading>.hds-link-inline__icon{margin-right:.25em}.hds-link-inline--icon-trailing>.hds-link-inline__icon{margin-left:.25em}.hds-link-inline--color-primary{color:var(--token-color-foreground-action)}.hds-link-inline--color-primary.mock-hover,.hds-link-inline--color-primary:hover{color:var(--token-color-foreground-action-hover)}.hds-link-inline--color-primary.mock-active,.hds-link-inline--color-primary:active{color:var(--token-color-foreground-action-active)}.hds-link-inline--color-secondary{color:var(--token-color-foreground-strong)}.hds-link-inline--color-secondary.mock-hover,.hds-link-inline--color-secondary:hover{color:var(--token-color-foreground-primary)}.hds-link-inline--color-secondary.mock-active,.hds-link-inline--color-secondary:active{color:var(--token-color-foreground-faint)}.hds-link-standalone{display:flex;align-items:center;justify-content:center;width:-moz-fit-content;width:fit-content;padding-top:4px;padding-bottom:4px;background-color:transparent;border:1px solid transparent;text-decoration-color:transparent;position:relative;outline-style:solid;outline-color:transparent}.hds-link-standalone__text{flex:1 0 0;text-decoration:underline;text-decoration-color:transparent;transition:text-decoration-color .25s ease-in}#login-toggle+div footer button,.consul-intention-fieldsets .permissions>button,.empty-state>ul>li>*,.empty-state>ul>li>:active,.empty-state>ul>li>label>button,.empty-state>ul>li>label>button:active,.hds-pagination-nav__control,.hds-side-nav__list-item-link,.hds-tabs__tab,.modal-dialog [role=document] dd a,.modal-dialog [role=document] p a,.oidc-select button.reset,.search-bar-status .remove-all button,a,main dd a,main dd a:active,main p a,main p a:active{text-decoration:none}.hds-link-standalone--size-small .hds-link-standalone__icon{width:.75rem;height:.75rem}.hds-link-standalone--size-small .hds-link-standalone__text{font-size:.8125rem;line-height:1.231}.hds-link-standalone--size-medium .hds-link-standalone__icon{width:1rem;height:1rem}.hds-link-standalone--size-medium .hds-link-standalone__text{font-size:.875rem;line-height:1.143}.hds-link-standalone--size-large .hds-link-standalone__icon{width:1.5rem;height:1.5rem}.hds-link-standalone--size-large .hds-link-standalone__text{font-size:1rem;line-height:1.5}.hds-link-standalone--color-primary{color:var(--token-color-foreground-action)}.hds-link-standalone--color-primary.mock-hover,.hds-link-standalone--color-primary:hover{color:var(--token-color-foreground-action-hover)}.hds-link-standalone--color-primary.mock-hover .hds-link-standalone__text,.hds-link-standalone--color-primary:hover .hds-link-standalone__text{text-decoration-color:#4e81e8}.hds-link-standalone--color-primary.mock-active,.hds-link-standalone--color-primary:active{color:var(--token-color-foreground-action-active)}.hds-link-standalone--color-primary.mock-active .hds-link-standalone__text,.hds-link-standalone--color-primary:active .hds-link-standalone__text{text-decoration-color:#396ed6}.hds-link-standalone--color-primary.mock-active::before,.hds-link-standalone--color-primary:active::before{background-color:var(--token-color-surface-action)}.hds-link-standalone--color-secondary{color:var(--token-color-foreground-strong)}.hds-link-standalone--color-secondary.mock-hover .hds-link-standalone__text,.hds-link-standalone--color-secondary:hover .hds-link-standalone__text{text-decoration-color:#4d4d4f}.hds-link-standalone--color-secondary.mock-active,.hds-link-standalone--color-secondary:active{color:var(--token-color-foreground-primary)}.hds-link-standalone--color-secondary.mock-active .hds-link-standalone__text,.hds-link-standalone--color-secondary:active .hds-link-standalone__text{text-decoration-color:#6e7075}.hds-link-standalone--color-secondary.mock-active::before,.hds-link-standalone--color-secondary:active::before{background-color:var(--token-color-surface-interactive-active)}.hds-link-standalone::before{position:absolute;top:0;right:-5px;bottom:0;left:-5px;z-index:-1;border-radius:5px;content:""}.hds-link-standalone:focus:not(:focus-visible)::before{box-shadow:none}.hds-link-standalone:focus-visible::before,.hds-pagination-nav__control.mock-focus::before,.hds-pagination-nav__control:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-link-standalone.mock-focus.mock-active::before,.hds-link-standalone:focus:active::before{box-shadow:none}.hds-link-standalone.hds-link-standalone--icon-position-leading::before{right:-7px}.hds-link-standalone.hds-link-standalone--icon-position-trailing::before{left:-7px}.hds-menu-primitive{position:relative;width:-moz-fit-content;width:fit-content}.hds-modal{z-index:50;flex-direction:column;padding:0;background:var(--token-color-surface-primary);border:none;border-radius:8px;box-shadow:var(--token-surface-overlay-box-shadow)}.hds-modal[open]{position:fixed;display:flex}.hds-modal::backdrop{display:none}.hds-modal__overlay{position:fixed;top:0;right:0;bottom:0;left:0;z-index:50;background:var(--token-color-palette-neutral-700);opacity:.5}.hds-modal__header{display:flex;flex:none;gap:16px;align-items:flex-start;padding:16px 24px;border-top-left-radius:inherit;border-top-right-radius:inherit}.hds-modal__icon{flex:none;align-self:center}.freetext-filter>label,.freetext-filter_input,.hds-modal__title{flex-grow:1}.hds-modal__tagline{margin-bottom:4px}.hds-modal__dismiss{align-self:center}.hds-modal__body{flex:1 1 auto;padding:24px;overflow-y:auto;overscroll-behavior:contain}.hds-modal__footer{flex:none;padding:16px 24px;background:var(--token-color-surface-faint);border-top:1px solid var(--token-color-border-primary);border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.hds-modal--size-small{width:min(400px,95vw)}.hds-modal--size-medium{width:min(600px,95vw)}.hds-modal--size-large{width:min(800px,95vw)}.hds-modal--color-neutral .hds-modal__header{color:var(--token-color-foreground-strong);background:var(--token-color-surface-faint);border-bottom:1px solid var(--token-color-border-primary)}.hds-modal--color-neutral .hds-modal__tagline{color:var(--token-color-foreground-faint)}.hds-modal--color-warning .hds-modal__header,.hds-modal--color-warning .hds-modal__tagline{color:var(--token-color-foreground-warning-on-surface)}.hds-modal--color-warning .hds-modal__header{background:var(--token-color-surface-warning);border-bottom:1px solid var(--token-color-border-warning)}.hds-modal--color-critical .hds-modal__header,.hds-modal--color-critical .hds-modal__tagline{color:var(--token-color-foreground-critical-on-surface)}.hds-modal--color-critical .hds-modal__header{background:var(--token-color-surface-critical);border-bottom:1px solid var(--token-color-border-critical)}.hds-page-header{position:relative;display:flex;flex-direction:column;gap:16px;container-type:inline-size}.hds-page-header__body{display:flex;flex-direction:column;gap:8px 16px}.hds-page-header__body .hds-icon-tile{flex-shrink:0}@container (min-width:400px){.hds-page-header__body{flex-direction:row}}.hds-page-header__main{display:flex;flex-direction:column;flex-grow:1;gap:16px;align-items:start;justify-content:start}@container (min-width:768px){.hds-page-header__main{flex-direction:row;justify-content:space-between;min-width:0}}.hds-page-header__content{display:flex;flex-direction:column;flex-grow:1;gap:8px;min-width:0;max-width:100%}.hds-page-header__title-wrapper{display:flex;flex-direction:row;flex-wrap:wrap;gap:8px 16px;align-items:center}.hds-page-header__title{max-width:100%;overflow-wrap:break-word}.hds-page-header__badges-wrapper{display:flex;flex-wrap:wrap;gap:8px}.hds-page-header__metadata{display:flex;flex-direction:column;gap:4px}.hds-page-header__description,.hds-page-header__subtitle{overflow-wrap:break-word}.hds-page-header__actions{display:flex;flex-direction:row;flex-shrink:0;flex-wrap:wrap;gap:16px;align-items:center}.hds-pagination{display:grid;grid-template-areas:"info nav selector";grid-template-rows:auto;grid-template-columns:1fr auto 1fr;align-items:center;margin:0 auto}@media screen and (max-width:1000px){.hds-pagination{display:flex;flex-wrap:wrap;justify-content:center}.hds-pagination .hds-pagination-info{margin-top:var(--token-pagination-child-spacing-vertical);margin-left:var(--token-pagination-child-spacing-horizontal)}}.hds-pagination .hds-pagination-info{grid-area:info;justify-self:flex-start;margin-right:var(--token-pagination-child-spacing-horizontal)}.hds-pagination .hds-pagination-nav{grid-area:nav}@media screen and (max-width:1000px){.hds-pagination .hds-pagination-nav{justify-content:center;order:-1;width:100%}.hds-pagination .hds-pagination-size-selector{margin-top:var(--token-pagination-child-spacing-vertical);margin-right:var(--token-pagination-child-spacing-horizontal)}}.hds-pagination .hds-pagination-size-selector{grid-area:selector;justify-self:flex-end;margin-left:var(--token-pagination-child-spacing-horizontal)}.hds-pagination-info{white-space:nowrap}.hds-pagination-nav{display:flex}.hds-pagination-nav__page-list{display:flex;margin:0;padding:0}.hds-pagination-nav__page-item{list-style-type:none}.hds-pagination-nav__control{display:flex;align-items:center;height:var(--token-pagination-nav-control-height);padding:0 calc(var(--token-pagination-nav-control-padding-horizontal) - 1px);color:var(--token-color-foreground-primary);background-color:transparent;border:1px solid transparent;position:relative;outline-style:solid;outline-color:transparent;isolation:isolate}.hds-pagination-nav__control::before{position:absolute;top:var(--token-pagination-nav-control-focus-inset);right:var(--token-pagination-nav-control-focus-inset);bottom:var(--token-pagination-nav-control-focus-inset);left:var(--token-pagination-nav-control-focus-inset);z-index:-1;border-radius:5px;content:""}.hds-pagination-nav__control:focus:not(:focus-visible)::before{box-shadow:none}.hds-pagination-nav__control:focus-visible::before,.hds-side-nav__home-link.mock-focus.mock-focus::before,.hds-side-nav__home-link.mock-focus:focus::before,.hds-side-nav__home-link:focus.mock-focus::before,.hds-side-nav__home-link:focus:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-pagination-nav__control.mock-focus.mock-active::before,.hds-pagination-nav__control:focus:active::before{box-shadow:none}.hds-pagination-nav__control.mock-hover,.hds-pagination-nav__control:hover{color:var(--token-color-foreground-action-hover)}.hds-pagination-nav__control.mock-active,.hds-pagination-nav__control:active{color:var(--token-color-foreground-action-active)}.hds-pagination-nav__arrow{gap:var(--token-pagination-nav-control-icon-spacing)}.hds-pagination-nav__arrow.mock-disabled,.hds-pagination-nav__arrow:disabled{color:var(--token-color-foreground-disabled);cursor:not-allowed}.hds-pagination-nav__arrow--direction-prev{flex-direction:row;justify-content:flex-start}.hds-pagination-nav__arrow--direction-next{flex-direction:row-reverse;justify-content:flex-end}.hds-pagination-nav__number--is-selected{position:relative;color:var(--token-color-foreground-action)}.hds-pagination-nav__number--is-selected:hover{color:var(--token-color-foreground-action-hover)}.hds-pagination-nav__number--is-selected:active{color:var(--token-color-foreground-action-active)}.hds-pagination-nav__number--is-selected::after{position:absolute;right:calc(var(--token-pagination-nav-indicator-spacing) - 1px);bottom:-1px;left:calc(var(--token-pagination-nav-indicator-spacing) - 1px);height:var(--token-pagination-nav-indicator-height);margin:0 auto;background-color:currentColor;border-radius:2px;content:""}.hds-pagination-nav__ellipsis{display:flex;align-items:center;height:var(--token-pagination-nav-control-height);padding:0 var(--token-pagination-nav-control-padding-horizontal);color:var(--token-color-foreground-faint)}.hds-pagination-size-selector{display:flex;align-items:center}.hds-pagination-size-selector>label{white-space:nowrap}.hds-pagination-size-selector>select{height:28px;margin-left:12px;padding:0 24px 0 8px;font-size:var(--token-typography-body-100-font-size);font-family:var(--token-typography-body-100-font-family);line-height:var(--token-typography-body-100-line-height);background-position:center right 5px}.hds-reveal{width:-moz-fit-content;width:fit-content}.hds-reveal__toggle-button{min-height:1.75rem;padding:.313rem .313rem .313rem .188rem}@media (prefers-reduced-motion:no-preference){.hds-reveal__toggle-button .flight-icon-chevron-down{transition:transform .3s}}.hds-reveal__toggle-button--is-open .flight-icon-chevron-down{transform:rotate(-180deg)}.hds-reveal__content{margin-top:4px}.hds-segmented-group{display:inline-flex}.hds-side-nav--is-desktop .hds-side-nav__overlay,.hds-side-nav__toggle-button.mock-focus::after,.hds-side-nav__toggle-button.mock-focus::before,.hds-side-nav__toggle-button:focus-visible::after,.hds-side-nav__toggle-button:focus-visible::before{display:none}.hds-segmented-group>.hds-button:first-child,.hds-segmented-group>.hds-dropdown:first-child,.hds-segmented-group>.hds-form-select:first-child,.hds-segmented-group>.hds-form-text-input:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.hds-segmented-group>.hds-button:first-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-button:first-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-button:first-child::before,.hds-segmented-group>.hds-dropdown:first-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-dropdown:first-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-dropdown:first-child::before,.hds-segmented-group>.hds-form-select:first-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-form-select:first-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-form-select:first-child::before,.hds-segmented-group>.hds-form-text-input:first-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-form-text-input:first-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-form-text-input:first-child::before{border-top-right-radius:inherit;border-bottom-right-radius:inherit}.hds-segmented-group>.hds-button:last-child,.hds-segmented-group>.hds-dropdown:last-child,.hds-segmented-group>.hds-form-select:last-child,.hds-segmented-group>.hds-form-text-input:last-child{margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.hds-segmented-group>.hds-button:last-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-button:last-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-button:last-child::before,.hds-segmented-group>.hds-dropdown:last-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-dropdown:last-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-dropdown:last-child::before,.hds-segmented-group>.hds-form-select:last-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-form-select:last-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-form-select:last-child::before,.hds-segmented-group>.hds-form-text-input:last-child .hds-dropdown-toggle-button,.hds-segmented-group>.hds-form-text-input:last-child .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-form-text-input:last-child::before{border-top-left-radius:inherit;border-bottom-left-radius:inherit}.hds-segmented-group>.hds-button:not(:first-child,:last-child),.hds-segmented-group>.hds-dropdown:not(:first-child,:last-child),.hds-segmented-group>.hds-form-select:not(:first-child,:last-child),.hds-segmented-group>.hds-form-text-input:not(:first-child,:last-child){margin-left:-1px;border-radius:0}.hds-segmented-group>.hds-button:not(:first-child,:last-child) .hds-dropdown-toggle-button,.hds-segmented-group>.hds-button:not(:first-child,:last-child) .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-button:not(:first-child,:last-child)::before,.hds-segmented-group>.hds-dropdown:not(:first-child,:last-child) .hds-dropdown-toggle-button,.hds-segmented-group>.hds-dropdown:not(:first-child,:last-child) .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-dropdown:not(:first-child,:last-child)::before,.hds-segmented-group>.hds-form-select:not(:first-child,:last-child) .hds-dropdown-toggle-button,.hds-segmented-group>.hds-form-select:not(:first-child,:last-child) .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-form-select:not(:first-child,:last-child)::before,.hds-segmented-group>.hds-form-text-input:not(:first-child,:last-child) .hds-dropdown-toggle-button,.hds-segmented-group>.hds-form-text-input:not(:first-child,:last-child) .hds-dropdown-toggle-button::before,.hds-segmented-group>.hds-form-text-input:not(:first-child,:last-child)::before{border-radius:inherit}.hds-segmented-group>.hds-button .hds-dropdown-toggle-button:focus,.hds-segmented-group>.hds-button.mock-focus,.hds-segmented-group>.hds-button:focus,.hds-segmented-group>.hds-dropdown .hds-dropdown-toggle-button:focus,.hds-segmented-group>.hds-dropdown.mock-focus,.hds-segmented-group>.hds-dropdown:focus,.hds-segmented-group>.hds-form-select .hds-dropdown-toggle-button:focus,.hds-segmented-group>.hds-form-select.mock-focus,.hds-segmented-group>.hds-form-select:focus,.hds-segmented-group>.hds-form-text-input .hds-dropdown-toggle-button:focus,.hds-segmented-group>.hds-form-text-input.mock-focus,.hds-segmented-group>.hds-form-text-input:focus{z-index:1}.hds-separator{border:none;border-top:1px solid var(--token-color-border-primary)}.hds-separator--spacing-24{margin:24px 0}.hds-separator--spacing-0{margin:0}.hds-side-nav{top:0;z-index:20;width:var(--hds-app-sidenav-width-fixed);height:100vh;min-height:100vh;isolation:isolate}.hds-side-nav.hds-side-nav--is-responsive{transition:width var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing)}.hds-side-nav.hds-side-nav--is-mobile{width:var(--hds-app-sidenav-width-minimized)}.hds-side-nav.hds-side-nav--is-desktop.hds-side-nav--is-not-minimized{width:var(--hds-app-sidenav-width-expanded)}.hds-side-nav--is-minimized .hds-side-nav__wrapper,.hds-side-nav.hds-side-nav--is-desktop.hds-side-nav--is-minimized{width:var(--hds-app-sidenav-width-minimized)}.hds-side-nav__overlay{position:fixed;z-index:-1;inset:0;background-color:var(--token-color-palette-neutral-700);opacity:.2;transition:opacity var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing) var(--hds-app-sidenav-animation-delay)}.hds-side-nav--is-minimized .hds-side-nav__overlay{opacity:0;pointer-events:none}.hds-side-nav__wrapper{display:flex;flex-direction:column;height:100%;color:var(--token-side-nav-color-foreground-primary);background:var(--token-side-nav-color-surface-primary);border-right:var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color)}.hds-side-nav--is-responsive .hds-side-nav__wrapper{transition:width var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing)}.hds-side-nav--is-not-minimized .hds-side-nav__wrapper{width:var(--hds-app-sidenav-width-expanded)}.hds-side-nav__wrapper-header{padding-top:var(--token-side-nav-wrapper-padding-vertical);padding-right:var(--token-side-nav-wrapper-padding-horizontal);padding-bottom:8px;padding-left:var(--token-side-nav-wrapper-padding-horizontal);transition:padding var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing)}.hds-side-nav--is-minimized .hds-side-nav__wrapper-header{padding-top:var(--token-side-nav-wrapper-padding-vertical-minimized);padding-right:var(--token-side-nav-wrapper-padding-horizontal-minimized);padding-left:var(--token-side-nav-wrapper-padding-horizontal-minimized)}.hds-side-nav__wrapper-body,.hds-side-nav__wrapper-footer{padding:var(--token-side-nav-wrapper-padding-vertical) var(--token-side-nav-wrapper-padding-horizontal)}.hds-side-nav__wrapper-body{flex:1;overflow-x:hidden;overflow-y:auto}.hds-side-nav--is-minimized .hds-side-nav-hide-when-minimized{visibility:hidden!important;opacity:0;pointer-events:none}.hds-side-nav--is-not-minimized .hds-side-nav-hide-when-minimized{visibility:visible;opacity:1;transition:opacity var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing) var(--hds-app-sidenav-animation-delay)}.hds-side-nav--is-animating .hds-side-nav-hide-when-minimized{pointer-events:none}.hds-side-nav-header{display:flex;align-items:center;justify-content:space-between}.hds-side-nav-header__logo-container{display:flex;flex:none;align-items:center;justify-content:center;width:var(--token-side-nav-header-home-link-logo-size);height:var(--token-side-nav-header-home-link-logo-size);transition:width var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing),height var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing)}.hds-side-nav--is-minimized .hds-side-nav-header__logo-container{width:var(--token-side-nav-header-home-link-logo-size-minimized);height:var(--token-side-nav-header-home-link-logo-size-minimized)}.hds-side-nav__home-link{color:var(--token-side-nav-color-foreground-strong);background-color:transparent;border:1px solid transparent;border-radius:var(--token-side-nav-body-list-item-border-radius);cursor:pointer;display:block;width:100%;height:100%;padding:calc(var(--token-side-nav-header-home-link-padding) - 1px)}.hds-side-nav__home-link.mock-focus,.hds-side-nav__home-link:focus{position:relative;outline-style:solid;outline-color:transparent;isolation:isolate}.hds-side-nav__home-link.mock-focus::before,.hds-side-nav__home-link:focus::before{position:absolute;top:-1px;right:-1px;bottom:-1px;left:-1px;z-index:-1;border-radius:5px;content:""}.hds-side-nav__home-link.mock-focus:focus:not(:focus-visible)::before,.hds-side-nav__home-link:focus:focus:not(:focus-visible)::before{box-shadow:none}.hds-side-nav__home-link.mock-focus:focus-visible::before,.hds-side-nav__home-link:focus:focus-visible::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__home-link.mock-focus.mock-focus.mock-active::before,.hds-side-nav__home-link.mock-focus:focus:active::before,.hds-side-nav__home-link:focus.mock-focus.mock-active::before,.hds-side-nav__home-link:focus:focus:active::before{box-shadow:none}.hds-side-nav__home-link.mock-hover,.hds-side-nav__home-link:hover{color:var(--token-side-nav-color-foreground-strong);background:var(--token-side-nav-color-surface-interactive-hover)}.hds-side-nav__home-link.mock-active,.hds-side-nav__home-link:active{color:var(--token-side-nav-color-foreground-strong);background:var(--token-side-nav-color-surface-interactive-active)}.hds-side-nav-header__actions-container{display:flex;gap:var(--token-side-nav-header-actions-spacing)}.hds-side-nav__dropdown .hds-dropdown-toggle-button,.hds-side-nav__dropdown .hds-dropdown-toggle-icon{color:var(--token-side-nav-color-foreground-strong);background-color:transparent;border:1px solid transparent;border-radius:var(--token-side-nav-body-list-item-border-radius);cursor:pointer;border-color:var(--token-color-palette-neutral-500)}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus{position:relative;outline-style:solid;outline-color:transparent;isolation:isolate}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus::before{position:absolute;top:-1px;right:-1px;bottom:-1px;left:-1px;z-index:-1;border-radius:5px;content:""}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus.mock-focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus:focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus.mock-focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus:focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus.mock-focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus:focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus.mock-focus::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus:focus:not(:focus-visible)::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus:focus:not(:focus-visible)::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus:focus:not(:focus-visible)::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus:focus:not(:focus-visible)::before{box-shadow:none}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus:focus-visible::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus:focus-visible::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus:focus-visible::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus:focus-visible::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus.mock-focus.mock-active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-focus:focus:active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus.mock-focus.mock-active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-button:focus:focus:active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus.mock-focus.mock-active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-focus:focus:active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus.mock-focus.mock-active::before,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:focus:focus:active::before{box-shadow:none}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-hover,.hds-side-nav__dropdown .hds-dropdown-toggle-button:hover,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-hover,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:hover{color:var(--token-side-nav-color-foreground-strong);background:var(--token-side-nav-color-surface-interactive-hover)}.hds-side-nav__dropdown .hds-dropdown-toggle-button.mock-active,.hds-side-nav__dropdown .hds-dropdown-toggle-button:active,.hds-side-nav__dropdown .hds-dropdown-toggle-icon.mock-active,.hds-side-nav__dropdown .hds-dropdown-toggle-icon:active{color:var(--token-side-nav-color-foreground-strong);background:var(--token-side-nav-color-surface-interactive-active);border-color:var(--token-color-palette-neutral-400)}.hds-side-nav__icon-button{color:var(--token-side-nav-color-foreground-strong);background-color:transparent;border:1px solid transparent;border-radius:var(--token-side-nav-body-list-item-border-radius);cursor:pointer;border-color:var(--token-color-palette-neutral-500);display:flex;align-items:center;justify-content:center;width:36px;height:36px;padding:5px}.hds-side-nav__icon-button.mock-focus,.hds-side-nav__icon-button:focus{position:relative;outline-style:solid;outline-color:transparent;isolation:isolate}.hds-side-nav__icon-button.mock-focus::before,.hds-side-nav__icon-button:focus::before{position:absolute;top:-1px;right:-1px;bottom:-1px;left:-1px;z-index:-1;border-radius:5px;content:""}.hds-side-nav__icon-button.mock-focus.mock-focus::before,.hds-side-nav__icon-button.mock-focus:focus::before,.hds-side-nav__icon-button:focus.mock-focus::before,.hds-side-nav__icon-button:focus:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__icon-button.mock-focus:focus:not(:focus-visible)::before,.hds-side-nav__icon-button:focus:focus:not(:focus-visible)::before{box-shadow:none}.hds-side-nav__icon-button.mock-focus:focus-visible::before,.hds-side-nav__icon-button:focus:focus-visible::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__icon-button.mock-focus.mock-focus.mock-active::before,.hds-side-nav__icon-button.mock-focus:focus:active::before,.hds-side-nav__icon-button:focus.mock-focus.mock-active::before,.hds-side-nav__icon-button:focus:focus:active::before{box-shadow:none}.hds-side-nav__icon-button.mock-hover,.hds-side-nav__icon-button:hover{color:var(--token-side-nav-color-foreground-strong);background:var(--token-side-nav-color-surface-interactive-hover)}.hds-side-nav__icon-button.mock-active,.hds-side-nav__icon-button:active{color:var(--token-side-nav-color-foreground-strong);background:var(--token-side-nav-color-surface-interactive-active);border-color:var(--token-color-palette-neutral-400)}.hds-side-nav__content{margin:0 calc(var(--token-side-nav-wrapper-padding-horizontal) * -1)}.hds-side-nav__content-panels{display:grid;grid-template-columns:repeat(5,var(--hds-app-sidenav-width-expanded));width:100%}.hds-side-nav__content-panel{padding:0 var(--token-side-nav-wrapper-padding-horizontal)}.hds-side-nav__list-title{display:flex;align-items:center;min-height:var(--token-side-nav-body-list-item-height);margin-top:var(--token-side-nav-body-list-margin-vertical);padding:9px var(--token-side-nav-body-list-item-padding-horizontal);color:var(--token-side-nav-color-foreground-faint)}.hds-side-nav__list-wrapper:first-child .hds-side-nav__list-item:first-child>.hds-side-nav__list-title{margin-top:0}.hds-side-nav__list,.hds-side-nav__list-wrapper{margin:0;padding:0}.hds-side-nav__list-item{list-style-type:none}.hds-side-nav__list-item+.hds-side-nav__list-item{margin-top:var(--token-side-nav-body-list-item-spacing-vertical)}.hds-side-nav__list-item-link{display:flex;gap:var(--token-side-nav-body-list-item-content-spacing-horizontal);align-items:center;width:100%;min-height:var(--token-side-nav-body-list-item-height);padding:var(--token-side-nav-body-list-item-padding-vertical) var(--token-side-nav-body-list-item-padding-horizontal);color:var(--token-side-nav-color-foreground-primary);background:var(--token-side-nav-color-surface-primary);border-color:transparent;border-radius:var(--token-side-nav-body-list-item-border-radius)}.hds-side-nav__list-item-link.active .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link.active .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link.active .hds-side-nav__list-item-text,.hds-side-nav__list-item-link.mock-active .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link.mock-active .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link.mock-active .hds-side-nav__list-item-text,.hds-side-nav__list-item-link.mock-hover .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link.mock-hover .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link.mock-hover .hds-side-nav__list-item-text,.hds-side-nav__list-item-link:active .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link:active .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link:active .hds-side-nav__list-item-text,.hds-side-nav__list-item-link:hover .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link:hover .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link:hover .hds-side-nav__list-item-text,.hds-side-nav__list-item-link:hover:focus .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link:hover:focus .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link:hover:focus .hds-side-nav__list-item-text{color:var(--token-side-nav-color-foreground-strong)}.hds-side-nav__list-item-link.mock-focus,.hds-side-nav__list-item-link:focus{position:relative;outline-style:solid;outline-color:transparent;isolation:isolate}.hds-side-nav__list-item-link.mock-focus::before,.hds-side-nav__list-item-link:focus::before{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;border-radius:5px;content:""}.hds-side-nav__list-item-link.mock-focus.mock-focus::before,.hds-side-nav__list-item-link.mock-focus:focus::before,.hds-side-nav__list-item-link:focus.mock-focus::before,.hds-side-nav__list-item-link:focus:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__list-item-link.mock-focus:focus:not(:focus-visible)::before,.hds-side-nav__list-item-link:focus:focus:not(:focus-visible)::before{box-shadow:none}.hds-side-nav__list-item-link.mock-focus:focus-visible::before,.hds-side-nav__list-item-link:focus:focus-visible::before,.hds-table__th-sort button.mock-focus::before,.hds-table__th-sort button:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-side-nav__list-item-link.mock-focus.mock-focus.mock-active::before,.hds-side-nav__list-item-link.mock-focus:focus:active::before,.hds-side-nav__list-item-link:focus.mock-focus.mock-active::before,.hds-side-nav__list-item-link:focus:focus:active::before{box-shadow:none}.hds-side-nav__list-item-link.mock-hover,.hds-side-nav__list-item-link:hover{background:var(--token-side-nav-color-surface-interactive-hover);border-color:transparent}.hds-side-nav__list-item-link.active,.hds-side-nav__list-item-link.mock-active,.hds-side-nav__list-item-link:active,.hds-side-nav__list-item-link:hover:focus{background:var(--token-side-nav-color-surface-interactive-active)}.hds-side-nav__list-item-link.active .hds-badge,.hds-side-nav__list-item-link.active .hds-badge-count,.hds-side-nav__list-item-link.mock-active .hds-badge,.hds-side-nav__list-item-link.mock-active .hds-badge-count,.hds-side-nav__list-item-link:active .hds-badge,.hds-side-nav__list-item-link:active .hds-badge-count,.hds-side-nav__list-item-link:hover:focus .hds-badge,.hds-side-nav__list-item-link:hover:focus .hds-badge-count{color:var(--token-color-foreground-primary);background:var(--token-color-surface-strong)}.hds-side-nav__list-item-link--back-link.mock-active .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link--back-link.mock-active .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link--back-link.mock-active .hds-side-nav__list-item-text,.hds-side-nav__list-item-link--back-link:active .hds-side-nav__list-item-icon-leading,.hds-side-nav__list-item-link--back-link:active .hds-side-nav__list-item-icon-trailing,.hds-side-nav__list-item-link--back-link:active .hds-side-nav__list-item-text,.hds-side-nav__list-item-text{color:var(--token-side-nav-color-foreground-primary)}.hds-side-nav__list-item-link--back-link.mock-active,.hds-side-nav__list-item-link--back-link:active{background:var(--token-side-nav-color-surface-primary)}.hds-side-nav__list-item-text{text-align:left}.hds-side-nav__list-item-icon-leading{flex:none}.hds-side-nav__list-item-icon-trailing{flex:none;margin-left:auto}.hds-side-nav__toggle-button{position:absolute;top:22px;left:calc(var(--token-side-nav-wrapper-border-width) * -1);z-index:1;display:flex;flex-direction:row-reverse;align-items:center;width:26px;height:36px;padding:0 4px;color:var(--token-color-foreground-high-contrast);background:0 0;background-color:var(--token-side-nav-color-surface-primary);border:var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);border-left-color:transparent;border-top-right-radius:var(--token-side-nav-toggle-button-border-radius);border-bottom-right-radius:var(--token-side-nav-toggle-button-border-radius);transform:translateX(var(--hds-app-sidenav-width-expanded));cursor:pointer;transition:transform var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing),width var(--hds-app-sidenav-animation-duration) var(--hds-app-sidenav-animation-easing)}.hds-side-nav__toggle-button::after,.hds-side-nav__toggle-button::before{position:absolute;left:calc(var(--token-side-nav-wrapper-border-width) * -1);width:calc(var(--token-side-nav-toggle-button-border-radius) * 2);height:calc(var(--token-side-nav-toggle-button-border-radius) * 2);border-left:var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);box-sizing:border-box;content:""}.hds-side-nav__toggle-button::before{top:calc(var(--token-side-nav-toggle-button-border-radius) * -2);border-bottom:var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);border-bottom-left-radius:var(--token-side-nav-toggle-button-border-radius);box-shadow:0 var(--token-side-nav-toggle-button-border-radius) 0 var(--token-side-nav-color-surface-primary)}.hds-side-nav__toggle-button::after{bottom:calc(var(--token-side-nav-toggle-button-border-radius) * -2);border-top:var(--token-side-nav-wrapper-border-width) solid var(--token-side-nav-wrapper-border-color);border-top-left-radius:var(--token-side-nav-toggle-button-border-radius);box-shadow:0 calc(var(--token-side-nav-toggle-button-border-radius) * -1) 0 var(--token-side-nav-color-surface-primary)}.hds-side-nav__toggle-button.mock-hover,.hds-side-nav__toggle-button:hover{width:30px;background-color:var(--token-side-nav-color-surface-interactive-hover)}.hds-side-nav__toggle-button.mock-hover::before,.hds-side-nav__toggle-button:hover::before{box-shadow:0 var(--token-side-nav-toggle-button-border-radius) 0 var(--token-side-nav-color-surface-interactive-hover)}.hds-side-nav__toggle-button.mock-hover::after,.hds-side-nav__toggle-button:hover::after{box-shadow:0 calc(var(--token-side-nav-toggle-button-border-radius) * -1) 0 var(--token-side-nav-color-surface-interactive-hover)}.hds-side-nav__toggle-button.mock-active,.hds-side-nav__toggle-button:active{background-color:var(--token-side-nav-color-surface-interactive-active)}.hds-side-nav__toggle-button.mock-active::before,.hds-side-nav__toggle-button:active::before{box-shadow:0 var(--token-side-nav-toggle-button-border-radius) 0 var(--token-side-nav-color-surface-interactive-active)}.hds-side-nav__toggle-button.mock-active::after,.hds-side-nav__toggle-button:active::after{box-shadow:0 calc(var(--token-side-nav-toggle-button-border-radius) * -1) 0 var(--token-side-nav-color-surface-interactive-active)}.hds-side-nav__toggle-button.mock-focus,.hds-side-nav__toggle-button:focus-visible{border-color:var(--token-color-focus-action-internal);outline:var(--token-color-focus-action-external) solid 3px}.hds-table__th-sort button,.hds-tabs__tab-button,.hds-tooltip-button{outline-color:transparent;outline-style:solid}.hds-side-nav--is-minimized .hds-side-nav__toggle-button{transform:translateX(var(--hds-app-sidenav-width-minimized))}.hds-side-nav .ember-a11y-refocus-skip-link{top:10px;left:10px;z-index:20;width:-moz-max-content;width:max-content;padding:2px 10px 4px;color:var(--token-color-foreground-action);font-family:var(--token-typography-display-200-font-family);background-color:var(--token-color-surface-faint);border-radius:3px;transform:translateY(-200%);transition:.6s ease-in-out}.hds-side-nav .ember-a11y-refocus-skip-link:focus{transform:translateY(0)}.hds-stepper-indicator-step{position:relative;width:24px;height:24px}.hds-stepper-indicator-step__svg-hexagon{width:100%;height:100%;filter:drop-shadow(0 1px 1px rgba(101, 106, 118, .05))}.hds-stepper-indicator-step__status{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;align-items:center;justify-content:center}.hds-stepper-indicator-step__icon{width:12px;height:12px}.hds-stepper-indicator-step__text{width:20px;overflow:hidden;white-space:nowrap;text-align:center;-webkit-user-select:none;-moz-user-select:none;user-select:none}.hds-stepper-indicator-step--status-incomplete .hds-stepper-indicator-step__status{color:var(--token-color-foreground-strong)}.hds-stepper-indicator-step--status-complete .hds-stepper-indicator-step__status,.hds-stepper-indicator-step--status-processing .hds-stepper-indicator-step__status,.hds-stepper-indicator-step--status-progress .hds-stepper-indicator-step__status{color:var(--token-color-foreground-high-contrast)}.hds-stepper-indicator-step--status-incomplete .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-surface-faint);stroke:var(--token-color-foreground-strong)}.hds-stepper-indicator-step--status-complete .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--status-processing .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--status-progress .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-foreground-strong);stroke:var(--token-color-foreground-strong)}.hds-stepper-indicator-step--is-interactive{cursor:pointer}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-incomplete .hds-stepper-indicator-step__status{color:var(--token-color-foreground-primary)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-processing .hds-stepper-indicator-step__status,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-progress .hds-stepper-indicator-step__status{color:var(--token-color-foreground-high-contrast)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-incomplete .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-surface-interactive);stroke:var(--token-color-border-strong)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-incomplete.mock-hover .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-incomplete:hover .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-surface-interactive-hover)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-incomplete.mock-active .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-incomplete:active .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-surface-interactive-active)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-progress .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-200);stroke:var(--token-color-palette-blue-300)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-progress.mock-hover .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-progress:hover .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-300);stroke:var(--token-color-palette-blue-400)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-progress.mock-active .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-progress:active .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-400);stroke:var(--token-color-palette-blue-400)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-processing .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-200);stroke:var(--token-color-palette-blue-300)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-processing.mock-hover .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-processing:hover .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-300);stroke:var(--token-color-palette-blue-400)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-processing.mock-active .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-processing:active .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-400);stroke:var(--token-color-palette-blue-400)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete .hds-stepper-indicator-step__status{color:var(--token-color-palette-blue-200)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-50);stroke:var(--token-color-palette-blue-300)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete.mock-active .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete.mock-hover .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete:active .hds-stepper-indicator-step__svg-hexagon path,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete:hover .hds-stepper-indicator-step__svg-hexagon path{fill:var(--token-color-palette-blue-100);stroke:var(--token-color-palette-blue-400)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete.mock-hover .hds-stepper-indicator-step__status,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete:hover .hds-stepper-indicator-step__status{color:var(--token-color-palette-blue-300)}.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete.mock-active .hds-stepper-indicator-step__status,.hds-stepper-indicator-step--is-interactive.hds-stepper-indicator-step--status-complete:active .hds-stepper-indicator-step__status{color:var(--token-color-palette-blue-400)}.hds-stepper-indicator-task{position:relative;display:flex;align-items:center;justify-content:center;width:16px;height:16px;color:var(--token-color-foreground-strong)}.hds-stepper-indicator-task__icon{width:12px;height:12px}.hds-stepper-indicator-task--is-interactive{cursor:pointer}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-incomplete{color:var(--token-color-palette-neutral-300)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-incomplete.mock-hover,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-incomplete:hover{color:var(--token-color-palette-blue-300)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-incomplete.mock-active,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-incomplete:active{color:var(--token-color-palette-blue-400)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-progress{color:var(--token-color-palette-blue-200)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-progress.mock-hover,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-progress:hover{color:var(--token-color-palette-blue-300)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-progress.mock-active,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-progress:active{color:var(--token-color-palette-blue-400)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-processing{color:var(--token-color-palette-blue-200)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-processing.mock-hover,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-processing:hover{color:var(--token-color-palette-blue-300)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-processing.mock-active,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-processing:active{color:var(--token-color-palette-blue-400)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-complete{color:var(--token-color-palette-green-200)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-complete.mock-hover,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-complete:hover{color:var(--token-color-palette-green-300)}.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-complete.mock-active,.hds-stepper-indicator-task--is-interactive.hds-stepper-indicator-task--status-complete:active{color:var(--token-color-palette-green-400)}.hds-table{width:100%;border:1px solid var(--token-color-border-primary);border-radius:6px}.hds-table--layout-fixed{table-layout:fixed}.hds-table__thead .hds-table__tr{color:var(--token-color-foreground-strong);background-color:var(--token-color-surface-strong)}.hds-table__thead .hds-table__tr:first-of-type th:first-child{border-top-left-radius:5px}.hds-table__thead .hds-table__tr:first-of-type th:last-child{border-top-right-radius:5px}.hds-table__thead .hds-table__th,.hds-table__thead .hds-table__th-sort{min-height:48px}.hds-table__th,.hds-table__th-sort{text-align:left;border-top:none;border-right:none;border-bottom:1px solid var(--token-color-border-primary);border-left:none}.hds-table__th{padding:14px 16px 13px}.hds-table__th-sort{padding:0}.hds-table__th-sort button{width:100%;height:100%;min-height:48px;margin:0;padding:14px 16px 13px;text-align:inherit;background-color:transparent;border:1px solid transparent;border-radius:inherit;position:relative;isolation:isolate}.hds-table__th-sort button .hds-table__th-sort--button-content{display:flex;align-items:center}.hds-table__th-sort button .hds-table__th-sort--button-content .flight-icon{flex:none;margin-left:8px;color:var(--token-color-foreground-action)}.hds-table__th-sort button.mock-hover,.hds-table__th-sort button:hover{color:var(--token-color-foreground-strong);background-color:var(--token-color-palette-neutral-200);cursor:pointer}.hds-table__th-sort button::before{position:absolute;top:0;right:0;bottom:0;left:0;z-index:-1;border-radius:inherit;content:""}.hds-table__th-sort button:focus:not(:focus-visible)::before{box-shadow:none}.hds-table__th-sort button:focus-visible::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-table__th-sort button.mock-focus.mock-active::before,.hds-table__th-sort button:focus:active::before{box-shadow:none}.hds-table__th-sort button.mock-active,.hds-table__th-sort button:active{color:var(--token-color-foreground-strong);background-color:var(--token-color-palette-neutral-300)}.hds-table__tbody .hds-table__tr,.hds-tabs__tab,.hds-tag__dismiss-icon{color:var(--token-color-foreground-primary)}.hds-table--striped .hds-table__tbody .hds-table__tr:nth-child(even){background-color:var(--token-color-surface-faint)}.hds-table--density-short .hds-table__tbody td,.hds-table--density-short .hds-table__tbody th{padding:6px 16px 5px}.hds-table--density-medium .hds-table__tbody td,.hds-table--density-medium .hds-table__tbody th{padding:14px 16px 13px}.hds-table--density-tall .hds-table__tbody td,.hds-table--density-tall .hds-table__tbody th{padding:22px 16px 21px}.hds-table--valign-top .hds-table__tbody td,.hds-table--valign-top .hds-table__tbody th{vertical-align:top}.hds-table--valign-middle .hds-table__tbody td,.hds-table--valign-middle .hds-table__tbody th,.hds-tag{vertical-align:middle}.hds-table__td--text-right,.hds-table__th--text-right,.hds-table__th-sort--text-right{text-align:right}.hds-table__th-sort--text-right .hds-table__th-sort--button-content{justify-content:flex-end}.hds-table__td--text-center,.hds-table__th--text-center,.hds-table__th-sort--text-center{text-align:center}.hds-table__tbody .hds-table__tr{background-color:var(--token-color-surface-primary)}.hds-table__tbody .hds-table__tr td,.hds-table__tbody .hds-table__tr th{border-top:none;border-right:none;border-bottom:1px solid var(--token-color-border-primary);border-left:none}.hds-table__tbody .hds-table__tr:last-of-type td,.hds-table__tbody .hds-table__tr:last-of-type th{border-bottom:none}.hds-table__tbody .hds-table__tr:last-of-type td:first-child,.hds-table__tbody .hds-table__tr:last-of-type th:first-child{border-bottom-left-radius:5px}.hds-table__tbody .hds-table__tr:last-of-type td:last-child{border-bottom-right-radius:5px}.hds-tabs__tablist-wrapper{position:relative}.hds-tabs__tablist-wrapper::before{position:absolute;right:0;bottom:calc((var(--token-tabs-indicator-height) - var(--token-tabs-divider-height))/ 2);left:0;display:block;border-top:var(--token-tabs-divider-height) solid var(--token-color-border-primary);content:""}.hds-tabs__tablist{position:relative;display:flex;margin:0;padding:0;overflow-x:auto;-webkit-overflow-scrolling:touch}.hds-tabs__tab{position:relative;display:flex;align-items:center;height:var(--token-tabs-tab-height);margin:0;padding:var(--token-tabs-tab-padding-vertical) var(--token-tabs-tab-padding-horizontal);white-space:nowrap;list-style:none}.hds-tabs__tab.hds-tabs__tab--is-selected,.hds-tabs__tab.mock-hover,.hds-tabs__tab:hover{color:var(--token-color-foreground-action)}.hds-tabs__tab.hds-tabs__tab--is-selected:hover{color:var(--token-color-foreground-action-hover)}.hds-tabs__tab.hds-tabs__tab--is-selected:hover~.hds-tabs__tab-indicator{background:var(--token-color-foreground-action-hover)}.hds-tabs__tab-button{isolation:isolate;position:static;display:flex;gap:var(--token-tabs-tab-gutter);align-items:center;padding:0;color:inherit;background-color:transparent;border:none;border-radius:var(--token-tabs-tab-border-radius);cursor:pointer}.hds-tabs__tab-button::before{position:absolute;top:var(--token-tabs-tab-focus-inset);right:var(--token-tabs-tab-focus-inset);bottom:var(--token-tabs-tab-focus-inset);left:var(--token-tabs-tab-focus-inset);z-index:-1;border-radius:5px;content:""}.hds-tabs__tab-button.mock-focus::before,.hds-tabs__tab-button:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-tabs__tab-button:focus:not(:focus-visible)::before{box-shadow:none}.hds-tabs__tab-button:focus-visible::before,.hds-tag__dismiss.mock-focus.mock-focus,.hds-tag__dismiss.mock-focus:focus,.hds-tag__dismiss:focus.mock-focus,.hds-tag__dismiss:focus:focus,.hds-tag__link.mock-focus.mock-focus,.hds-tag__link.mock-focus:focus,.hds-tag__link:focus.mock-focus,.hds-tag__link:focus:focus{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-tabs__tab-button.mock-focus.mock-active::before,.hds-tabs__tab-button:focus:active::before{box-shadow:none}.hds-tabs__tab-button::after{position:absolute;content:"";inset:0}.hds-tabs__tab-indicator{position:absolute;right:0;bottom:0;left:var(--indicator-left-pos);z-index:10;display:block;width:var(--indicator-width);height:var(--token-tabs-indicator-height);background-color:var(--token-color-foreground-action);border-radius:var(--token-tabs-indicator-height)}.hds-tabs__panel[hidden],.readonly-codemirror .CodeMirror-cursors{display:none}.hds-tag,.hds-tag__dismiss,.hds-tag__link{background-color:var(--token-color-surface-interactive)}@media screen and (prefers-reduced-motion:no-preference){.hds-tabs__tab-indicator{transition-timing-function:var(--token-tabs-indicator-transition-function);transition-duration:var(--token-tabs-indicator-transition-duration);transition-property:left,width}}.tippy-box,.tippy-box[data-theme~=hds]{transition-property:transform,visibility,opacity}.hds-tag,:where(.hds-tooltip-button--is-inline){display:inline-flex}.hds-tag{align-items:stretch;line-height:1rem;border:1px solid var(--token-color-border-strong);border-radius:50px}.hds-tag__dismiss{flex:0 0 auto;margin:0;padding:6px 4px 6px 8px;border:none;border-radius:inherit;border-top-right-radius:0;border-bottom-right-radius:0}.hds-tag__dismiss-icon{width:12px;height:12px}.hds-tag__link,.hds-tag__text{flex:1 0 0;padding:3px 10px 5px;border-radius:inherit}.hds-tag__dismiss~.hds-tag__link,.hds-tag__dismiss~.hds-tag__text{padding:3px 8px 5px 6px;border-top-left-radius:0;border-bottom-left-radius:0}.hds-tag__dismiss,.hds-tag__link{cursor:pointer}.hds-tag__dismiss.mock-hover,.hds-tag__dismiss:hover,.hds-tag__link.mock-hover,.hds-tag__link:hover{background-color:var(--token-color-surface-interactive-hover)}.hds-tag__dismiss.mock-active,.hds-tag__dismiss:active,.hds-tag__link.mock-active,.hds-tag__link:active{background-color:var(--token-color-surface-interactive-active)}.hds-tag__dismiss.mock-focus,.hds-tag__dismiss:focus,.hds-tag__link.mock-focus,.hds-tag__link:focus{outline-style:solid;outline-color:transparent;z-index:1}.hds-tag__dismiss.mock-focus:focus:not(:focus-visible),.hds-tag__dismiss:focus:focus:not(:focus-visible),.hds-tag__link.mock-focus:focus:not(:focus-visible),.hds-tag__link:focus:focus:not(:focus-visible){box-shadow:none}.hds-tag__dismiss.mock-focus:focus-visible,.hds-tag__dismiss:focus:focus-visible,.hds-tag__link.mock-focus:focus-visible,.hds-tag__link:focus:focus-visible{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-tag__dismiss.mock-focus.mock-focus.mock-active,.hds-tag__dismiss.mock-focus:focus:active,.hds-tag__dismiss:focus.mock-focus.mock-active,.hds-tag__dismiss:focus:focus:active,.hds-tag__link.mock-focus.mock-focus.mock-active,.hds-tag__link.mock-focus:focus:active,.hds-tag__link:focus.mock-focus.mock-active,.hds-tag__link:focus:focus:active{box-shadow:none}.hds-tag--color-primary .hds-tag__link{color:var(--token-color-foreground-action)}.hds-tag--color-primary .hds-tag__link.mock-hover,.hds-tag--color-primary .hds-tag__link:hover{color:var(--token-color-foreground-action-hover)}.hds-tag--color-primary .hds-tag__link.mock-active,.hds-tag--color-primary .hds-tag__link:active{color:var(--token-color-foreground-action-active)}.hds-tag--color-secondary .hds-tag__link{color:var(--token-color-foreground-strong)}.hds-text--align-left{text-align:left}.hds-text--align-center{text-align:center}.hds-text--align-right{text-align:right}.hds-toast{width:-moz-fit-content;width:fit-content;min-width:min(360px,80vw);max-width:min(500px,80vw);box-shadow:var(--token-elevation-higher-box-shadow)}.hds-tooltip-button{position:relative;isolation:isolate}.hds-tooltip-button::before{position:absolute;top:var(--token-tooltip-focus-offset);right:var(--token-tooltip-focus-offset);bottom:var(--token-tooltip-focus-offset);left:var(--token-tooltip-focus-offset);z-index:-1;border-radius:5px;content:""}.hds-tooltip-button.mock-focus::before,.hds-tooltip-button:focus::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-tooltip-button:focus:not(:focus-visible)::before{box-shadow:none}.hds-tooltip-button:focus-visible::before{box-shadow:var(--token-focus-ring-action-box-shadow)}.hds-tooltip-button.mock-focus.mock-active::before,.hds-tooltip-button:focus:active::before{box-shadow:none}:where(.hds-tooltip-button){margin:0;padding:0;color:inherit;font:inherit;text-align:inherit;background-color:inherit;border:none}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li,.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.menu-panel>ul>[role=treeitem],.menu-panel>ul>li,.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.modal-dialog [role=document] table caption,.modal-dialog [role=document] table thead th,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li,.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],main table caption,main table thead th,table td,table th,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],td,th{text-align:left}:where(.hds-tooltip-button--is-block){display:flex}article,aside,figure,footer,header,hgroup,hr,section{display:block}.tippy-box[data-theme~=hds]{padding:var(--token-tooltip-padding-vertical) var(--token-tooltip-padding-horizontal);color:var(--token-tooltip-color-foreground-primary);font-weight:var(--token-typography-font-weight-regular);font-size:var(--token-typography-body-200-font-size);font-family:var(--token-typography-body-200-font-family);line-height:var(--token-typography-body-200-line-height);overflow-wrap:break-word;background-color:var(--token-tooltip-color-surface-primary);border-radius:var(--token-tooltip-border-radius);box-shadow:var(--token-elevation-higher-box-shadow)}.tippy-box[data-theme~=hds][data-animation=fade][data-state=hidden]{opacity:0}.tippy-box[data-theme~=hds][data-inertia][data-state=visible]{transition-timing-function:var(--token-tooltip-transition-function)}.tippy-box[data-theme~=hds] .tippy-content{position:relative;z-index:1;max-width:var(--token-tooltip-max-width);white-space:normal}.tippy-box[data-theme~=hds] .tippy-svg-arrow{fill:var(--token-tooltip-color-surface-primary)}.sr-only{position:absolute!important;width:1px!important;height:1px!important;margin:-1px!important;padding:0!important;overflow:hidden!important;white-space:nowrap!important;border:0!important;clip:rect(1px,1px,1px,1px)!important;-webkit-clip-path:inset(50%)!important;clip-path:inset(50%)!important}fieldset,hr{border:none}blockquote,body,dd,dl,dt,fieldset,figure,hr,html,iframe,legend,li,ol,p,textarea,ul{margin:0;padding:0}ul{list-style:none}table td,table th{padding:0}audio,embed,img,object,video{height:auto;max-width:100%}.consul-intention-action-warn-modal button.dangerous,.copy-button button,.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.informed-action>ul>li>*,.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-select label>*,.topology-notices button,.type-sort.popover-select label>*,label span,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;user-select:none}a{color:var(--token-color-foreground-action)}span,strong,td,th{color:inherit}body{color:var(--token-color-foreground-strong)}html{background-color:var(--token-color-surface-primary);font-size:16px;text-rendering:optimizeLegibility;-webkit-text-size-adjust:100%;-moz-text-size-adjust:100%;text-size-adjust:100%;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;overflow-x:hidden;overflow-y:scroll;box-sizing:border-box;min-width:300px}hr{background-color:var(--token-color-surface-interactive-active);height:1px;margin:1.5rem 0}body,input,select,textarea{font-family:var(--token-typography-font-stack-text)}strong{font-style:inherit}code,pre{-moz-osx-font-smoothing:auto;-webkit-font-smoothing:auto}pre{-webkit-overflow-scrolling:touch;overflow-x:auto;white-space:pre;word-wrap:normal}*,::after,::before{box-sizing:inherit;animation-play-state:paused;animation-fill-mode:forwards}fieldset{width:100%}a,input[type=checkbox],input[type=radio]{cursor:pointer}td,th{vertical-align:top}button,input,select,textarea{margin:0}iframe{border:0}.consul-bucket-list .service,.consul-bucket-list:not([class]) dt:not([class]),.consul-exposed-path-list>ul>li>.detail dl:not([class]) dt:not([class]),.consul-instance-checks:not([class]) dt:not([class]),.consul-lock-session-list dl:not([class]) dt:not([class]),.consul-server-card dt:not(.name),.consul-upstream-instance-list dl.local-bind-address dt,.consul-upstream-instance-list dl.local-bind-socket-path dt,.consul-upstream-instance-list dl:not([class]) dt:not([class]),.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dt:not([class]),.route-title,.tag-list:not([class]) dt:not([class]),section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dt:not([class]),section[data-route="dc.show.license"] .validity dl .expired+dd,section[data-route="dc.show.license"] .validity dl:not([class]) dt:not([class]),td.tags:not([class]) dt:not([class]){position:absolute;overflow:hidden;clip:rect(0 0 0 0);width:1px;height:1px;margin:-1px;padding:0;border:0}.consul-upstream-instance-list dl.local-bind-socket-mode dt,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dt{position:static!important;clip:unset!important;overflow:visible!important;width:auto!important;height:auto!important;margin:0!important;padding:0!important}.animatable.tab-nav ul::after,.consul-auth-method-type,.consul-external-source,.consul-intention-action-warn-modal button.dangerous,.consul-intention-action-warn-modal button.dangerous:hover:active,.consul-intention-action-warn-modal button.dangerous:hover:not(:disabled):not(:active),.consul-intention-list td.intent- strong,.consul-intention-permission-form button.type-submit,.consul-intention-permission-form button.type-submit:disabled,.consul-intention-permission-form button.type-submit:focus:not(:disabled),.consul-intention-permission-form button.type-submit:hover:not(:disabled),.consul-intention-search-bar .value- span,.consul-kind,.consul-source,.consul-transparent-proxy,.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:first-child,.discovery-chain .route-card>header ul li,.informed-action>ul>.dangerous>*,.informed-action>ul>.dangerous>:focus,.informed-action>ul>.dangerous>:hover,.leader,.menu-panel>ul>li.dangerous>:first-child,.modal-dialog [role=document]>footer,.modal-dialog [role=document]>header,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:first-child,.tab-nav .selected>*,.topology-metrics-source-type,html[data-route^="dc.acls.index"] main td strong,span.policy-node-identity,span.policy-service-identity,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:first-child{border-style:solid}.ember-power-select-trigger,.ember-power-select-trigger--active,.ember-power-select-trigger:focus{border-top:1px solid #aaa;border-bottom:1px solid #aaa;border-right:1px solid #aaa;border-left:1px solid #aaa}.animatable.tab-nav ul::after,.app .notifications .app-notification,.tab-nav li>*{transition-duration:.15s;transition-timing-function:ease-out}html body>.brand-loader{transition-timing-function:cubic-bezier(.1,.1,.25,.9);transition-duration:.1s}html[data-state]:not(.ember-loading) body>.brand-loader{animation-timing-function:cubic-bezier(.1,.1,.25,.9);animation-duration:.1s;animation-name:remove-from-flow;animation-fill-mode:forwards}@keyframes remove-from-flow{100%{visibility:hidden;overflow:hidden;clip:rect(0 0 0 0)}}@keyframes typo-truncate{100%{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}}.CodeMirror-lint-tooltip,dd code,dd pre code{font-family:var(--token-typography-font-stack-code);font-size:var(--token-typography-code-100-font-size);line-height:var(--token-typography-code-100-line-height);font-weight:var(--token-typography-font-weight-regular)}.consul-health-check-list .health-check-output pre code,code,pre,pre code{font-family:var(--token-typography-font-stack-code);font-size:var(--token-typography-code-200-font-size);line-height:var(--token-typography-code-200-line-height);font-weight:var(--token-typography-font-weight-regular)}.informed-action header>*{font-size:inherit;font-weight:inherit;line-height:inherit;font-style:inherit}::after,::before{--tw-content:'';display:inline-block;vertical-align:text-top;background-repeat:no-repeat;background-position:center;mask-repeat:no-repeat;-webkit-mask-repeat:no-repeat;mask-position:center;-webkit-mask-position:center}::before{animation-name:var(--icon-name-start,var(--icon-name)),var(--icon-size-start,var(--icon-size,icon-000));background-color:var(--icon-color-start,var(--icon-color))}::after{animation-name:var(--icon-name-end,var(--icon-name)),var(--icon-size-end,var(--icon-size,icon-000));background-color:var(--icon-color-end,var(--icon-color))}[style*="--icon-color-start"]::before{color:var(--icon-color-start)}[style*="--icon-color-end"]::after{color:var(--icon-color-end)}[style*="--icon-name-start"]::before,[style*="--icon-name-end"]::after{content:""}@keyframes icon-000{100%{width:1.2em;height:1.2em}}@keyframes icon-100{100%{width:.625rem;height:.625rem}}@keyframes icon-200{100%{width:.75rem;height:.75rem}}@keyframes icon-300{100%{width:1rem;height:1rem}}@keyframes icon-400{100%{width:1.125rem;height:1.125rem}}@keyframes icon-500{100%{width:1.25rem;height:1.25rem}}@keyframes icon-600{100%{width:1.375rem;height:1.375rem}}@keyframes icon-700{100%{width:1.5rem;height:1.5rem}}@keyframes icon-800{100%{width:1.625rem;height:1.625rem}}@keyframes icon-900{100%{width:1.75rem;height:1.75rem}}@keyframes icon-999{100%{width:100%;height:100%}}.consul-intention-permission-header-list dt::before,.consul-intention-permission-list dt::before,.discovery-chain .resolver-card dt,.discovery-chain .route-card section header>::before{font-weight:var(--token-typography-font-weight-regular);background-color:var(--token-color-surface-strong);visibility:visible;padding:0 4px}#downstream-container .topology-metrics-card .details .group span::before,#downstream-container .topology-metrics-card div .critical::before,#downstream-container .topology-metrics-card div .empty::before,#downstream-container .topology-metrics-card div .health dt::before,#downstream-container .topology-metrics-card div .nspace dt::before,#downstream-container .topology-metrics-card div .partition dt::before,#downstream-container .topology-metrics-card div .passing::before,#downstream-container .topology-metrics-card div .warning::before,#downstream-container>div:first-child span::before,#login-toggle+div footer button::after,#metrics-container .link .config-link::before,#metrics-container .link .metrics-link::before,#metrics-container:hover .sparkline-key-link::before,#upstream-container .topology-metrics-card .details .group span::before,#upstream-container .topology-metrics-card div .critical::before,#upstream-container .topology-metrics-card div .empty::before,#upstream-container .topology-metrics-card div .health dt::before,#upstream-container .topology-metrics-card div .nspace dt::before,#upstream-container .topology-metrics-card div .partition dt::before,#upstream-container .topology-metrics-card div .passing::before,#upstream-container .topology-metrics-card div .warning::before,.animatable.tab-nav ul::after,.consul-auth-method-binding-list dl dt.type+dd span::before,.consul-auth-method-list ul .locality::before,.consul-auth-method-view dl dt.type+dd span::before,.consul-auth-method-view section dl dt.type+dd span::before,.consul-bucket-list .nspace::before,.consul-bucket-list .partition::before,.consul-bucket-list .peer::before,.consul-exposed-path-list>ul>li>.detail .policy-management::before,.consul-exposed-path-list>ul>li>.detail .policy::before,.consul-exposed-path-list>ul>li>.detail .role::before,.consul-exposed-path-list>ul>li>.detail dl.address dt::before,.consul-exposed-path-list>ul>li>.detail dl.behavior dt::before,.consul-exposed-path-list>ul>li>.detail dl.checks dt::before,.consul-exposed-path-list>ul>li>.detail dl.critical dt::before,.consul-exposed-path-list>ul>li>.detail dl.datacenter dt::before,.consul-exposed-path-list>ul>li>.detail dl.empty dt::before,.consul-exposed-path-list>ul>li>.detail dl.lock-delay dt::before,.consul-exposed-path-list>ul>li>.detail dl.mesh dt::before,.consul-exposed-path-list>ul>li>.detail dl.node dt::before,.consul-exposed-path-list>ul>li>.detail dl.nspace dt::before,.consul-exposed-path-list>ul>li>.detail dl.passing dt::before,.consul-exposed-path-list>ul>li>.detail dl.path dt::before,.consul-exposed-path-list>ul>li>.detail dl.port dt::before,.consul-exposed-path-list>ul>li>.detail dl.protocol dt::before,.consul-exposed-path-list>ul>li>.detail dl.socket dt::before,.consul-exposed-path-list>ul>li>.detail dl.ttl dt::before,.consul-exposed-path-list>ul>li>.detail dl.unknown dt::before,.consul-exposed-path-list>ul>li>.detail dl.warning dt::before,.consul-exposed-path-list>ul>li>.header .critical dd::before,.consul-exposed-path-list>ul>li>.header .empty dd::before,.consul-exposed-path-list>ul>li>.header .passing dd::before,.consul-exposed-path-list>ul>li>.header .policy-management dd::before,.consul-exposed-path-list>ul>li>.header .unknown dd::before,.consul-exposed-path-list>ul>li>.header .warning dd::before,.consul-exposed-path-list>ul>li>.header [rel=me] dd::before,.consul-external-source.jwt::before,.consul-external-source.oidc::before,.consul-health-check-list .health-check-output dd em.jwt::before,.consul-health-check-list .health-check-output dd em.oidc::before,.consul-health-check-list .health-check-output::before,.consul-instance-checks dt::before,.consul-intention-fieldsets .value->:last-child::before,.consul-intention-fieldsets .value-allow>:last-child::before,.consul-intention-fieldsets .value-deny>:last-child::before,.consul-intention-list em span::before,.consul-intention-list td strong.jwt::before,.consul-intention-list td strong.oidc::before,.consul-intention-list td.intent- strong::before,.consul-intention-list td.intent-allow strong::before,.consul-intention-list td.intent-deny strong::before,.consul-intention-permission-list .intent-allow::before,.consul-intention-permission-list .intent-deny::before,.consul-intention-permission-list strong.jwt::before,.consul-intention-permission-list strong.oidc::before,.consul-intention-search-bar .value- span::before,.consul-intention-search-bar .value-allow span::before,.consul-intention-search-bar .value-deny span::before,.consul-intention-search-bar li button span.jwt::before,.consul-intention-search-bar li button span.oidc::before,.consul-kind::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .policy-management::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .policy::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .role::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.address dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.behavior dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.checks dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.critical dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.datacenter dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.empty dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.lock-delay dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.mesh dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.node dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.nspace dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.passing dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.path dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.port dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.protocol dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.socket dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.ttl dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.unknown dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.warning dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .critical dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header .empty dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header .passing dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header .policy-management dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header .unknown dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header .warning dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header [rel=me] dd::before,.consul-peer-search-bar li button span.jwt::before,.consul-peer-search-bar li button span.oidc::before,.consul-server-card .health-status+dd.jwt::before,.consul-server-card .health-status+dd.oidc::before,.consul-upstream-instance-list dl.datacenter dt::before,.consul-upstream-instance-list dl.nspace dt::before,.consul-upstream-instance-list dl.partition dt::before,.consul-upstream-instance-list li>.detail .policy-management::before,.consul-upstream-instance-list li>.detail .policy::before,.consul-upstream-instance-list li>.detail .role::before,.consul-upstream-instance-list li>.detail dl.address dt::before,.consul-upstream-instance-list li>.detail dl.behavior dt::before,.consul-upstream-instance-list li>.detail dl.checks dt::before,.consul-upstream-instance-list li>.detail dl.critical dt::before,.consul-upstream-instance-list li>.detail dl.datacenter dt::before,.consul-upstream-instance-list li>.detail dl.empty dt::before,.consul-upstream-instance-list li>.detail dl.lock-delay dt::before,.consul-upstream-instance-list li>.detail dl.mesh dt::before,.consul-upstream-instance-list li>.detail dl.node dt::before,.consul-upstream-instance-list li>.detail dl.nspace dt::before,.consul-upstream-instance-list li>.detail dl.passing dt::before,.consul-upstream-instance-list li>.detail dl.path dt::before,.consul-upstream-instance-list li>.detail dl.port dt::before,.consul-upstream-instance-list li>.detail dl.protocol dt::before,.consul-upstream-instance-list li>.detail dl.socket dt::before,.consul-upstream-instance-list li>.detail dl.ttl dt::before,.consul-upstream-instance-list li>.detail dl.unknown dt::before,.consul-upstream-instance-list li>.detail dl.warning dt::before,.consul-upstream-instance-list li>.header .critical dd::before,.consul-upstream-instance-list li>.header .empty dd::before,.consul-upstream-instance-list li>.header .passing dd::before,.consul-upstream-instance-list li>.header .policy-management dd::before,.consul-upstream-instance-list li>.header .unknown dd::before,.consul-upstream-instance-list li>.header .warning dd::before,.consul-upstream-instance-list li>.header [rel=me] dd::before,.consul-upstream-list dl.partition dt::before,.copy-button button::before,.dangerous.informed-action header::before,.disclosure-menu [aria-expanded]~*>ul>li.is-active>::after,.disclosure-menu [aria-expanded]~*>ul>li[aria-checked]>::after,.disclosure-menu [aria-expanded]~*>ul>li[aria-current]>::after,.disclosure-menu [aria-expanded]~*>ul>li[aria-selected]>::after,.discovery-chain .resolvers>header span::after,.discovery-chain .route-card::before,.discovery-chain .route-card>header ul li.jwt::before,.discovery-chain .route-card>header ul li.oidc::before,.discovery-chain .routes>header span::after,.discovery-chain .splitter-card::before,.discovery-chain .splitters>header span::after,.empty-state li[class*=-link]>::after,.has-error>strong::before,.info.informed-action header::before,.jwt.consul-auth-method-type::before,.jwt.consul-external-source::before,.jwt.consul-kind::before,.jwt.consul-source::before,.jwt.consul-transparent-proxy::before,.jwt.leader::before,.jwt.topology-metrics-source-type::before,.leader::before,.list-collection>button::after,.list-collection>ul>li:not(:first-child)>.detail .policy-management::before,.list-collection>ul>li:not(:first-child)>.detail .policy::before,.list-collection>ul>li:not(:first-child)>.detail .role::before,.list-collection>ul>li:not(:first-child)>.detail dl.address dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.behavior dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.checks dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.critical dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.datacenter dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.empty dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.lock-delay dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.mesh dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.node dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.nspace dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.passing dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.path dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.port dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.protocol dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.socket dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.ttl dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.unknown dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.warning dt::before,.list-collection>ul>li:not(:first-child)>.header .critical dd::before,.list-collection>ul>li:not(:first-child)>.header .empty dd::before,.list-collection>ul>li:not(:first-child)>.header .passing dd::before,.list-collection>ul>li:not(:first-child)>.header .policy-management dd::before,.list-collection>ul>li:not(:first-child)>.header .unknown dd::before,.list-collection>ul>li:not(:first-child)>.header .warning dd::before,.list-collection>ul>li:not(:first-child)>.header [rel=me] dd::before,.menu-panel>ul>li.is-active>::after,.menu-panel>ul>li[aria-checked]>::after,.menu-panel>ul>li[aria-current]>::after,.menu-panel>ul>li[aria-selected]>::after,.modal-dialog [role=document] a[rel*=help]::after,.modal-dialog [role=document] table td.folder::before,.modal-dialog [role=document] table th span::after,.more-popover-menu>[type=checkbox]+label+div>ul>li.is-active>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li[aria-checked]>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li[aria-current]>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li[aria-selected]>::after,.more-popover-menu>[type=checkbox]+label>::after,.oidc-select .auth0-oidc-provider::before,.oidc-select .google-oidc-provider::before,.oidc-select .microsoft-oidc-provider::before,.oidc-select .okta-oidc-provider::before,.oidc.consul-auth-method-type::before,.oidc.consul-external-source::before,.oidc.consul-kind::before,.oidc.consul-source::before,.oidc.consul-transparent-proxy::before,.oidc.leader::before,.oidc.topology-metrics-source-type::before,.popover-menu>[type=checkbox]+label+div>ul>li.is-active>::after,.popover-menu>[type=checkbox]+label+div>ul>li[aria-checked]>::after,.popover-menu>[type=checkbox]+label+div>ul>li[aria-current]>::after,.popover-menu>[type=checkbox]+label+div>ul>li[aria-selected]>::after,.popover-menu>[type=checkbox]+label>::after,.popover-select .jwt button::before,.popover-select .oidc button::before,.popover-select .value-critical button::before,.popover-select .value-empty button::before,.popover-select .value-passing button::before,.popover-select .value-unknown button::before,.popover-select .value-warning button::before,.search-bar-status li.jwt:not(.remove-all)::before,.search-bar-status li.oidc:not(.remove-all)::before,.search-bar-status li:not(.remove-all) button::before,.sparkline-key h3::before,.tag-list dt::before,.tooltip-panel dd>div::before,.topology-metrics-popover.deny .tippy-arrow::after,.topology-metrics-popover.deny>button::before,.topology-metrics-popover.l7 .tippy-arrow::after,.topology-metrics-popover.l7>button::before,.topology-metrics-popover.not-defined .tippy-arrow::after,.topology-metrics-popover.not-defined>button::before,.topology-metrics-status-error span::before,.topology-metrics-status-loader span::before,.topology-notices button::before,.type-sort.popover-select label>::before,.type-source.popover-select li.partition button::before,.warning.informed-action header::before,.warning.modal-dialog header::before,[class*=status-].empty-state header::before,a[rel*=external]::after,html[data-route^="dc.acls.index"] main td strong.jwt::before,html[data-route^="dc.acls.index"] main td strong.oidc::before,main a[rel*=help]::after,main header nav:first-child ol li:first-child a::before,main table td.folder::before,main table th span::after,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl.jwt::before,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl.oidc::before,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em.jwt::before,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em.oidc::before,span.jwt.policy-node-identity::before,span.jwt.policy-service-identity::before,span.oidc.policy-node-identity::before,span.oidc.policy-service-identity::before,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.is-active>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li[aria-checked]>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li[aria-current]>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li[aria-selected]>::after,table.has-actions tr>.actions>[type=checkbox]+label>::after,table.with-details td:only-child>div>label::before,table.with-details td>label::before,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.is-active>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li[aria-checked]>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li[aria-current]>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li[aria-selected]>::after,table.with-details tr>.actions>[type=checkbox]+label>::after,td.tags dt::before{content:""}@keyframes icon-alert-circle-outline{100%{-webkit-mask-image:var(--icon-alert-circle-16);mask-image:var(--icon-alert-circle-16);background-color:var(--icon-color,var(--color-alert-circle-outline-500,currentColor))}}[class*=status-].empty-state header::before{--icon-name:icon-alert-circle-outline;content:""}@keyframes icon-alert-triangle{100%{-webkit-mask-image:var(--icon-alert-triangle-16);mask-image:var(--icon-alert-triangle-16);background-color:var(--icon-color,var(--color-alert-triangle-500,currentColor))}}#downstream-container .topology-metrics-card div .warning::before,#upstream-container .topology-metrics-card div .warning::before,.consul-exposed-path-list>ul>li>.detail dl.warning dt::before,.consul-exposed-path-list>ul>li>.header .warning dd::before,.consul-health-check-list .warning.health-check-output::before,.consul-instance-checks.warning dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.warning dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .warning dd::before,.consul-upstream-instance-list li>.detail dl.warning dt::before,.consul-upstream-instance-list li>.header .warning dd::before,.dangerous.informed-action header::before,.list-collection>ul>li:not(:first-child)>.detail dl.warning dt::before,.list-collection>ul>li:not(:first-child)>.header .warning dd::before,.popover-select .value-warning button::before,.topology-metrics-popover.not-defined .tippy-arrow::after,.topology-metrics-popover.not-defined>button::before,.warning.informed-action header::before,.warning.modal-dialog header::before{--icon-name:icon-alert-triangle;content:""}@keyframes icon-arrow-left{100%{-webkit-mask-image:var(--icon-arrow-left-16);mask-image:var(--icon-arrow-left-16);background-color:var(--icon-color,var(--color-arrow-left-500,currentColor))}}@keyframes icon-arrow-right{100%{-webkit-mask-image:var(--icon-arrow-right-16);mask-image:var(--icon-arrow-right-16);background-color:var(--icon-color,var(--color-arrow-right-500,currentColor))}}@keyframes icon-cancel-plain{100%{-webkit-mask-image:var(--icon-x-16);mask-image:var(--icon-x-16);background-color:var(--icon-color,var(--color-cancel-plain-500,currentColor))}}.search-bar-status li:not(.remove-all) button::before{--icon-name:icon-cancel-plain;content:""}@keyframes icon-cancel-square-fill{100%{-webkit-mask-image:var(--icon-x-square-fill-16);mask-image:var(--icon-x-square-fill-16);background-color:var(--icon-color,var(--color-cancel-square-fill-500,currentColor))}}#downstream-container .topology-metrics-card div .critical::before,#upstream-container .topology-metrics-card div .critical::before,.consul-exposed-path-list>ul>li>.detail dl.critical dt::before,.consul-exposed-path-list>ul>li>.header .critical dd::before,.consul-health-check-list .critical.health-check-output::before,.consul-instance-checks.critical dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.critical dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .critical dd::before,.consul-upstream-instance-list li>.detail dl.critical dt::before,.consul-upstream-instance-list li>.header .critical dd::before,.has-error>strong::before,.list-collection>ul>li:not(:first-child)>.detail dl.critical dt::before,.list-collection>ul>li:not(:first-child)>.header .critical dd::before,.popover-select .value-critical button::before,.topology-metrics-popover.deny .tippy-arrow::after,.topology-metrics-popover.deny>button::before{--icon-name:icon-cancel-square-fill;content:""}@keyframes icon-check-plain{100%{-webkit-mask-image:var(--icon-check-16);mask-image:var(--icon-check-16);background-color:var(--icon-color,var(--color-check-plain-500,currentColor))}}.disclosure-menu [aria-expanded]~*>ul>li.is-active>::after,.disclosure-menu [aria-expanded]~*>ul>li[aria-checked]>::after,.disclosure-menu [aria-expanded]~*>ul>li[aria-current]>::after,.disclosure-menu [aria-expanded]~*>ul>li[aria-selected]>::after,.menu-panel>ul>li.is-active>::after,.menu-panel>ul>li[aria-checked]>::after,.menu-panel>ul>li[aria-current]>::after,.menu-panel>ul>li[aria-selected]>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li.is-active>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li[aria-checked]>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li[aria-current]>::after,.more-popover-menu>[type=checkbox]+label+div>ul>li[aria-selected]>::after,.popover-menu>[type=checkbox]+label+div>ul>li.is-active>::after,.popover-menu>[type=checkbox]+label+div>ul>li[aria-checked]>::after,.popover-menu>[type=checkbox]+label+div>ul>li[aria-current]>::after,.popover-menu>[type=checkbox]+label+div>ul>li[aria-selected]>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.is-active>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li[aria-checked]>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li[aria-current]>::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li[aria-selected]>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.is-active>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li[aria-checked]>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li[aria-current]>::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li[aria-selected]>::after{--icon-name:icon-check-plain;content:""}@keyframes icon-chevron-down{100%{-webkit-mask-image:var(--icon-chevron-down-16);mask-image:var(--icon-chevron-down-16);background-color:var(--icon-color,var(--color-chevron-down-500,currentColor))}}.list-collection>button.closed::after,.more-popover-menu>[type=checkbox]+label>::after,.popover-menu>[type=checkbox]+label>::after,.topology-notices button::before,table.has-actions tr>.actions>[type=checkbox]+label>::after,table.with-details td:only-child>div>label::before,table.with-details td>label::before,table.with-details tr>.actions>[type=checkbox]+label>::after{--icon-name:icon-chevron-down;content:""}@keyframes icon-copy-action{100%{-webkit-mask-image:var(--icon-clipboard-copy-16);mask-image:var(--icon-clipboard-copy-16);background-color:var(--icon-color,var(--color-copy-action-500,currentColor))}}.copy-button button::before{--icon-name:icon-copy-action;content:"";--icon-color:var(--token-color-foreground-faint)}@keyframes icon-deny-alt{100%{-webkit-mask-image:var(--icon-skip-16);mask-image:var(--icon-skip-16);background-color:var(--icon-color,var(--color-deny-alt-500,currentColor))}}@keyframes icon-deny-default{100%{-webkit-mask-image:var(--icon-skip-16);mask-image:var(--icon-skip-16);background-color:var(--icon-color,var(--color-deny-default-500,currentColor))}}@keyframes icon-disabled{100%{-webkit-mask-image:var(--icon-skip-16);mask-image:var(--icon-skip-16);background-color:var(--icon-color,var(--color-disabled-500,currentColor))}}.status-403.empty-state header::before{--icon-name:icon-disabled;content:""}@keyframes icon-docs{100%{-webkit-mask-image:var(--icon-docs-16);mask-image:var(--icon-docs-16);background-color:var(--icon-color,var(--color-docs-500,currentColor))}}#metrics-container .link .config-link::before,.empty-state .docs-link>::after{--icon-name:icon-docs;content:""}@keyframes icon-exit{100%{-webkit-mask-image:var(--icon-external-link-16);mask-image:var(--icon-external-link-16);background-color:var(--icon-color,var(--color-exit-500,currentColor))}}#metrics-container .link .metrics-link::before,a[rel*=external]::after{--icon-name:icon-exit;content:""}@keyframes icon-file-fill{100%{-webkit-mask-image:var(--icon-file-16);mask-image:var(--icon-file-16);background-color:var(--icon-color,var(--color-file-fill-500,currentColor))}}@keyframes icon-folder-outline{100%{-webkit-mask-image:var(--icon-folder-16);mask-image:var(--icon-folder-16);background-color:var(--icon-color,var(--color-folder-outline-500,currentColor))}}#downstream-container .topology-metrics-card div .nspace dt::before,#upstream-container .topology-metrics-card div .nspace dt::before,.consul-bucket-list .nspace::before,.consul-exposed-path-list>ul>li>.detail dl.nspace dt::before,.consul-intention-list span[class|=nspace]::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.nspace dt::before,.consul-upstream-instance-list dl.nspace dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.nspace dt::before,.modal-dialog [role=document] table td.folder::before,main table td.folder::before{--icon-name:icon-folder-outline;content:""}@keyframes icon-health{100%{-webkit-mask-image:var(--icon-activity-16);mask-image:var(--icon-activity-16);background-color:var(--icon-color,var(--color-health-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.checks dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.checks dt::before,.consul-upstream-instance-list li>.detail dl.checks dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.checks dt::before{--icon-name:icon-health;content:""}@keyframes icon-help-circle-outline{100%{-webkit-mask-image:var(--icon-help-16);mask-image:var(--icon-help-16);background-color:var(--icon-color,var(--color-help-circle-outline-500,currentColor))}}#downstream-container .topology-metrics-card div .health dt::before,#upstream-container .topology-metrics-card div .health dt::before,.consul-exposed-path-list>ul>li>.detail dl.unknown dt::before,.consul-exposed-path-list>ul>li>.header .unknown dd::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.unknown dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .unknown dd::before,.consul-upstream-instance-list li>.detail dl.unknown dt::before,.consul-upstream-instance-list li>.header .unknown dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.unknown dt::before,.list-collection>ul>li:not(:first-child)>.header .unknown dd::before,.popover-select .value-unknown button::before,.status-404.empty-state header::before{--icon-name:icon-help-circle-outline;content:""}@keyframes icon-info-circle-fill{100%{-webkit-mask-image:var(--icon-info-16);mask-image:var(--icon-info-16);background-color:var(--icon-color,var(--color-info-circle-fill-500,currentColor))}}#metrics-container:hover .sparkline-key-link::before,.info.informed-action header::before,.sparkline-key h3::before{--icon-name:icon-info-circle-fill;content:""}@keyframes icon-info-circle-outline{100%{-webkit-mask-image:var(--icon-info-16);mask-image:var(--icon-info-16);background-color:var(--icon-color,var(--color-info-circle-outline-500,currentColor))}}#downstream-container>div:first-child span::before,.consul-auth-method-binding-list dl dt.type+dd span::before,.consul-auth-method-view dl dt.type+dd span::before,.consul-exposed-path-list>ul>li>.detail dl.behavior dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.behavior dt::before,.consul-upstream-instance-list li>.detail dl.behavior dt::before,.discovery-chain .resolvers>header span::after,.discovery-chain .routes>header span::after,.discovery-chain .splitters>header span::after,.list-collection>ul>li:not(:first-child)>.detail dl.behavior dt::before,.modal-dialog [role=document] a[rel*=help]::after,.modal-dialog [role=document] table th span::after,.topology-metrics-status-error span::before,.topology-metrics-status-loader span::before,main a[rel*=help]::after,main table th span::after{--icon-name:icon-info-circle-outline;content:""}@keyframes icon-learn{100%{-webkit-mask-image:var(--icon-learn-16);mask-image:var(--icon-learn-16);background-color:var(--icon-color,var(--color-learn-500,currentColor))}}.empty-state .learn-link>::after{--icon-name:icon-learn;content:""}@keyframes icon-logo-github-monochrome{100%{-webkit-mask-image:var(--icon-github-color-16);mask-image:var(--icon-github-color-16);background-color:var(--icon-color,var(--color-logo-github-monochrome-500,currentColor))}}@keyframes icon-logo-google-color{100%{background-image:var(--icon-google-color-16)}}.oidc-select .google-oidc-provider::before{--icon-name:icon-logo-google-color;content:""}@keyframes icon-logo-kubernetes-color{100%{background-image:var(--icon-kubernetes-color-16)}}@keyframes icon-menu{100%{-webkit-mask-image:var(--icon-menu-16);mask-image:var(--icon-menu-16);background-color:var(--icon-color,var(--color-menu-500,currentColor))}}@keyframes icon-minus-square-fill{100%{-webkit-mask-image:var(--icon-minus-square-16);mask-image:var(--icon-minus-square-16);background-color:var(--icon-color,var(--color-minus-square-fill-500,currentColor))}}#downstream-container .topology-metrics-card div .empty::before,#upstream-container .topology-metrics-card div .empty::before,.consul-exposed-path-list>ul>li>.detail dl.empty dt::before,.consul-exposed-path-list>ul>li>.header .empty dd::before,.consul-instance-checks.empty dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.empty dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .empty dd::before,.consul-upstream-instance-list li>.detail dl.empty dt::before,.consul-upstream-instance-list li>.header .empty dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.empty dt::before,.list-collection>ul>li:not(:first-child)>.header .empty dd::before,.popover-select .value-empty button::before{--icon-name:icon-minus-square-fill;content:""}@keyframes icon-more-horizontal{100%{-webkit-mask-image:var(--icon-more-horizontal-16);mask-image:var(--icon-more-horizontal-16);background-color:var(--icon-color,var(--color-more-horizontal-500,currentColor))}}@keyframes icon-public-default{100%{-webkit-mask-image:var(--icon-globe-16);mask-image:var(--icon-globe-16);background-color:var(--icon-color,var(--color-public-default-500,currentColor))}}.consul-auth-method-list ul .locality::before,.consul-exposed-path-list>ul>li>.detail dl.address dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.address dt::before,.consul-upstream-instance-list li>.detail dl.address dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.address dt::before{--icon-name:icon-public-default;content:""}@keyframes icon-search{100%{-webkit-mask-image:var(--icon-search-16);mask-image:var(--icon-search-16);background-color:var(--icon-color,var(--color-search-500,currentColor))}}@keyframes icon-star-outline{100%{-webkit-mask-image:var(--icon-star-16);mask-image:var(--icon-star-16);background-color:var(--icon-color,var(--color-star-outline-500,currentColor))}}.leader::before{--icon-name:icon-star-outline;content:""}@keyframes icon-user-organization{100%{-webkit-mask-image:var(--icon-org-16);mask-image:var(--icon-org-16);background-color:var(--icon-color,var(--color-user-organization-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.datacenter dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.datacenter dt::before,.consul-upstream-instance-list dl.datacenter dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.datacenter dt::before{--icon-name:icon-user-organization;content:""}@keyframes icon-user-plain{100%{-webkit-mask-image:var(--icon-user-16);mask-image:var(--icon-user-16);background-color:var(--icon-color,var(--color-user-plain-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail .role::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .role::before,.consul-upstream-instance-list li>.detail .role::before,.list-collection>ul>li:not(:first-child)>.detail .role::before{--icon-name:icon-user-plain;content:""}@keyframes icon-user-team{100%{-webkit-mask-image:var(--icon-users-16);mask-image:var(--icon-users-16);background-color:var(--icon-color,var(--color-user-team-500,currentColor))}}#downstream-container .topology-metrics-card div .partition dt::before,#upstream-container .topology-metrics-card div .partition dt::before,.consul-bucket-list .partition::before,.consul-intention-list span[class|=partition]::before,.consul-upstream-instance-list dl.partition dt::before,.consul-upstream-list dl.partition dt::before,.type-source.popover-select li.partition button::before{--icon-name:icon-user-team;content:""}@keyframes icon-alert-circle{100%{-webkit-mask-image:var(--icon-alert-circle-16);mask-image:var(--icon-alert-circle-16);background-color:var(--icon-color,var(--color-alert-circle-500,currentColor))}}@keyframes icon-check{100%{-webkit-mask-image:var(--icon-check-16);mask-image:var(--icon-check-16);background-color:var(--icon-color,var(--color-check-500,currentColor))}}@keyframes icon-check-circle{100%{-webkit-mask-image:var(--icon-check-circle-16);mask-image:var(--icon-check-circle-16);background-color:var(--icon-color,var(--color-check-circle-500,currentColor))}}@keyframes icon-check-circle-fill{100%{-webkit-mask-image:var(--icon-check-circle-fill-16);mask-image:var(--icon-check-circle-fill-16);background-color:var(--icon-color,var(--color-check-circle-fill-500,currentColor))}}#downstream-container .topology-metrics-card div .passing::before,#upstream-container .topology-metrics-card div .passing::before,.consul-exposed-path-list>ul>li>.detail dl.passing dt::before,.consul-exposed-path-list>ul>li>.header .passing dd::before,.consul-exposed-path-list>ul>li>.header [rel=me] dd::before,.consul-health-check-list .passing.health-check-output::before,.consul-instance-checks.passing dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.passing dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .passing dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header [rel=me] dd::before,.consul-upstream-instance-list li>.detail dl.passing dt::before,.consul-upstream-instance-list li>.header .passing dd::before,.consul-upstream-instance-list li>.header [rel=me] dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.passing dt::before,.list-collection>ul>li:not(:first-child)>.header .passing dd::before,.list-collection>ul>li:not(:first-child)>.header [rel=me] dd::before,.popover-select .value-passing button::before{--icon-name:icon-check-circle-fill;content:""}@keyframes icon-chevron-left{100%{-webkit-mask-image:var(--icon-chevron-left-16);mask-image:var(--icon-chevron-left-16);background-color:var(--icon-color,var(--color-chevron-left-500,currentColor))}}.empty-state .back-link>::after,main header nav:first-child ol li:first-child a::before{--icon-name:icon-chevron-left;content:""}@keyframes icon-chevron-right{100%{-webkit-mask-image:var(--icon-chevron-right-16);mask-image:var(--icon-chevron-right-16);background-color:var(--icon-color,var(--color-chevron-right-500,currentColor))}}#login-toggle+div footer button::after{--icon-name:icon-chevron-right;content:""}@keyframes icon-chevron-up{100%{-webkit-mask-image:var(--icon-chevron-up-16);mask-image:var(--icon-chevron-up-16);background-color:var(--icon-color,var(--color-chevron-up-500,currentColor))}}.list-collection>button::after,.more-popover-menu>[type=checkbox]:checked+label>::after,.popover-menu>[type=checkbox]:checked+label>::after,.topology-notices button[aria-expanded=true]::before,table.has-actions tr>.actions>[type=checkbox]:checked+label>::after,table.with-details tr>.actions>[type=checkbox]:checked+label>::after{--icon-name:icon-chevron-up;content:""}@keyframes icon-delay{100%{-webkit-mask-image:var(--icon-delay-16);mask-image:var(--icon-delay-16);background-color:var(--icon-color,var(--color-delay-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.lock-delay dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.lock-delay dt::before,.consul-upstream-instance-list li>.detail dl.lock-delay dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.lock-delay dt::before{--icon-name:icon-delay;content:""}@keyframes icon-docs-link{100%{-webkit-mask-image:var(--icon-docs-link-16);mask-image:var(--icon-docs-link-16);background-color:var(--icon-color,var(--color-docs-link-500,currentColor))}}@keyframes icon-eye{100%{-webkit-mask-image:var(--icon-eye-16);mask-image:var(--icon-eye-16);background-color:var(--icon-color,var(--color-eye-500,currentColor))}}@keyframes icon-eye-off{100%{-webkit-mask-image:var(--icon-eye-off-16);mask-image:var(--icon-eye-off-16);background-color:var(--icon-color,var(--color-eye-off-500,currentColor))}}@keyframes icon-file-text{100%{-webkit-mask-image:var(--icon-file-text-16);mask-image:var(--icon-file-text-16);background-color:var(--icon-color,var(--color-file-text-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail .policy::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .policy::before,.consul-upstream-instance-list li>.detail .policy::before,.list-collection>ul>li:not(:first-child)>.detail .policy::before{--icon-name:icon-file-text;content:""}@keyframes icon-gateway{100%{-webkit-mask-image:var(--icon-gateway-16);mask-image:var(--icon-gateway-16);background-color:var(--icon-color,var(--color-gateway-500,currentColor))}}.consul-kind::before{--icon-name:icon-gateway;content:""}@keyframes icon-git-commit{100%{-webkit-mask-image:var(--icon-git-commit-16);mask-image:var(--icon-git-commit-16);background-color:var(--icon-color,var(--color-git-commit-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.node dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.node dt::before,.consul-upstream-instance-list li>.detail dl.node dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.node dt::before{--icon-name:icon-git-commit;content:""}@keyframes icon-hexagon{100%{-webkit-mask-image:var(--icon-hexagon-16);mask-image:var(--icon-hexagon-16);background-color:var(--icon-color,var(--color-hexagon-500,currentColor))}}@keyframes icon-history{100%{-webkit-mask-image:var(--icon-history-16);mask-image:var(--icon-history-16);background-color:var(--icon-color,var(--color-history-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.ttl dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.ttl dt::before,.consul-upstream-instance-list li>.detail dl.ttl dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.ttl dt::before{--icon-name:icon-history;content:""}@keyframes icon-info{100%{-webkit-mask-image:var(--icon-info-16);mask-image:var(--icon-info-16);background-color:var(--icon-color,var(--color-info-500,currentColor))}}@keyframes icon-layers{100%{-webkit-mask-image:var(--icon-layers-16);mask-image:var(--icon-layers-16);background-color:var(--icon-color,var(--color-layers-500,currentColor))}}.topology-metrics-popover.l7 .tippy-arrow::after,.topology-metrics-popover.l7>button::before{--icon-name:icon-layers;content:"";--icon-color:var(--token-color-palette-neutral-300)}@keyframes icon-loading{100%{-webkit-mask-image:var(--icon-loading-16);mask-image:var(--icon-loading-16);background-color:var(--icon-color,var(--color-loading-500,currentColor))}}@keyframes icon-network-alt{100%{-webkit-mask-image:var(--icon-network-alt-16);mask-image:var(--icon-network-alt-16);background-color:var(--icon-color,var(--color-network-alt-500,currentColor))}}.consul-bucket-list .peer::before{--icon-name:icon-network-alt;content:""}@keyframes icon-path{100%{-webkit-mask-image:var(--icon-path-16);mask-image:var(--icon-path-16);background-color:var(--icon-color,var(--color-path-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.path dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.path dt::before,.consul-upstream-instance-list li>.detail dl.path dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.path dt::before{--icon-name:icon-path;content:""}@keyframes icon-running{100%{-webkit-mask-image:var(--icon-running-16);mask-image:var(--icon-running-16);background-color:var(--icon-color,var(--color-running-500,currentColor))}}@keyframes icon-skip{100%{-webkit-mask-image:var(--icon-skip-16);mask-image:var(--icon-skip-16);background-color:var(--icon-color,var(--color-skip-500,currentColor))}}@keyframes icon-socket{100%{-webkit-mask-image:var(--icon-socket-16);mask-image:var(--icon-socket-16);background-color:var(--icon-color,var(--color-socket-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.socket dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.socket dt::before,.consul-upstream-instance-list li>.detail dl.socket dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.socket dt::before{--icon-name:icon-socket;content:""}@keyframes icon-star-circle{100%{-webkit-mask-image:var(--icon-star-circle-16);mask-image:var(--icon-star-circle-16);background-color:var(--icon-color,var(--color-star-circle-500,currentColor))}}@keyframes icon-star-fill{100%{-webkit-mask-image:var(--icon-star-fill-16);mask-image:var(--icon-star-fill-16);background-color:var(--icon-color,var(--color-star-fill-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail .policy-management::before,.consul-exposed-path-list>ul>li>.header .policy-management dd::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .policy-management::before,.consul-lock-session-list ul>li:not(:first-child)>.header .policy-management dd::before,.consul-upstream-instance-list li>.detail .policy-management::before,.consul-upstream-instance-list li>.header .policy-management dd::before,.list-collection>ul>li:not(:first-child)>.detail .policy-management::before,.list-collection>ul>li:not(:first-child)>.header .policy-management dd::before{--icon-name:icon-star-fill;content:"";--icon-color:var(--token-color-consul-brand)}@keyframes icon-tag{100%{-webkit-mask-image:var(--icon-tag-16);mask-image:var(--icon-tag-16);background-color:var(--icon-color,var(--color-tag-500,currentColor))}}.tag-list dt::before,td.tags dt::before{--icon-name:icon-tag;content:""}@keyframes icon-x{100%{-webkit-mask-image:var(--icon-x-16);mask-image:var(--icon-x-16);background-color:var(--icon-color,var(--color-x-500,currentColor))}}@keyframes icon-x-circle{100%{-webkit-mask-image:var(--icon-x-circle-16);mask-image:var(--icon-x-circle-16);background-color:var(--icon-color,var(--color-x-circle-500,currentColor))}}@keyframes icon-x-square{100%{-webkit-mask-image:var(--icon-x-square-16);mask-image:var(--icon-x-square-16);background-color:var(--icon-color,var(--color-x-square-500,currentColor))}}@keyframes icon-cloud-cross{100%{-webkit-mask-image:var(--icon-cloud-cross-16);mask-image:var(--icon-cloud-cross-16);background-color:var(--icon-color,var(--color-cloud-cross-500,currentColor))}}@keyframes icon-loading-motion{100%{-webkit-mask-image:var(--icon-loading-motion-16);mask-image:var(--icon-loading-motion-16);background-color:var(--icon-color,var(--color-loading-motion-500,currentColor))}}@keyframes icon-logo-auth0-color{100%{background-image:var(--icon-auth0-color-16)}}.oidc-select .auth0-oidc-provider::before{--icon-name:icon-logo-auth0-color;content:""}@keyframes icon-logo-ember-circle-color{100%{background-image:var(--icon-logo-ember-circle-color-16)}}@keyframes icon-logo-glimmer-color{100%{background-image:var(--icon-logo-glimmer-color-16)}}@keyframes icon-logo-jwt-color{100%{background-image:var(--icon-logo-jwt-color-16)}}.consul-external-source.jwt::before,.consul-health-check-list .health-check-output dd em.jwt::before,.consul-intention-list td strong.jwt::before,.consul-intention-permission-list strong.jwt::before,.consul-intention-search-bar li button span.jwt::before,.consul-peer-search-bar li button span.jwt::before,.consul-server-card .health-status+dd.jwt::before,.discovery-chain .route-card>header ul li.jwt::before,.jwt.consul-auth-method-type::before,.jwt.consul-kind::before,.jwt.consul-source::before,.jwt.consul-transparent-proxy::before,.jwt.leader::before,.jwt.topology-metrics-source-type::before,.popover-select .jwt button::before,.search-bar-status li.jwt:not(.remove-all)::before,html[data-route^="dc.acls.index"] main td strong.jwt::before,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl.jwt::before,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em.jwt::before,span.jwt.policy-node-identity::before,span.jwt.policy-service-identity::before{--icon-name:icon-logo-jwt-color;content:""}@keyframes icon-logo-microsoft-color{100%{background-image:var(--icon-microsoft-color-16)}}.oidc-select .microsoft-oidc-provider::before{--icon-name:icon-logo-microsoft-color;content:""}@keyframes icon-logo-oidc-color{100%{background-image:var(--icon-logo-oidc-color-16)}}.consul-external-source.oidc::before,.consul-health-check-list .health-check-output dd em.oidc::before,.consul-intention-list td strong.oidc::before,.consul-intention-permission-list strong.oidc::before,.consul-intention-search-bar li button span.oidc::before,.consul-peer-search-bar li button span.oidc::before,.consul-server-card .health-status+dd.oidc::before,.discovery-chain .route-card>header ul li.oidc::before,.oidc.consul-auth-method-type::before,.oidc.consul-kind::before,.oidc.consul-source::before,.oidc.consul-transparent-proxy::before,.oidc.leader::before,.oidc.topology-metrics-source-type::before,.popover-select .oidc button::before,.search-bar-status li.oidc:not(.remove-all)::before,html[data-route^="dc.acls.index"] main td strong.oidc::before,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl.oidc::before,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em.oidc::before,span.oidc.policy-node-identity::before,span.oidc.policy-service-identity::before{--icon-name:icon-logo-oidc-color;content:""}@keyframes icon-logo-okta-color{100%{background-image:var(--icon-okta-color-16)}}.oidc-select .okta-oidc-provider::before{--icon-name:icon-logo-okta-color;content:""}@keyframes icon-mesh{100%{-webkit-mask-image:var(--icon-mesh-16);mask-image:var(--icon-mesh-16);background-color:var(--icon-color,var(--color-mesh-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.mesh dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.mesh dt::before,.consul-upstream-instance-list li>.detail dl.mesh dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.mesh dt::before{--icon-name:icon-mesh;content:""}@keyframes icon-port{100%{-webkit-mask-image:var(--icon-port-16);mask-image:var(--icon-port-16);background-color:var(--icon-color,var(--color-port-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.port dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.port dt::before,.consul-upstream-instance-list li>.detail dl.port dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.port dt::before{--icon-name:icon-port;content:""}@keyframes icon-protocol{100%{-webkit-mask-image:var(--icon-protocol-16);mask-image:var(--icon-protocol-16);background-color:var(--icon-color,var(--color-protocol-500,currentColor))}}.consul-exposed-path-list>ul>li>.detail dl.protocol dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.protocol dt::before,.consul-upstream-instance-list li>.detail dl.protocol dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.protocol dt::before{--icon-name:icon-protocol;content:""}@keyframes icon-redirect{100%{-webkit-mask-image:var(--icon-redirect-16);mask-image:var(--icon-redirect-16);background-color:var(--icon-color,var(--color-redirect-500,currentColor))}}@keyframes icon-search-color{100%{background-image:var(--icon-search-color-16)}}[for=toolbar-toggle]{--icon-name:icon-search-color;content:""}@keyframes icon-sort{100%{-webkit-mask-image:var(--icon-sort-desc-16);mask-image:var(--icon-sort-desc-16);background-color:var(--icon-color,var(--color-sort-500,currentColor))}}.type-sort.popover-select label>::before{--icon-name:icon-sort;content:""}@keyframes icon-union{100%{-webkit-mask-image:var(--icon-union-16);mask-image:var(--icon-union-16);background-color:var(--icon-color,var(--color-union-500,currentColor))}}#downstream-container .topology-metrics-card .details .group span::before,#upstream-container .topology-metrics-card .details .group span::before{--icon-name:icon-union;content:""}.ember-basic-dropdown{position:relative}.ember-basic-dropdown,.ember-basic-dropdown-content,.ember-basic-dropdown-content *{box-sizing:border-box}.ember-basic-dropdown-content{position:absolute;width:auto;z-index:1000;background-color:#fff}.ember-basic-dropdown-content--left{left:0}.ember-basic-dropdown-content--right{right:0}.ember-basic-dropdown-overlay{position:fixed;background:rgba(0,0,0,.5);width:100%;height:100%;z-index:10;top:0;left:0;pointer-events:none}.ember-basic-dropdown-content-wormhole-origin{display:inline}.ember-power-select-dropdown *{box-sizing:border-box}.ember-power-select-trigger{position:relative;border-radius:4px;background-color:#fff;line-height:1.75;overflow-x:hidden;text-overflow:ellipsis;min-height:1.75em;-moz-user-select:none;user-select:none;-webkit-user-select:none;color:inherit}.ember-power-select-trigger:after{content:"";display:table;clear:both}.ember-power-select-trigger--active,.ember-power-select-trigger:focus{box-shadow:none}.ember-basic-dropdown-trigger--below.ember-power-select-trigger[aria-expanded=true],.ember-basic-dropdown-trigger--in-place.ember-power-select-trigger[aria-expanded=true]{border-bottom-left-radius:0;border-bottom-right-radius:0}.ember-basic-dropdown-trigger--above.ember-power-select-trigger[aria-expanded=true]{border-top-left-radius:0;border-top-right-radius:0}.ember-power-select-placeholder{color:#999;display:block;overflow-x:hidden;white-space:nowrap;text-overflow:ellipsis}.ember-power-select-status-icon{position:absolute;display:inline-block;width:0;height:0;top:0;bottom:0;margin:auto;border-style:solid;border-width:7px 4px 0;border-color:#aaa transparent transparent;right:5px}.ember-basic-dropdown-trigger[aria-expanded=true] .ember-power-select-status-icon{transform:rotate(180deg)}.ember-power-select-clear-btn{position:absolute;cursor:pointer;right:25px}.ember-power-select-trigger-multiple-input{font-family:inherit;font-size:inherit;border:none;display:inline-block;line-height:inherit;-webkit-appearance:none;outline:0;padding:0;float:left;background-color:transparent;text-indent:2px}.ember-power-select-trigger-multiple-input:disabled{background-color:#eee}.ember-power-select-trigger-multiple-input::placeholder{opacity:1;color:#999}.ember-power-select-trigger-multiple-input::-webkit-input-placeholder{opacity:1;color:#999}.ember-power-select-trigger-multiple-input::-moz-placeholder{opacity:1;color:#999}.ember-power-select-trigger-multiple-input::-ms-input-placeholder{opacity:1;color:#999}.active.discovery-chain [id*=":"],.discovery-chain path,.ember-power-select-multiple-remove-btn:not(:hover){opacity:.5}.ember-power-select-multiple-options{padding:0;margin:0}.ember-power-select-multiple-option{border:1px solid gray;border-radius:4px;color:#333;background-color:#e4e4e4;padding:0 4px;display:inline-block;line-height:1.45;float:left;margin:2px 0 2px 3px}.ember-power-select-multiple-remove-btn{cursor:pointer}.ember-power-select-search{padding:4px}.ember-power-select-search-input{border:1px solid #aaa;border-radius:0;width:100%;font-size:inherit;line-height:inherit;padding:0 5px}.ember-power-select-search-input:focus{border:1px solid #aaa;box-shadow:none}.ember-power-select-dropdown{border-left:1px solid #aaa;border-right:1px solid #aaa;line-height:1.75;border-radius:4px;box-shadow:none;overflow:hidden;color:inherit}.ember-power-select-dropdown.ember-basic-dropdown-content--above{border-top:1px solid #aaa;border-bottom:none;border-bottom-left-radius:0;border-bottom-right-radius:0}.ember-power-select-dropdown.ember-basic-dropdown-content--below,.ember-power-select-dropdown.ember-basic-dropdown-content--in-place{border-top:none;border-bottom:1px solid #aaa;border-top-left-radius:0;border-top-right-radius:0}.ember-power-select-dropdown.ember-basic-dropdown-content--in-place{width:100%}.ember-power-select-options{list-style:none;margin:0;padding:0;-moz-user-select:none;user-select:none;-webkit-user-select:none}.ember-power-select-placeholder,.ember-power-select-selected-item,a[rel*=external]::after{margin-left:8px}.ember-power-select-options[role=listbox]{overflow-y:auto;-webkit-overflow-scrolling:touch;max-height:12.25em}.ember-power-select-option{cursor:pointer;padding:0 8px}.ember-power-select-group[aria-disabled=true]{color:#999;cursor:not-allowed}.ember-power-select-group[aria-disabled=true] .ember-power-select-option,.ember-power-select-option[aria-disabled=true]{color:#999;pointer-events:none;cursor:not-allowed}.ember-power-select-option[aria-selected=true]{background-color:#ddd}.ember-power-select-option[aria-current=true]{background-color:#5897fb;color:#fff}.ember-power-select-group-name{cursor:default;font-weight:700}.ember-power-select-trigger[aria-disabled=true]{background-color:#eee}.ember-power-select-trigger{padding:0 16px 0 0}.ember-power-select-group .ember-power-select-group .ember-power-select-group-name{padding-left:24px}.ember-power-select-group .ember-power-select-group .ember-power-select-option{padding-left:40px}.ember-power-select-group .ember-power-select-option{padding-left:24px}.ember-power-select-group .ember-power-select-group-name{padding-left:8px}.ember-power-select-trigger[dir=rtl]{padding:0 0 0 16px}.ember-power-select-trigger[dir=rtl] .ember-power-select-placeholder,.ember-power-select-trigger[dir=rtl] .ember-power-select-selected-item{margin-right:8px}.ember-power-select-trigger[dir=rtl] .ember-power-select-multiple-option,.ember-power-select-trigger[dir=rtl] .ember-power-select-trigger-multiple-input{float:right}.ember-power-select-trigger[dir=rtl] .ember-power-select-status-icon{left:5px;right:initial}.ember-power-select-trigger[dir=rtl] .ember-power-select-clear-btn{left:25px;right:initial}.ember-power-select-dropdown[dir=rtl] .ember-power-select-group .ember-power-select-group .ember-power-select-group-name{padding-right:24px}.ember-power-select-dropdown[dir=rtl] .ember-power-select-group .ember-power-select-group .ember-power-select-option{padding-right:40px}.ember-power-select-dropdown[dir=rtl] .ember-power-select-group .ember-power-select-option{padding-right:24px}.ember-power-select-dropdown[dir=rtl] .ember-power-select-group .ember-power-select-group-name{padding-right:8px}#login-toggle+div footer button:focus,#login-toggle+div footer button:hover,.consul-intention-fieldsets .permissions>button:focus,.consul-intention-fieldsets .permissions>button:hover,.empty-state>ul>li>:focus,.empty-state>ul>li>:hover,.empty-state>ul>li>label>button:focus,.empty-state>ul>li>label>button:hover,.modal-dialog [role=document] dd a:focus,.modal-dialog [role=document] dd a:hover,.modal-dialog [role=document] p a:focus,.modal-dialog [role=document] p a:hover,.oidc-select button.reset:focus,.oidc-select button.reset:hover,.search-bar-status .remove-all button:focus,.search-bar-status .remove-all button:hover,main dd a:focus,main dd a:hover,main p a:focus,main p a:hover{text-decoration:underline}#login-toggle+div footer button,.consul-intention-fieldsets .permissions>button,.empty-state>ul>li>*,.empty-state>ul>li>:active,.empty-state>ul>li>:focus,.empty-state>ul>li>:hover,.empty-state>ul>li>label>button,.empty-state>ul>li>label>button:active,.empty-state>ul>li>label>button:focus,.empty-state>ul>li>label>button:hover,.modal-dialog [role=document] dd a,.modal-dialog [role=document] p a,.oidc-select button.reset,.search-bar-status .remove-all button,main dd a,main dd a:active,main dd a:focus,main dd a:hover,main p a,main p a:active,main p a:focus,main p a:hover{color:var(--token-color-foreground-action)}.modal-dialog [role=document] label a[rel*=help],div.with-confirmation p,main label a[rel*=help]{color:var(--token-color-foreground-disabled)}#login-toggle+div footer button,.consul-intention-fieldsets .permissions>button,.empty-state>ul>li>*,.empty-state>ul>li>label>button,.modal-dialog [role=document] dd a,.modal-dialog [role=document] p a,.oidc-select button.reset,.search-bar-status .remove-all button,main dd a,main p a{cursor:pointer;background-color:transparent}#login-toggle+div footer button:active,.consul-intention-fieldsets .permissions>button:active,.empty-state>ul>li>:active,.empty-state>ul>li>label>button:active,.modal-dialog [role=document] dd a:active,.modal-dialog [role=document] p a:active,.oidc-select button.reset:active,.search-bar-status .remove-all button:active,main dd a:active,main p a:active{outline:0}.modal-dialog [role=document] a[rel*=help]::after,main a[rel*=help]::after{opacity:.4}.modal-dialog [role=document] h2 a,main h2 a{color:var(--token-color-foreground-strong)}.auth-form em,.empty-state,main header nav:first-child ol li a,main header nav:first-child ol li:not(:first-child) a::before{color:var(--token-color-foreground-faint)}.modal-dialog [role=document] h2 a[rel*=help]::after,main h2 a[rel*=help]::after{font-size:.65em;margin-top:.2em;margin-left:.2em}.tab-section>p:only-child [rel*=help]::after{content:none}.auth-form{width:320px;margin:-20px 25px 0}.auth-form em{font-style:normal;display:inline-block;margin-top:1em}.auth-form .oidc-select,.auth-form form{padding-top:1em}.auth-form form{margin-bottom:0!important}.auth-form .ember-basic-dropdown-trigger,.auth-form button:not(.reset){width:100%}.auth-form .progress{margin:0 auto}#login-toggle+div footer button::after{font-size:120%;position:relative;top:-1px;left:-3px}#login-toggle+div footer{border-top:0;background-color:transparent;padding:10px 42px 20px}#login-toggle+div>div>div>div{padding-bottom:0}main header nav:first-child ol li a{text-decoration:none}main header nav:first-child ol li a:hover{color:var(--token-color-foreground-action);text-decoration:underline}main header nav:first-child ol li a::before{text-decoration:none}main header nav:first-child ol{display:grid;grid-auto-flow:column;white-space:nowrap;overflow:hidden}main header nav:first-child ol>li{list-style-type:none;display:inline-flex;overflow:hidden}main header nav:first-child ol li:first-child a::before{background-color:var(--token-color-foreground-faint);margin-right:4px;display:inline-block}main header nav:first-child ol li:not(:first-child) a{margin-left:6px;overflow:hidden;text-overflow:ellipsis}main header nav:first-child ol li:not(:first-child) a::before{content:"/";margin-right:8px;display:inline-block}main header nav:first-child{position:absolute;top:12px}.consul-intention-action-warn-modal button.dangerous,.copy-button button,.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.informed-action>ul>li>*,.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-select label>*,.topology-notices button,.type-sort.popover-select label>*,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{cursor:pointer;white-space:nowrap;text-decoration:none}.consul-intention-action-warn-modal button.dangerous:disabled,.copy-button button:disabled,.disclosure-menu [aria-expanded]~*>ul>[role=treeitem]:disabled,.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem]:disabled,.disclosure-menu [aria-expanded]~*>ul>li>[role=option]:disabled,.informed-action>ul>li>:disabled,.menu-panel>ul>[role=treeitem]:disabled,.menu-panel>ul>li>[role=menuitem]:disabled,.menu-panel>ul>li>[role=option]:disabled,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]:disabled,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]:disabled,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option]:disabled,.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]:disabled,.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]:disabled,.popover-menu>[type=checkbox]+label+div>ul>li>[role=option]:disabled,.popover-select label>:disabled,.topology-notices button:disabled,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]:disabled,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]:disabled,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]:disabled,table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]:disabled,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]:disabled,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]:disabled{cursor:default;box-shadow:none}.checkbox-group label,.more-popover-menu>[type=checkbox]~label,.popover-menu>[type=checkbox]~label,html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label,table.has-actions tr>.actions>[type=checkbox]~label,table.with-details tr>.actions>[type=checkbox]~label{cursor:pointer}.consul-intention-action-warn-modal button.dangerous{border-width:1px;border-radius:var(--decor-radius-100);box-shadow:var(--token-elevation-high-box-shadow)}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.informed-action>ul>li>*,.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{color:var(--token-color-foreground-strong);background-color:var(--token-color-surface-primary)}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem]:focus,.disclosure-menu [aria-expanded]~*>ul>[role=treeitem]:hover,.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem]:focus,.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem]:hover,.disclosure-menu [aria-expanded]~*>ul>li>[role=option]:focus,.disclosure-menu [aria-expanded]~*>ul>li>[role=option]:hover,.informed-action>ul>li>:focus,.informed-action>ul>li>:hover,.menu-panel>ul>[role=treeitem]:focus,.menu-panel>ul>[role=treeitem]:hover,.menu-panel>ul>li>[role=menuitem]:focus,.menu-panel>ul>li>[role=menuitem]:hover,.menu-panel>ul>li>[role=option]:focus,.menu-panel>ul>li>[role=option]:hover,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]:focus,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]:hover,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]:focus,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]:hover,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option]:focus,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option]:hover,.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]:focus,.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]:hover,.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]:focus,.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]:hover,.popover-menu>[type=checkbox]+label+div>ul>li>[role=option]:focus,.popover-menu>[type=checkbox]+label+div>ul>li>[role=option]:hover,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]:focus,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]:hover,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]:focus,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]:hover,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]:focus,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]:hover,table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]:focus,table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]:hover,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]:focus,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]:hover,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]:focus,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]:hover{background-color:var(--token-color-surface-strong)}.type-sort.popover-select label>::before{position:relative;width:16px;height:16px}.type-sort.popover-select label>::after{top:0!important}.consul-intention-action-warn-modal button.dangerous,.copy-button button,.popover-select label>*,.topology-notices button,.type-sort.popover-select label>*{position:relative}.consul-intention-action-warn-modal button.dangerous .progress.indeterminate,.copy-button button .progress.indeterminate,.popover-select label>* .progress.indeterminate,.topology-notices button .progress.indeterminate{position:absolute;top:50%;left:50%;margin-left:-12px;margin-top:-12px}.consul-intention-action-warn-modal button.dangerous:disabled .progress+*,.copy-button button:disabled .progress+*,.popover-select label>:disabled .progress+*,.topology-notices button:disabled .progress+*{visibility:hidden}.consul-intention-action-warn-modal button.dangerous:empty,.copy-button button:empty,.popover-select label>:empty,.topology-notices button:empty{padding-right:0!important;padding-left:18px!important;margin-right:5px}.consul-intention-action-warn-modal button.dangerous:empty::before,.copy-button button:empty::before,.popover-select label>:empty::before,.topology-notices button:empty::before{left:1px}.consul-intention-action-warn-modal button.dangerous:not(:empty),.copy-button button:not(:empty),.popover-select label>:not(:empty),.topology-notices button:not(:empty){display:inline-flex;text-align:center;justify-content:center;align-items:center;padding:calc(.5em - 1px) calc(2.2em - 1px);min-width:100px}.consul-intention-action-warn-modal button.dangerous:not(:last-child),.copy-button button:not(:last-child),.popover-select label>:not(:last-child),.topology-notices button:not(:last-child){margin-right:8px}.app-view>header .actions a{padding-top:calc(.4em - 1px)!important;padding-bottom:calc(.4em - 1px)!important}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.informed-action>ul>li>*,.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{padding:.9em 1em;text-align:center;display:inline-block;box-sizing:border-box}.type-sort.popover-select label>*{height:35px!important}.discovery-chain .resolver-card,.discovery-chain .route-card,.discovery-chain .splitter-card{border:var(--decor-border-100);border-radius:var(--decor-radius-100);background-color:var(--token-color-surface-faint);display:block;position:relative}.discovery-chain .resolver-card>section,.discovery-chain .resolver-card>ul>li,.discovery-chain .route-card>section,.discovery-chain .route-card>ul>li,.discovery-chain .splitter-card>section,.discovery-chain .splitter-card>ul>li{border-top:var(--decor-border-100)}.discovery-chain .resolver-card,.discovery-chain .resolver-card>section,.discovery-chain .resolver-card>ul>li,.discovery-chain .route-card,.discovery-chain .route-card>section,.discovery-chain .route-card>ul>li,.discovery-chain .splitter-card,.discovery-chain .splitter-card>section,.discovery-chain .splitter-card>ul>li{border-color:var(--token-color-surface-interactive-active)}.discovery-chain .resolver-card:focus,.discovery-chain .resolver-card:hover,.discovery-chain .route-card:focus,.discovery-chain .route-card:hover,.discovery-chain .splitter-card:focus,.discovery-chain .splitter-card:hover{box-shadow:var(--token-surface-mid-box-shadow)}.discovery-chain .resolver-card>header,.discovery-chain .route-card>header,.discovery-chain .splitter-card>header{padding:10px}.discovery-chain .resolver-card>section,.discovery-chain .resolver-card>ul>li,.discovery-chain .route-card>section,.discovery-chain .route-card>ul>li,.discovery-chain .splitter-card>section,.discovery-chain .splitter-card>ul>li{padding:5px 10px}.discovery-chain .resolver-card ul,.discovery-chain .route-card ul,.discovery-chain .splitter-card ul{list-style-type:none;margin:0;padding:0}.checkbox-group label{margin-right:10px;white-space:nowrap}.checkbox-group span{display:inline-block;margin-left:10px;min-width:50px}.CodeMirror{max-width:1260px;min-height:300px;height:auto;padding-bottom:20px}.CodeMirror-scroll{overflow-x:hidden!important}.CodeMirror-lint-tooltip{background-color:#f9f9fa;border:1px solid var(--syntax-light-gray);border-radius:0;color:#212121;padding:7px 8px 9px}.cm-s-hashi.CodeMirror{width:100%;background-color:var(--token-color-hashicorp-brand)!important;color:#cfd2d1!important;border:none;font-family:var(--token-typography-font-stack-code);-webkit-font-smoothing:auto;line-height:1.4}.cm-s-hashi .CodeMirror-gutters{color:var(--syntax-dark-grey);background-color:var(--syntax-gutter-grey);border:none}.cm-s-hashi .CodeMirror-cursor{border-left:solid thin #f8f8f0}.cm-s-hashi .CodeMirror-linenumber{color:#6d8a88}.cm-s-hashi.CodeMirror-focused div.CodeMirror-selected{background:#214283}.cm-s-hashi .CodeMirror-line::selection,.cm-s-hashi .CodeMirror-line>span::selection,.cm-s-hashi .CodeMirror-line>span>span::selection{background:#214283}.cm-s-hashi .CodeMirror-line::-moz-selection,.cm-s-hashi .CodeMirror-line>span::-moz-selection,.cm-s-hashi .CodeMirror-line>span>span::-moz-selection{background:var(--token-color-surface-interactive)}.cm-s-hashi span.cm-comment{color:var(--syntax-light-grey)}.cm-s-hashi span.cm-string,.cm-s-hashi span.cm-string-2{color:var(--syntax-packer)}.cm-s-hashi span.cm-number{color:var(--syntax-serf)}.cm-s-hashi span.cm-variable,.cm-s-hashi span.cm-variable-2{color:#9e84c5}.cm-s-hashi span.cm-def{color:var(--syntax-packer)}.cm-s-hashi span.cm-operator{color:var(--syntax-gray)}.cm-s-hashi span.cm-keyword{color:var(--syntax-yellow)}.cm-s-hashi span.cm-atom{color:var(--syntax-serf)}.cm-s-hashi span.cm-meta,.cm-s-hashi span.cm-tag{color:var(--syntax-packer)}.cm-s-hashi span.cm-error{color:var(--syntax-red)}.cm-s-hashi span.cm-attribute,.cm-s-hashi span.cm-qualifier{color:#9fca56}.cm-s-hashi span.cm-property{color:#9e84c5}.cm-s-hashi span.cm-builtin,.cm-s-hashi span.cm-variable-3{color:#9fca56}.cm-s-hashi .CodeMirror-activeline-background{background:#101213}.cm-s-hashi .CodeMirror-matchingbracket{text-decoration:underline;color:var(--token-color-surface-primary)!important}.readonly-codemirror .cm-s-hashi span{color:var(--syntax-light-grey)}.readonly-codemirror .cm-s-hashi span.cm-string,.readonly-codemirror .cm-s-hashi span.cm-string-2{color:var(--syntax-faded-gray)}.readonly-codemirror .cm-s-hashi span.cm-number{color:#a3acbc}.readonly-codemirror .cm-s-hashi span.cm-property,.tippy-box[data-theme~=tooltip]{color:var(--token-color-surface-primary)}.readonly-codemirror .cm-s-hashi span.cm-variable-2{color:var(--syntax-light-grey-blue)}.code-editor .toolbar-container{background:var(--token-color-surface-strong);background:linear-gradient(180deg,var(--token-color-surface-strong) 50%,var(--token-color-surface-interactive-active) 100%);border:1px solid var(--token-color-surface-interactive-active);border-bottom-color:var(--token-color-foreground-faint);border-top-color:var(--token-color-foreground-disabled)}.code-editor .toolbar-container .toolbar .title{color:var(--token-color-foreground-strong);padding:0 8px}.code-editor .toolbar-container .toolbar .toolbar-separator{border-right:1px solid var(--token-color-palette-neutral-300)}.code-editor .toolbar-container .ember-power-select-trigger{background-color:var(--token-color-surface-primary);color:var(--token-color-hashicorp-brand);border-radius:var(--decor-radius-100);border:var(--decor-border-100);border-color:var(--token-color-foreground-faint)}.code-editor{display:block;border:10px;overflow:hidden;position:relative;clear:both}.code-editor::after{position:absolute;bottom:0;width:100%;height:25px;background-color:var(--token-color-hashicorp-brand);content:"";display:block}.code-editor>pre{display:none}.code-editor .toolbar-container,.code-editor .toolbar-container .toolbar{align-items:center;justify-content:space-between;display:flex}.code-editor .toolbar-container{position:relative;margin-top:4px;height:44px}.code-editor .toolbar-container .toolbar{flex:1;white-space:nowrap}.code-editor .toolbar-container .toolbar .toolbar-separator{height:32px;margin:0 4px;width:0}.code-editor .toolbar-container .toolbar .tools{display:flex;flex-direction:row;margin:0 10px;align-items:center}.code-editor .toolbar-container .toolbar .tools .copy-button{margin-left:10px}.code-editor .toolbar-container .ember-basic-dropdown-trigger{margin:0 8px;width:120px;height:32px;display:flex;align-items:center;flex-direction:row}.consul-exposed-path-list>ul>li,.consul-lock-session-list ul>li:not(:first-child),.consul-upstream-instance-list li,.list-collection>ul>li:not(:first-child){display:grid;grid-template-columns:1fr auto;grid-template-rows:50% 50%;grid-template-areas:"header actions" "detail actions"}.consul-exposed-path-list>ul>li>.header,.consul-lock-session-list ul>li:not(:first-child)>.header,.consul-upstream-instance-list li>.header,.list-collection>ul>li:not(:first-child)>.header{grid-area:header;align-self:start}.consul-exposed-path-list>ul>li>.detail,.consul-lock-session-list ul>li:not(:first-child)>.detail,.consul-upstream-instance-list li>.detail,.list-collection>ul>li:not(:first-child)>.detail{grid-area:detail;align-self:end}.consul-exposed-path-list>ul>li>.detail *,.consul-lock-session-list ul>li:not(:first-child)>.detail *,.consul-upstream-instance-list li>.detail *,.list-collection>ul>li:not(:first-child)>.detail *{flex-wrap:nowrap!important}.consul-exposed-path-list>ul>li>.actions,.consul-lock-session-list ul>li:not(:first-child)>.actions,.consul-upstream-instance-list li>.actions,.list-collection>ul>li:not(:first-child)>.actions{grid-area:actions;display:inline-flex}.consul-nspace-list>ul>li:not(:first-child) dt,.consul-policy-list>ul li:not(:first-child) dl:not(.datacenter) dt,.consul-role-list>ul>li:not(:first-child) dt,.consul-service-instance-list .port dt,.consul-service-instance-list .port dt::before,.consul-token-list>ul>li:not(:first-child) dt{display:none}.consul-exposed-path-list>ul>li>.header:nth-last-child(2),.consul-lock-session-list ul>li:not(:first-child)>.header:nth-last-child(2),.consul-upstream-instance-list li>.header:nth-last-child(2),.list-collection>ul>li:not(:first-child)>.header:nth-last-child(2){grid-column-start:header;grid-column-end:actions}.consul-exposed-path-list>ul>li>.detail:last-child,.consul-lock-session-list ul>li:not(:first-child)>.detail:last-child,.consul-upstream-instance-list li>.detail:last-child,.list-collection>ul>li:not(:first-child)>.detail:last-child{grid-column-start:detail;grid-column-end:actions}.consul-nspace-list>ul>li:not(:first-child) dt+dd,.consul-policy-list>ul li:not(:first-child) dl:not(.datacenter) dt+dd,.consul-role-list>ul>li:not(:first-child) dt+dd,.consul-token-list>ul>li:not(:first-child) dt+dd{margin-left:0!important}.consul-policy-list dl.datacenter dt,.consul-service-list li>div:first-child>dl:first-child dd{margin-top:1px}.consul-service-instance-list .detail,.consul-service-list .detail{overflow-x:visible!important}.consul-intention-permission-list>ul{border-top:1px solid var(--token-color-surface-interactive-active)}.consul-service-instance-list .port .copy-button{margin-right:0}.consul-exposed-path-list>ul>li .copy-button,.consul-lock-session-list ul>li:not(:first-child) .copy-button,.consul-upstream-instance-list li .copy-button,.list-collection>ul>li:not(:first-child) .copy-button{display:inline-flex}.consul-exposed-path-list>ul>li>.header .copy-button,.consul-lock-session-list ul>li:not(:first-child)>.header .copy-button,.consul-upstream-instance-list li>.header .copy-button,.list-collection>ul>li:not(:first-child)>.header .copy-button{margin-left:4px}.consul-exposed-path-list>ul>li>.detail .copy-button,.consul-lock-session-list ul>li:not(:first-child)>.detail .copy-button,.consul-upstream-instance-list li>.detail .copy-button,.list-collection>ul>li:not(:first-child)>.detail .copy-button{margin-top:2px}.consul-exposed-path-list>ul>li .copy-button button,.consul-lock-session-list ul>li:not(:first-child) .copy-button button,.consul-upstream-instance-list li .copy-button button,.list-collection>ul>li:not(:first-child) .copy-button button{padding:0!important;margin:0!important}.consul-exposed-path-list>ul>li>.header .copy-button button,.consul-lock-session-list ul>li:not(:first-child)>.header .copy-button button,.consul-upstream-instance-list li>.header .copy-button button,.list-collection>ul>li:not(:first-child)>.header .copy-button button{display:none}.consul-exposed-path-list>ul>li>.header:hover .copy-button button,.consul-lock-session-list ul>li:not(:first-child)>.header:hover .copy-button button,.consul-upstream-instance-list li>.header:hover .copy-button button,.list-collection>ul>li:not(:first-child)>.header:hover .copy-button button{display:block}.consul-exposed-path-list>ul>li .copy-button button:hover,.consul-lock-session-list ul>li:not(:first-child) .copy-button button:hover,.consul-upstream-instance-list li .copy-button button:hover,.list-collection>ul>li:not(:first-child) .copy-button button:hover{background-color:transparent!important}.consul-exposed-path-list>ul>li>.detail>.consul-external-source:first-child,.consul-exposed-path-list>ul>li>.detail>.consul-kind:first-child,.consul-lock-session-list ul>li:not(:first-child)>.detail>.consul-external-source:first-child,.consul-lock-session-list ul>li:not(:first-child)>.detail>.consul-kind:first-child,.consul-upstream-instance-list li>.detail>.consul-external-source:first-child,.consul-upstream-instance-list li>.detail>.consul-kind:first-child,.list-collection>ul>li:not(:first-child)>.detail>.consul-external-source:first-child,.list-collection>ul>li:not(:first-child)>.detail>.consul-kind:first-child{margin-left:-5px}.consul-exposed-path-list>ul>li>.detail .policy-management::before,.consul-exposed-path-list>ul>li>.detail .policy::before,.consul-exposed-path-list>ul>li>.detail .role::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .policy-management::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .policy::before,.consul-lock-session-list ul>li:not(:first-child)>.detail .role::before,.consul-upstream-instance-list li>.detail .policy-management::before,.consul-upstream-instance-list li>.detail .policy::before,.consul-upstream-instance-list li>.detail .role::before,.list-collection>ul>li:not(:first-child)>.detail .policy-management::before,.list-collection>ul>li:not(:first-child)>.detail .policy::before,.list-collection>ul>li:not(:first-child)>.detail .role::before{margin-right:3px}table div.with-confirmation.confirming{background-color:var(--token-color-surface-primary)}div.with-confirmation p{margin-right:12px;padding-left:12px;margin-bottom:0!important}div.with-confirmation{justify-content:end;width:100%;display:flex;align-items:center}table td>div.with-confirmation.confirming{position:absolute;right:0}@media (max-width:420px){div.with-confirmation{float:none;margin-top:1em;display:block}div.with-confirmation p{margin-bottom:1em}}.copy-button button{color:var(--token-color-foreground-action);--icon-color:transparent;min-height:17px}.copy-button button::after{--icon-color:var(--token-color-surface-strong)}.copy-button button:focus,.copy-button button:hover:not(:disabled):not(:active){color:var(--token-color-foreground-action);--icon-color:var(--token-color-surface-strong)}.copy-button button:hover::before{--icon-color:var(--token-color-foreground-action)}.copy-button button:active{--icon-color:var(--token-color-surface-interactive-active)}.copy-button button:empty{padding:0!important;margin-right:0;top:-1px}.copy-button button:empty::after{content:"";display:none;position:absolute;top:-2px;left:-3px;width:20px;height:22px}.copy-button button:empty:hover::after{display:block}.copy-button button:empty::before{position:relative;z-index:1}.copy-button button:not(:empty)::before{margin-right:4px}.consul-bucket-list .copy-button,.consul-exposed-path-list>ul>li>.detail dl .copy-button,.consul-instance-checks .copy-button,.consul-lock-session-list dl .copy-button,.consul-lock-session-list ul>li:not(:first-child)>.detail dl .copy-button,.consul-upstream-instance-list dl .copy-button,.list-collection>ul>li:not(:first-child)>.detail dl .copy-button,.tag-list .copy-button,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .copy-button,section[data-route="dc.show.license"] .validity dl .copy-button,td.tags .copy-button{margin-top:0!important}.consul-bucket-list .copy-btn,.consul-exposed-path-list>ul>li>.detail dl .copy-btn,.consul-instance-checks .copy-btn,.consul-lock-session-list dl .copy-btn,.consul-lock-session-list ul>li:not(:first-child)>.detail dl .copy-btn,.consul-upstream-instance-list dl .copy-btn,.list-collection>ul>li:not(:first-child)>.detail dl .copy-btn,.tag-list .copy-btn,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .copy-btn,section[data-route="dc.show.license"] .validity dl .copy-btn,td.tags .copy-btn{top:0!important}.consul-bucket-list .copy-btn:empty::before,.consul-exposed-path-list>ul>li>.detail dl .copy-btn:empty::before,.consul-instance-checks .copy-btn:empty::before,.consul-lock-session-list dl .copy-btn:empty::before,.consul-upstream-instance-list dl .copy-btn:empty::before,.list-collection>ul>li:not(:first-child)>.detail dl .copy-btn:empty::before,.tag-list .copy-btn:empty::before,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .copy-btn:empty::before,section[data-route="dc.show.license"] .validity dl .copy-btn:empty::before,td.tags .copy-btn:empty::before{left:0!important}.definition-table>dl{display:grid;grid-template-columns:140px auto;grid-gap:.4em 20px;margin-bottom:1.4em}.disclosure-menu{position:relative}.disclosure-menu [aria-expanded]~*{overflow-y:auto!important;will-change:scrollPosition}.more-popover-menu>[type=checkbox],.more-popover-menu>[type=checkbox]~:not(.animating):not(label),.popover-menu>[type=checkbox],.popover-menu>[type=checkbox]~:not(.animating):not(label),table.has-actions tr>.actions>[type=checkbox],table.has-actions tr>.actions>[type=checkbox]~:not(.animating):not(label),table.with-details tr>.actions>[type=checkbox],table.with-details tr>.actions>[type=checkbox]~:not(.animating):not(label){display:none}.more-popover-menu>[type=checkbox]:checked~:not(label),.popover-menu>[type=checkbox]:checked~:not(label),table.has-actions tr>.actions>[type=checkbox]:checked~:not(label),table.with-details tr>.actions>[type=checkbox]:checked~:not(label){display:block}table.dom-recycling{position:relative}table.dom-recycling tr>*{overflow:hidden}.list-collection-scroll-virtual>ul,table.dom-recycling tbody{overflow-x:hidden!important}table.dom-recycling dd{flex-wrap:nowrap}table.dom-recycling dd>*{margin-bottom:0}.empty-state,.empty-state>div{display:flex;flex-direction:column}.empty-state header :first-child{padding:0;margin:0}.empty-state{margin-top:0!important;padding-bottom:2.8em;background-color:var(--token-color-surface-faint)}.empty-state>*{width:370px;margin:0 auto}.empty-state button{margin:0 auto;display:inline}.empty-state header :first-child{margin-bottom:-3px;border-bottom:none}.empty-state header{margin-top:1.8em;margin-bottom:.5em}.empty-state>ul{display:flex;justify-content:space-between;margin-top:1em}.empty-state>ul>li>*,.empty-state>ul>li>label>button{display:inline-flex;align-items:center}.empty-state>div:only-child{padding:50px 0 10px;text-align:center}.empty-state header::before{font-size:2.6em;position:relative;top:-3px;float:left;margin-right:10px}.oidc-select button.reset,.type-dialog{float:right}.empty-state>ul>li>::before,.empty-state>ul>li>label>button::before{margin-top:-1px;margin-right:.5em}.empty-state li[class*=-link]>::after{margin-left:5px}html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup]{border:var(--decor-border-100);border-color:var(--token-color-palette-neutral-300);border-radius:var(--decor-radius-100)}html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] input[type=radio]:checked+*,html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] input[type=radio]:focus+*,html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] input[type=radio]:hover+*{box-shadow:var(--token-elevation-high-box-shadow);background-color:var(--token-color-surface-primary)}@media (min-width:996px){html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup]{display:flex}html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label{flex-grow:1}}html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] input[type=radio]{display:none}.app-view>div form:not(.filter-bar) [role=radiogroup] label,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea,.app-view>div form:not(.filter-bar) [role=radiogroup] label>em,.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.modal-dialog [role=document] .type-password,.modal-dialog [role=document] .type-password textarea,.modal-dialog [role=document] .type-password>em,.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-select,.modal-dialog [role=document] .type-select textarea,.modal-dialog [role=document] .type-select>em,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] .type-text,.modal-dialog [role=document] .type-text textarea,.modal-dialog [role=document] .type-text>em,.modal-dialog [role=document] .type-text>span,.modal-dialog [role=document] [role=radiogroup] label,.modal-dialog [role=document] [role=radiogroup] label textarea,.modal-dialog [role=document] [role=radiogroup] label>em,.modal-dialog [role=document] [role=radiogroup] label>span,.modal-dialog [role=document] form button+em,.oidc-select label,.oidc-select label textarea,.oidc-select label>em,.oidc-select label>span,.type-toggle,.type-toggle textarea,.type-toggle>em,.type-toggle>span,html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label,html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label span,main .type-password,main .type-password textarea,main .type-password>em,main .type-password>span,main .type-select,main .type-select textarea,main .type-select>em,main .type-select>span,main .type-text,main .type-text textarea,main .type-text>em,main .type-text>span,main form button+em,span.label{display:block}html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup],html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label,html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label span{height:100%}html[data-route^="dc.acls.index"] .filter-bar [role=radiogroup] label span{padding:5px 14px}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password],.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text],.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea,.modal-dialog [role=document] .type-password [type=password],.modal-dialog [role=document] .type-password [type=text],.modal-dialog [role=document] .type-password textarea,.modal-dialog [role=document] .type-select [type=password],.modal-dialog [role=document] .type-select [type=text],.modal-dialog [role=document] .type-select textarea,.modal-dialog [role=document] .type-text [type=password],.modal-dialog [role=document] .type-text [type=text],.modal-dialog [role=document] .type-text textarea,.modal-dialog [role=document] [role=radiogroup] label [type=password],.modal-dialog [role=document] [role=radiogroup] label [type=text],.modal-dialog [role=document] [role=radiogroup] label textarea,.oidc-select label [type=password],.oidc-select label [type=text],.oidc-select label textarea,.type-toggle [type=password],.type-toggle [type=text],.type-toggle textarea,main .type-password [type=password],main .type-password [type=text],main .type-password textarea,main .type-select [type=password],main .type-select [type=text],main .type-select textarea,main .type-text [type=password],main .type-text [type=text],main .type-text textarea{-moz-appearance:none;-webkit-appearance:none;box-shadow:var(--token-surface-inset-box-shadow);border-radius:var(--decor-radius-100);border:var(--decor-border-100);outline:0}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]:-moz-read-only,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]:-moz-read-only,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea:-moz-read-only,.modal-dialog [role=document] .type-password [type=password]:-moz-read-only,.modal-dialog [role=document] .type-password [type=text]:-moz-read-only,.modal-dialog [role=document] .type-password textarea:-moz-read-only,.modal-dialog [role=document] .type-select [type=password]:-moz-read-only,.modal-dialog [role=document] .type-select [type=text]:-moz-read-only,.modal-dialog [role=document] .type-select textarea:-moz-read-only,.modal-dialog [role=document] .type-text [type=password]:-moz-read-only,.modal-dialog [role=document] .type-text [type=text]:-moz-read-only,.modal-dialog [role=document] .type-text textarea:-moz-read-only,.modal-dialog [role=document] [role=radiogroup] label [type=password]:-moz-read-only,.modal-dialog [role=document] [role=radiogroup] label [type=text]:-moz-read-only,.modal-dialog [role=document] [role=radiogroup] label textarea:-moz-read-only,.oidc-select label [type=password]:-moz-read-only,.oidc-select label [type=text]:-moz-read-only,.oidc-select label textarea:-moz-read-only,.type-toggle [type=password]:-moz-read-only,.type-toggle [type=text]:-moz-read-only,.type-toggle textarea:-moz-read-only,main .type-password [type=password]:-moz-read-only,main .type-password [type=text]:-moz-read-only,main .type-password textarea:-moz-read-only,main .type-select [type=password]:-moz-read-only,main .type-select [type=text]:-moz-read-only,main .type-select textarea:-moz-read-only,main .type-text [type=password]:-moz-read-only,main .type-text [type=text]:-moz-read-only,main .type-text textarea:-moz-read-only{cursor:not-allowed}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]:disabled,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]:read-only,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]:disabled,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]:read-only,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea:disabled,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea:read-only,.modal-dialog [role=document] .type-password [type=password]:disabled,.modal-dialog [role=document] .type-password [type=password]:read-only,.modal-dialog [role=document] .type-password [type=text]:disabled,.modal-dialog [role=document] .type-password [type=text]:read-only,.modal-dialog [role=document] .type-password textarea:disabled,.modal-dialog [role=document] .type-password textarea:read-only,.modal-dialog [role=document] .type-select [type=password]:disabled,.modal-dialog [role=document] .type-select [type=password]:read-only,.modal-dialog [role=document] .type-select [type=text]:disabled,.modal-dialog [role=document] .type-select [type=text]:read-only,.modal-dialog [role=document] .type-select textarea:disabled,.modal-dialog [role=document] .type-select textarea:read-only,.modal-dialog [role=document] .type-text [type=password]:disabled,.modal-dialog [role=document] .type-text [type=password]:read-only,.modal-dialog [role=document] .type-text [type=text]:disabled,.modal-dialog [role=document] .type-text [type=text]:read-only,.modal-dialog [role=document] .type-text textarea:disabled,.modal-dialog [role=document] .type-text textarea:read-only,.modal-dialog [role=document] [role=radiogroup] label [type=password]:disabled,.modal-dialog [role=document] [role=radiogroup] label [type=password]:read-only,.modal-dialog [role=document] [role=radiogroup] label [type=text]:disabled,.modal-dialog [role=document] [role=radiogroup] label [type=text]:read-only,.modal-dialog [role=document] [role=radiogroup] label textarea:disabled,.modal-dialog [role=document] [role=radiogroup] label textarea:read-only,.oidc-select label [type=password]:disabled,.oidc-select label [type=password]:read-only,.oidc-select label [type=text]:disabled,.oidc-select label [type=text]:read-only,.oidc-select label textarea:disabled,.oidc-select label textarea:read-only,.type-toggle [type=password]:disabled,.type-toggle [type=password]:read-only,.type-toggle [type=text]:disabled,.type-toggle [type=text]:read-only,.type-toggle textarea:disabled,.type-toggle textarea:read-only,main .type-password [type=password]:disabled,main .type-password [type=password]:read-only,main .type-password [type=text]:disabled,main .type-password [type=text]:read-only,main .type-password textarea:disabled,main .type-password textarea:read-only,main .type-select [type=password]:disabled,main .type-select [type=password]:read-only,main .type-select [type=text]:disabled,main .type-select [type=text]:read-only,main .type-select textarea:disabled,main .type-select textarea:read-only,main .type-text [type=password]:disabled,main .type-text [type=password]:read-only,main .type-text [type=text]:disabled,main .type-text [type=text]:read-only,main .type-text textarea:disabled,main .type-text textarea:read-only,textarea:disabled+.CodeMirror{cursor:not-allowed}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]::-moz-placeholder,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]::-moz-placeholder,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea::-moz-placeholder,.modal-dialog [role=document] .type-password [type=password]::-moz-placeholder,.modal-dialog [role=document] .type-password [type=text]::-moz-placeholder,.modal-dialog [role=document] .type-password textarea::-moz-placeholder,.modal-dialog [role=document] .type-select [type=password]::-moz-placeholder,.modal-dialog [role=document] .type-select [type=text]::-moz-placeholder,.modal-dialog [role=document] .type-select textarea::-moz-placeholder,.modal-dialog [role=document] .type-text [type=password]::-moz-placeholder,.modal-dialog [role=document] .type-text [type=text]::-moz-placeholder,.modal-dialog [role=document] .type-text textarea::-moz-placeholder,.modal-dialog [role=document] [role=radiogroup] label [type=password]::-moz-placeholder,.modal-dialog [role=document] [role=radiogroup] label [type=text]::-moz-placeholder,.modal-dialog [role=document] [role=radiogroup] label textarea::-moz-placeholder,.oidc-select label [type=password]::-moz-placeholder,.oidc-select label [type=text]::-moz-placeholder,.oidc-select label textarea::-moz-placeholder,.type-toggle [type=password]::-moz-placeholder,.type-toggle [type=text]::-moz-placeholder,.type-toggle textarea::-moz-placeholder,main .type-password [type=password]::-moz-placeholder,main .type-password [type=text]::-moz-placeholder,main .type-password textarea::-moz-placeholder,main .type-select [type=password]::-moz-placeholder,main .type-select [type=text]::-moz-placeholder,main .type-select textarea::-moz-placeholder,main .type-text [type=password]::-moz-placeholder,main .type-text [type=text]::-moz-placeholder,main .type-text textarea::-moz-placeholder{color:var(--token-color-foreground-disabled)}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]::placeholder,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]::placeholder,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea::placeholder,.app-view>div form:not(.filter-bar) [role=radiogroup] label>em,.modal-dialog [role=document] .type-password [type=password]::placeholder,.modal-dialog [role=document] .type-password [type=text]::placeholder,.modal-dialog [role=document] .type-password textarea::placeholder,.modal-dialog [role=document] .type-password>em,.modal-dialog [role=document] .type-select [type=password]::placeholder,.modal-dialog [role=document] .type-select [type=text]::placeholder,.modal-dialog [role=document] .type-select textarea::placeholder,.modal-dialog [role=document] .type-select>em,.modal-dialog [role=document] .type-text [type=password]::placeholder,.modal-dialog [role=document] .type-text [type=text]::placeholder,.modal-dialog [role=document] .type-text textarea::placeholder,.modal-dialog [role=document] .type-text>em,.modal-dialog [role=document] [role=radiogroup] label [type=password]::placeholder,.modal-dialog [role=document] [role=radiogroup] label [type=text]::placeholder,.modal-dialog [role=document] [role=radiogroup] label textarea::placeholder,.modal-dialog [role=document] [role=radiogroup] label>em,.modal-dialog [role=document] form button+em,.modal-dialog [role=document] form fieldset>p,.oidc-select label [type=password]::placeholder,.oidc-select label [type=text]::placeholder,.oidc-select label textarea::placeholder,.oidc-select label>em,.type-toggle [type=password]::placeholder,.type-toggle [type=text]::placeholder,.type-toggle textarea::placeholder,.type-toggle>em,main .type-password [type=password]::placeholder,main .type-password [type=text]::placeholder,main .type-password textarea::placeholder,main .type-password>em,main .type-select [type=password]::placeholder,main .type-select [type=text]::placeholder,main .type-select textarea::placeholder,main .type-select>em,main .type-text [type=password]::placeholder,main .type-text [type=text]::placeholder,main .type-text textarea::placeholder,main .type-text>em,main form button+em,main form fieldset>p{color:var(--token-color-foreground-disabled)}.has-error>input,.has-error>textarea{border-color:var(--decor-error,var(--token-color-foreground-critical))!important}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password],.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text],.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea,.modal-dialog [role=document] .type-password [type=password],.modal-dialog [role=document] .type-password [type=text],.modal-dialog [role=document] .type-password textarea,.modal-dialog [role=document] .type-select [type=password],.modal-dialog [role=document] .type-select [type=text],.modal-dialog [role=document] .type-select textarea,.modal-dialog [role=document] .type-text [type=password],.modal-dialog [role=document] .type-text [type=text],.modal-dialog [role=document] .type-text textarea,.modal-dialog [role=document] [role=radiogroup] label [type=password],.modal-dialog [role=document] [role=radiogroup] label [type=text],.modal-dialog [role=document] [role=radiogroup] label textarea,.oidc-select label [type=password],.oidc-select label [type=text],.oidc-select label textarea,.type-toggle [type=password],.type-toggle [type=text],.type-toggle textarea,main .type-password [type=password],main .type-password [type=text],main .type-password textarea,main .type-select [type=password],main .type-select [type=text],main .type-select textarea,main .type-text [type=password],main .type-text [type=text],main .type-text textarea{color:var(--token-color-foreground-faint);border-color:var(--token-color-palette-neutral-300)}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]:hover,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]:hover,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea:hover,.modal-dialog [role=document] .type-password [type=password]:hover,.modal-dialog [role=document] .type-password [type=text]:hover,.modal-dialog [role=document] .type-password textarea:hover,.modal-dialog [role=document] .type-select [type=password]:hover,.modal-dialog [role=document] .type-select [type=text]:hover,.modal-dialog [role=document] .type-select textarea:hover,.modal-dialog [role=document] .type-text [type=password]:hover,.modal-dialog [role=document] .type-text [type=text]:hover,.modal-dialog [role=document] .type-text textarea:hover,.modal-dialog [role=document] [role=radiogroup] label [type=password]:hover,.modal-dialog [role=document] [role=radiogroup] label [type=text]:hover,.modal-dialog [role=document] [role=radiogroup] label textarea:hover,.oidc-select label [type=password]:hover,.oidc-select label [type=text]:hover,.oidc-select label textarea:hover,.type-toggle [type=password]:hover,.type-toggle [type=text]:hover,.type-toggle textarea:hover,main .type-password [type=password]:hover,main .type-password [type=text]:hover,main .type-password textarea:hover,main .type-select [type=password]:hover,main .type-select [type=text]:hover,main .type-select textarea:hover,main .type-text [type=password]:hover,main .type-text [type=text]:hover,main .type-text textarea:hover{border-color:var(--token-color-foreground-faint)}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password]:focus,.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text]:focus,.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea:focus,.modal-dialog [role=document] .type-password [type=password]:focus,.modal-dialog [role=document] .type-password [type=text]:focus,.modal-dialog [role=document] .type-password textarea:focus,.modal-dialog [role=document] .type-select [type=password]:focus,.modal-dialog [role=document] .type-select [type=text]:focus,.modal-dialog [role=document] .type-select textarea:focus,.modal-dialog [role=document] .type-text [type=password]:focus,.modal-dialog [role=document] .type-text [type=text]:focus,.modal-dialog [role=document] .type-text textarea:focus,.modal-dialog [role=document] [role=radiogroup] label [type=password]:focus,.modal-dialog [role=document] [role=radiogroup] label [type=text]:focus,.modal-dialog [role=document] [role=radiogroup] label textarea:focus,.oidc-select label [type=password]:focus,.oidc-select label [type=text]:focus,.oidc-select label textarea:focus,.type-toggle [type=password]:focus,.type-toggle [type=text]:focus,.type-toggle textarea:focus,main .type-password [type=password]:focus,main .type-password [type=text]:focus,main .type-password textarea:focus,main .type-select [type=password]:focus,main .type-select [type=text]:focus,main .type-select textarea:focus,main .type-text [type=password]:focus,main .type-text [type=text]:focus,main .type-text textarea:focus{border-color:var(--typo-action,var(--token-color-foreground-action))}.app-view>div form:not(.filter-bar) [role=radiogroup] label a,.modal-dialog [role=document] .type-password a,.modal-dialog [role=document] .type-select a,.modal-dialog [role=document] .type-text a,.modal-dialog [role=document] [role=radiogroup] label a,.oidc-select label a,.type-toggle a,main .type-password a,main .type-select a,main .type-text a{display:inline}.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=password],.app-view>div form:not(.filter-bar) [role=radiogroup] label [type=text],.modal-dialog [role=document] .type-password [type=password],.modal-dialog [role=document] .type-password [type=text],.modal-dialog [role=document] .type-select [type=password],.modal-dialog [role=document] .type-select [type=text],.modal-dialog [role=document] .type-text [type=password],.modal-dialog [role=document] .type-text [type=text],.modal-dialog [role=document] [role=radiogroup] label [type=password],.modal-dialog [role=document] [role=radiogroup] label [type=text],.oidc-select label [type=password],.oidc-select label [type=text],.type-toggle [type=password],.type-toggle [type=text],main .type-password [type=password],main .type-password [type=text],main .type-select [type=password],main .type-select [type=text],main .type-text [type=password],main .type-text [type=text]{display:inline-flex;justify-content:flex-start;max-width:100%;width:100%;height:0;padding:17px 13px}.consul-exposed-path-list>ul>li>.header dt,.consul-lock-session-list ul>li:not(:first-child)>.header dt,.consul-upstream-instance-list li>.header dt,.list-collection>ul>li:not(:first-child)>.header dt,.type-toggle input{display:none}.app-view>div form:not(.filter-bar) [role=radiogroup] label textarea,.modal-dialog [role=document] .type-password textarea,.modal-dialog [role=document] .type-select textarea,.modal-dialog [role=document] .type-text textarea,.modal-dialog [role=document] [role=radiogroup] label textarea,.oidc-select label textarea,.type-toggle textarea,main .type-password textarea,main .type-select textarea,main .type-text textarea{resize:vertical;max-width:100%;min-width:100%;min-height:70px;padding:6px 13px}.app-view>div form:not(.filter-bar) [role=radiogroup],.app-view>div form:not(.filter-bar) [role=radiogroup] label,.checkbox-group,.modal-dialog [role=document] .type-password,.modal-dialog [role=document] .type-select,.modal-dialog [role=document] .type-text,.modal-dialog [role=document] [role=radiogroup],.modal-dialog [role=document] [role=radiogroup] label,.modal-dialog [role=document] form table,.oidc-select label,.type-toggle,main .type-password,main .type-select,main .type-text,main form table{margin-bottom:1.4em}.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] .type-text>span,.modal-dialog [role=document] [role=radiogroup] label>span,.oidc-select label>span,.type-toggle>span,main .type-password>span,main .type-select>span,main .type-text>span,span.label{color:var(--typo-contrast,inherit);margin-bottom:.3em}.app-view>div form:not(.filter-bar) [role=radiogroup] label>em,.modal-dialog [role=document] .type-password>em,.modal-dialog [role=document] .type-select>em,.modal-dialog [role=document] .type-text>em,.modal-dialog [role=document] [role=radiogroup] label>em,.modal-dialog [role=document] form button+em,.oidc-select label>em,.type-toggle>em,main .type-password>em,main .type-select>em,main .type-text>em,main form button+em{margin-top:2px}.app-view>div form:not(.filter-bar) [role=radiogroup] label>span+em,.modal-dialog [role=document] .type-password>span+em,.modal-dialog [role=document] .type-select>span+em,.modal-dialog [role=document] .type-text>span+em,.modal-dialog [role=document] [role=radiogroup] label>span+em,.oidc-select label>span+em,.type-toggle>span+em,main .type-password>span+em,main .type-select>span+em,main .type-text>span+em,span.label+em{margin-top:-.5em;margin-bottom:.5em}.modal-dialog [role=document] .type-password>span,.modal-dialog [role=document] .type-select>span,.modal-dialog [role=document] label.type-text>span,main .type-password>span,main .type-select>span,main label.type-text>span{line-height:2.2em}.type-toggle+.checkbox-group{margin-top:-1em}.consul-exposed-path-list>ul>li,.consul-intention-permission-header-list>ul>li,.consul-intention-permission-list>ul>li,.consul-lock-session-list ul>li:not(:first-child),.consul-upstream-instance-list li,.list-collection>ul>li:not(:first-child){list-style-type:none;border:var(--decor-border-100);border-top-color:transparent;border-bottom-color:var(--token-color-surface-interactive-active);border-right-color:transparent;border-left-color:transparent;--horizontal-padding:12px;--vertical-padding:10px;padding:var(--vertical-padding) 0;padding-left:var(--horizontal-padding)}.consul-auth-method-list>ul>li:active:not(:first-child),.consul-auth-method-list>ul>li:focus:not(:first-child),.consul-auth-method-list>ul>li:hover:not(:first-child),.consul-exposed-path-list>ul>li.linkable:active,.consul-exposed-path-list>ul>li.linkable:focus,.consul-exposed-path-list>ul>li.linkable:hover,.consul-intention-permission-list:not(.readonly)>ul>li:active,.consul-intention-permission-list:not(.readonly)>ul>li:focus,.consul-intention-permission-list:not(.readonly)>ul>li:hover,.consul-lock-session-list ul>li.linkable:active:not(:first-child),.consul-lock-session-list ul>li.linkable:focus:not(:first-child),.consul-lock-session-list ul>li.linkable:hover:not(:first-child),.consul-node-list>ul>li:active:not(:first-child),.consul-node-list>ul>li:focus:not(:first-child),.consul-node-list>ul>li:hover:not(:first-child),.consul-policy-list>ul>li:active:not(:first-child),.consul-policy-list>ul>li:focus:not(:first-child),.consul-policy-list>ul>li:hover:not(:first-child),.consul-role-list>ul>li:active:not(:first-child),.consul-role-list>ul>li:focus:not(:first-child),.consul-role-list>ul>li:hover:not(:first-child),.consul-service-instance-list>ul>li:active:not(:first-child),.consul-service-instance-list>ul>li:focus:not(:first-child),.consul-service-instance-list>ul>li:hover:not(:first-child),.consul-token-list>ul>li:active:not(:first-child),.consul-token-list>ul>li:focus:not(:first-child),.consul-token-list>ul>li:hover:not(:first-child),.consul-upstream-instance-list li.linkable:active,.consul-upstream-instance-list li.linkable:focus,.consul-upstream-instance-list li.linkable:hover,.list-collection>ul>li.linkable:active:not(:first-child),.list-collection>ul>li.linkable:focus:not(:first-child),.list-collection>ul>li.linkable:hover:not(:first-child){border-color:var(--token-color-surface-interactive-active);box-shadow:var(--token-elevation-high-box-shadow);border-top-color:transparent;cursor:pointer}.radio-card,.tippy-box{box-shadow:var(--token-surface-mid-box-shadow)}.consul-exposed-path-list>ul>li>.header,.consul-lock-session-list ul>li:not(:first-child)>.header,.consul-upstream-instance-list li>.header,.list-collection>ul>li:not(:first-child)>.header{color:var(--token-color-hashicorp-brand)}.consul-exposed-path-list>ul>li>.header *,.consul-lock-session-list ul>li:not(:first-child)>.header *,.consul-upstream-instance-list li>.header *,.list-collection>ul>li:not(:first-child)>.header *{color:inherit}.consul-exposed-path-list>ul>li>.detail,.consul-lock-session-list ul>li:not(:first-child)>.detail,.consul-upstream-instance-list li>.detail,.list-collection>ul>li:not(:first-child)>.detail,.radio-card{color:var(--token-color-foreground-faint)}.consul-exposed-path-list>ul>li>.detail a,.consul-lock-session-list ul>li:not(:first-child)>.detail a,.consul-upstream-instance-list li>.detail a,.list-collection>ul>li:not(:first-child)>.detail a{color:inherit}.consul-exposed-path-list>ul>li>.detail a:hover,.consul-lock-session-list ul>li:not(:first-child)>.detail a:hover,.consul-upstream-instance-list li>.detail a:hover,.list-collection>ul>li:not(:first-child)>.detail a:hover{color:var(--token-color-foreground-action);text-decoration:underline}.consul-exposed-path-list>ul>li>.detail,.consul-exposed-path-list>ul>li>.header>dl:first-child,.consul-lock-session-list ul>li:not(:first-child)>.detail,.consul-lock-session-list ul>li:not(:first-child)>.header>dl:first-child,.consul-upstream-instance-list li>.detail,.consul-upstream-instance-list li>.header>dl:first-child,.list-collection>ul>li:not(:first-child)>.detail,.list-collection>ul>li:not(:first-child)>.header>dl:first-child{margin-right:6px}.consul-exposed-path-list>ul>li>.header dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header dd::before,.consul-upstream-instance-list li>.header dd::before,.list-collection>ul>li:not(:first-child)>.header dd::before{font-size:.9em}.consul-exposed-path-list>ul>li>.detail,.consul-exposed-path-list>ul>li>.header,.consul-lock-session-list ul>li:not(:first-child)>.detail,.consul-lock-session-list ul>li:not(:first-child)>.header,.consul-upstream-instance-list li>.detail,.consul-upstream-instance-list li>.header,.list-collection>ul>li:not(:first-child)>.detail,.list-collection>ul>li:not(:first-child)>.header{display:flex;flex-wrap:nowrap;overflow-x:hidden}.consul-exposed-path-list>ul>li>.detail *,.consul-exposed-path-list>ul>li>.header *,.consul-lock-session-list ul>li:not(:first-child)>.detail *,.consul-lock-session-list ul>li:not(:first-child)>.header *,.consul-upstream-instance-list li>.detail *,.consul-upstream-instance-list li>.header *,.list-collection>ul>li:not(:first-child)>.detail *,.list-collection>ul>li:not(:first-child)>.header *{white-space:nowrap;flex-wrap:nowrap}.consul-exposed-path-list>ul>li>.detail>span,.consul-lock-session-list ul>li:not(:first-child)>.detail>span,.consul-upstream-instance-list li>.detail>span,.list-collection>ul>li:not(:first-child)>.detail>span{margin-right:18px}.consul-intention-permission-header-list>ul>li,.consul-intention-permission-list>ul>li{padding-top:0!important;padding-bottom:0!important}.consul-intention-permission-header-list>ul>li .detail,.consul-intention-permission-list>ul>li .detail{grid-row-start:header!important;grid-row-end:detail!important;align-self:center!important;padding:5px 0}.consul-intention-permission-header-list>ul>li .popover-menu>[type=checkbox]+label,.consul-intention-permission-list>ul>li .popover-menu>[type=checkbox]+label{padding:0}.consul-intention-permission-header-list>ul>li .popover-menu>[type=checkbox]+label+div:not(.above),.consul-intention-permission-list>ul>li .popover-menu>[type=checkbox]+label+div:not(.above){top:30px}.has-error>strong{font-style:normal;font-weight:var(--token-typography-font-weight-regular);color:inherit;color:var(--token-color-foreground-critical);position:relative;padding-left:20px}.has-error>strong::before{color:var(--token-color-foreground-critical);position:absolute;top:50%;left:0;margin-top:-8px}.more-popover-menu .popover-menu>[type=checkbox]+label,table.has-actions tr>.actions .popover-menu>[type=checkbox]+label,table.with-details tr>.actions .popover-menu>[type=checkbox]+label{padding:7px}.more-popover-menu .popover-menu>[type=checkbox]+label>*,table.has-actions tr>.actions .popover-menu>[type=checkbox]+label>*,table.with-details tr>.actions .popover-menu>[type=checkbox]+label>*{background-color:transparent;border-radius:var(--decor-radius-100);width:30px;height:30px;font-size:0}.more-popover-menu .popover-menu>[type=checkbox]+label>:active,.more-popover-menu .popover-menu>[type=checkbox]+label>:focus,.more-popover-menu .popover-menu>[type=checkbox]+label>:hover,.radio-card>:first-child,table.has-actions tr>.actions .popover-menu>[type=checkbox]+label>:active,table.has-actions tr>.actions .popover-menu>[type=checkbox]+label>:focus,table.has-actions tr>.actions .popover-menu>[type=checkbox]+label>:hover,table.with-details tr>.actions .popover-menu>[type=checkbox]+label>:active,table.with-details tr>.actions .popover-menu>[type=checkbox]+label>:focus,table.with-details tr>.actions .popover-menu>[type=checkbox]+label>:hover{background-color:var(--token-color-surface-strong)}.more-popover-menu .popover-menu>[type=checkbox]+label>::after,table.has-actions tr>.actions .popover-menu>[type=checkbox]+label>::after,table.with-details tr>.actions .popover-menu>[type=checkbox]+label>::after{--icon-name:icon-more-horizontal;--icon-color:var(--token-color-foreground-strong);--icon-size:icon-300;content:"";position:absolute;top:50%;left:50%;margin-top:-8px;margin-left:-8px}.oidc-select [class$=-oidc-provider]::before{width:22px;height:22px;flex:0 0 auto;margin-right:10px}.oidc-select .ember-power-select-trigger,.oidc-select li{margin-bottom:1em}.informed-action header,.radio-card header{margin-bottom:.5em}.oidc-select .ember-power-select-trigger{width:100%}.radio-card{border:var(--decor-border-100);border-radius:var(--decor-radius-100);border-color:var(--token-color-surface-interactive-active);cursor:pointer;float:none!important;margin-right:0!important;display:flex!important}.checked.radio-card{border-color:var(--token-color-foreground-action)}.checked.radio-card>:first-child{background-color:var(--token-color-surface-action)}.radio-card header{color:var(--token-color-hashicorp-brand)}.consul-intention-fieldsets .radio-card>:last-child{padding-left:47px;position:relative}.consul-intention-fieldsets .radio-card>:last-child::before{position:absolute;left:14px;font-size:1rem}.radio-card>:first-child{padding:10px;display:grid;align-items:center;justify-items:center}.radio-card>:last-child{padding:18px}.consul-server-card,.disclosure-menu [aria-expanded]~*,.menu-panel,.more-popover-menu>[type=checkbox]+label+div,.popover-menu>[type=checkbox]+label+div,section[data-route="dc.show.serverstatus"] .server-failure-tolerance,section[data-route="dc.show.license"] aside,table.has-actions tr>.actions>[type=checkbox]+label+div,table.with-details tr>.actions>[type=checkbox]+label+div{--tone-border:var(--token-color-palette-neutral-300);border:var(--decor-border-100);border-radius:var(--decor-radius-200);box-shadow:var(--token-surface-high-box-shadow);color:var(--token-color-foreground-strong);background-color:var(--token-color-surface-primary);--padding-x:14px;--padding-y:14px;position:relative}.disclosure-menu [aria-expanded]~* [role=separator],.menu-panel [role=separator],.more-popover-menu>[type=checkbox]+label+div [role=separator],.popover-menu>[type=checkbox]+label+div [role=separator],table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator],table.with-details tr>.actions>[type=checkbox]+label+div [role=separator]{border-top:var(--decor-border-100);margin:0}.consul-server-card,.disclosure-menu [aria-expanded]~*,.disclosure-menu [aria-expanded]~* [role=separator],.menu-panel,.menu-panel [role=separator],.more-popover-menu>[type=checkbox]+label+div,.more-popover-menu>[type=checkbox]+label+div [role=separator],.popover-menu>[type=checkbox]+label+div,.popover-menu>[type=checkbox]+label+div [role=separator],section[data-route="dc.show.serverstatus"] .server-failure-tolerance,section[data-route="dc.show.license"] aside,table.has-actions tr>.actions>[type=checkbox]+label+div,table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator],table.with-details tr>.actions>[type=checkbox]+label+div,table.with-details tr>.actions>[type=checkbox]+label+div [role=separator]{border-color:var(--tone-border)}.paged-collection-scroll,[style*="--paged-row-height"]{overflow-y:auto!important;will-change:scrollPosition}[style*="--paged-start"]::before{content:"";display:block;height:var(--paged-start)}.consul-auth-method-type,.consul-external-source,.consul-health-check-list .health-check-output dd em,.consul-intention-list td strong,.consul-intention-permission-list strong,.consul-intention-search-bar li button span,.consul-kind,.consul-peer-search-bar li button span,.consul-server-card .health-status+dd,.consul-source,.consul-transparent-proxy,.discovery-chain .route-card>header ul li,.leader,.search-bar-status li:not(.remove-all),.topology-metrics-source-type,html[data-route^="dc.acls.index"] main td strong,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em,span.policy-node-identity,span.policy-service-identity{border-radius:var(--decor-radius-100);display:inline-flex;position:relative;align-items:center;white-space:nowrap}.consul-auth-method-type::before,.consul-external-source::before,.consul-health-check-list .health-check-output dd em::before,.consul-intention-list td strong::before,.consul-intention-permission-list strong::before,.consul-intention-search-bar li button span::before,.consul-kind::before,.consul-peer-search-bar li button span::before,.consul-server-card .health-status+dd::before,.consul-source::before,.consul-transparent-proxy::before,.discovery-chain .route-card>header ul li::before,.leader::before,.search-bar-status li:not(.remove-all)::before,.topology-metrics-source-type::before,html[data-route^="dc.acls.index"] main td strong::before,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl::before,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em::before,span.policy-node-identity::before,span.policy-service-identity::before{margin-right:4px;--icon-size:icon-300}.consul-auth-method-type,.consul-external-source,.consul-kind,.consul-server-card .health-status+dd,.consul-source,.consul-transparent-proxy,.leader,.search-bar-status li:not(.remove-all),.topology-metrics-source-type,section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em,span.policy-node-identity,span.policy-service-identity{padding:0 8px;--icon-size:icon-200}.consul-intention-permission-list strong,.consul-peer-search-bar li button span,.discovery-chain .route-card>header ul li,html[data-route^="dc.acls.index"] main td strong,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl{padding:1px 5px}.consul-intention-list td strong,.consul-intention-search-bar li button span{padding:4px 8px}span.policy-node-identity::before,span.policy-service-identity::before{vertical-align:unset}span.policy-node-identity::before{content:"Node Identity: "}span.policy-service-identity::before{content:"Service Identity: "}.more-popover-menu>[type=checkbox]+label>*,.popover-menu>[type=checkbox]+label>*,table.has-actions tr>.actions>[type=checkbox]+label>*,table.with-details tr>.actions>[type=checkbox]+label>*{cursor:pointer}.more-popover-menu>[type=checkbox]+label>::after,.popover-menu>[type=checkbox]+label>::after,table.has-actions tr>.actions>[type=checkbox]+label>::after,table.with-details tr>.actions>[type=checkbox]+label>::after{width:16px;height:16px;position:relative}.more-popover-menu,.popover-menu,table.has-actions tr>.actions,table.with-details tr>.actions{position:relative}.more-popover-menu>[type=checkbox]+label,.popover-menu>[type=checkbox]+label,table.has-actions tr>.actions>[type=checkbox]+label,table.with-details tr>.actions>[type=checkbox]+label{display:block}.more-popover-menu>[type=checkbox]+label+div,.popover-menu>[type=checkbox]+label+div,table.has-actions tr>.actions>[type=checkbox]+label+div,table.with-details tr>.actions>[type=checkbox]+label+div{min-width:192px}.more-popover-menu>[type=checkbox]+label+div:not(.above),.popover-menu>[type=checkbox]+label+div:not(.above),table.has-actions tr>.actions>[type=checkbox]+label+div:not(.above),table.with-details tr>.actions>[type=checkbox]+label+div:not(.above){top:38px}.more-popover-menu>[type=checkbox]+label+div:not(.left),.popover-menu>[type=checkbox]+label+div:not(.left),table.has-actions tr>.actions>[type=checkbox]+label+div:not(.left),table.with-details tr>.actions>[type=checkbox]+label+div:not(.left){right:5px}.popover-menu .menu-panel{position:absolute!important}.popover-select label{height:100%}.popover-select label>*{padding:0 8px!important;height:100%!important;justify-content:space-between!important;min-width:auto!important}.popover-select label>::after{margin-left:6px}.popover-select button::before{margin-right:10px}.popover-select .value-passing button::before{color:var(--token-color-foreground-success)}.popover-select .value-warning button::before{color:var(--token-color-foreground-warning)}.popover-select .value-critical button::before{color:var(--token-color-foreground-critical)}.popover-select .value-empty button::before{color:var(--token-color-foreground-disabled)}.popover-select .value-unknown button::before,.type-source.popover-select li.partition button::before{color:var(--token-color-foreground-faint)}.type-source.popover-select li.aws button{text-transform:uppercase}.progress.indeterminate{width:100%;display:flex;align-items:center;justify-content:center;--icon-size:icon-700;--icon-name:var(--icon-loading);--icon-color:var(--token-color-foreground-faint)}.progress.indeterminate::before{content:""}.app-view>div form:not(.filter-bar) [role=radiogroup],.modal-dialog [role=document] [role=radiogroup]{overflow:hidden;padding-left:1px}.app-view>div form:not(.filter-bar) [role=radiogroup] label,.modal-dialog [role=document] [role=radiogroup] label{float:left}.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.modal-dialog [role=document] [role=radiogroup] label>span{float:right;margin-left:1em}.app-view>div form:not(.filter-bar) [role=radiogroup] label:not(:last-child),.modal-dialog [role=document] [role=radiogroup] label:not(:last-child){margin-right:25px}.app-view>div form:not(.filter-bar) [role=radiogroup] label,.app-view>div form:not(.filter-bar) [role=radiogroup] label>span,.modal-dialog [role=document] [role=radiogroup] label,.modal-dialog [role=document] [role=radiogroup] label>span{margin-bottom:0!important}.type-toggle label span{cursor:pointer}.type-toggle label span::after{border-radius:var(--decor-radius-full)}.type-toggle label span::before{border-radius:7px;left:0;width:24px;height:12px;margin-top:-5px}.type-negative.type-toggle{border:0}.app-view>header .title,.modal-dialog [role=document] table td,.modal-dialog [role=document] table th,main table td,main table th{border-bottom:var(--decor-border-100)}.type-toggle label span::after{background-color:var(--token-color-surface-primary);margin-top:-3px;width:8px;height:8px}.type-negative.type-toggle label input+span::before,.type-toggle label input:checked+span::before{background-color:var(--token-color-foreground-action)}.type-negative.type-toggle label input:checked+span::before,.type-toggle label span::before{background-color:var(--token-color-palette-neutral-300)}.type-toggle label{position:relative}.type-toggle label span{color:var(--token-color-foreground-strong);display:inline-block;padding-left:34px}.type-toggle label span::after,.type-toggle label span::before{position:absolute;display:block;content:"";top:50%}.type-negative.type-toggle label input+span::after,.type-toggle label input:checked+span::after{left:14px}.type-negative.type-toggle label input:checked+span::after,.type-toggle label span::after{left:2px}.consul-intention-list td.destination,.consul-intention-list td.source,.modal-dialog [role=document] table th,main table th{border-color:var(--token-color-palette-neutral-300)}.modal-dialog [role=document] table td,main table td{border-color:var(--token-color-palette-neutral-300);color:var(--token-color-foreground-faint);height:50px;vertical-align:middle}.modal-dialog [role=document] table td strong,.modal-dialog [role=document] table th,main table td strong,main table th{color:var(--token-color-foreground-faint)}.modal-dialog [role=document] table a,.tomography-graph .tick text,main table a{color:var(--token-color-foreground-strong)}.modal-dialog [role=document] table,main table{width:100%;border-collapse:collapse}table.dom-recycling tr{display:flex}table.dom-recycling tr>*{flex:1 1 auto;display:inline-flex;align-items:center}.modal-dialog [role=document] table th.actions input,main table th.actions input{display:none}.modal-dialog [role=document] table th.actions,main table th.actions{text-align:right}.modal-dialog [role=document] table td a,main table td a{display:block}.modal-dialog [role=document] table td.no-actions~.actions,main table td.no-actions~.actions{display:none}.modal-dialog [role=document] table td:not(.actions)>:only-child,main table td:not(.actions)>:only-child{overflow:hidden;text-overflow:ellipsis}.modal-dialog [role=document] table td:not(.actions)>*,main table td:not(.actions)>*{white-space:nowrap}.modal-dialog [role=document] table caption,main table caption{margin-bottom:.8em}.modal-dialog [role=document] table th,main table th{padding:.6em 0}.modal-dialog [role=document] table td a,.modal-dialog [role=document] table td:not(.actions),.modal-dialog [role=document] table th:not(.actions),main table td a,main table td:not(.actions),main table th:not(.actions){padding-right:.9em}.modal-dialog [role=document] table tbody td em,main table tbody td em{display:block;font-style:normal;font-weight:var(--token-typography-font-weight-regular);color:var(--token-color-foreground-faint)}table.has-actions tr>.actions,table.with-details tr>.actions{width:60px!important;overflow:visible}table.has-actions tr>.actions>[type=checkbox]+label,table.with-details tr>.actions>[type=checkbox]+label{position:absolute;right:5px}table.consul-metadata-list tbody tr{cursor:default}table.consul-metadata-list tbody tr:hover{box-shadow:none}.modal-dialog [role=document] table th span::after,main table th span::after{color:var(--token-color-foreground-faint);margin-left:4px}.modal-dialog [role=document] table tbody tr,main table tbody tr{cursor:pointer}.modal-dialog [role=document] table td:first-child,main table td:first-child{padding:0}.modal-dialog [role=document] table tbody tr:hover,main table tbody tr:hover{box-shadow:var(--token-elevation-high-box-shadow)}.modal-dialog [role=document] table td.folder::before,main table td.folder::before{background-color:var(--token-color-palette-neutral-300);margin-top:1px;margin-right:5px}@media (max-width:420px){.consul-intention-list tr>:nth-last-child(2),.modal-dialog [role=document] table tr>.actions,main table tr>.actions{display:none}}.voting-status-leader.consul-server-card .name{width:var(--tile-size,3rem);height:var(--tile-size,3rem)}.voting-status-leader.consul-server-card .name::before{display:block;content:"";width:100%;height:100%;border-radius:var(--decor-radius-250);border:var(--decor-border-100);background-image:linear-gradient(135deg,var(--token-color-consul-surface) 0,var(--token-color-consul-border) 100%);border-color:var(--token-color-border-faint)}.voting-status-leader.consul-server-card .name::after{content:"";position:absolute;top:calc(var(--tile-size,3rem)/ 4);left:calc(var(--tile-size,3rem)/ 4);--icon-name:icon-star-fill;--icon-size:icon-700;color:var(--token-color-consul-brand)}table.with-details td:only-child>div>label,table.with-details td>label{border-radius:var(--decor-radius-100);cursor:pointer;min-width:30px;min-height:30px;display:inline-flex;align-items:center;justify-content:center}table.with-details td:only-child>div>label:active,table.with-details td:only-child>div>label:focus,table.with-details td:only-child>div>label:hover,table.with-details td>label:active,table.with-details td>label:focus,table.with-details td>label:hover{background-color:var(--token-color-surface-strong)}table.dom-recycling tbody{top:33px!important;width:100%}table.dom-recycling caption~tbody{top:57px!important}table tr>:nth-last-child(2):first-child,table tr>:nth-last-child(2):first-child~*{width:50%}table tr>:nth-last-child(3):first-child,table tr>:nth-last-child(3):first-child~*{width:33.3333333333%}table tr>:nth-last-child(4):first-child,table tr>:nth-last-child(4):first-child~*{width:25%}table tr>:nth-last-child(5):first-child,table tr>:nth-last-child(5):first-child~*{width:20%}table.has-actions tr>:nth-last-child(2):first-child,table.has-actions tr>:nth-last-child(2):first-child~*{width:calc(100% - 60px)}table.has-actions tr>:nth-last-child(3):first-child,table.has-actions tr>:nth-last-child(3):first-child~*{width:calc(50% - 30px)}table.has-actions tr>:nth-last-child(4):first-child,table.has-actions tr>:nth-last-child(4):first-child~*{width:calc(33% - 20px)}table.has-actions tr>:nth-last-child(5):first-child,table.has-actions tr>:nth-last-child(5):first-child~*{width:calc(25% - 15px)}html[data-route^="dc.acls.policies"] [role=dialog] table tr>:not(last-child),html[data-route^="dc.acls.policies"] table tr>:not(last-child),html[data-route^="dc.acls.roles"] [role=dialog] table tr>:not(last-child),html[data-route^="dc.acls.roles"] main table.token-list tr>:not(last-child){width:120px}html[data-route^="dc.acls.policies"] table tr>:last-child,html[data-route^="dc.acls.roles"] [role=dialog] table tr>:last-child,html[data-route^="dc.acls.roles"] main table.token-list tr>:last-child{width:calc(100% - 240px)!important}table.with-details td:only-child{cursor:default;border:0}table.with-details td:only-child>div::before,table.with-details td:only-child>div>div,table.with-details td:only-child>div>label{background-color:var(--token-color-surface-primary)}table.with-details td:only-child>div>label::before{transform:rotate(180deg)}table.with-details td:only-child>div::before{background:var(--token-color-surface-interactive-active);content:"";display:block;height:1px;position:absolute;bottom:-20px;left:10px;width:calc(100% - 20px)}table.with-details tr>.actions{position:relative}table.with-details td:only-child>div>label,table.with-details td>label{pointer-events:auto;position:absolute;top:8px}table.with-details td:only-child>div>label span,table.with-details td>label span{display:none}table.with-details td>label{right:2px}table.with-details tr:nth-child(even) td{height:auto;position:relative;display:table-cell}table.with-details tr:nth-child(even) td>*{display:none}table.with-details td:only-child>div>label{right:11px}table.with-details tr:nth-child(even) td>input:checked+*{display:block}table.with-details td:only-child{overflow:visible;width:100%}table.with-details td:only-child>div{border:1px solid var(--token-color-palette-neutral-300);border-radius:var(--decor-radius-100);box-shadow:var(--token-surface-high-box-shadow);margin-bottom:20px;position:relative;left:-10px;right:-10px;width:calc(100% + 20px);margin-top:-51px;pointer-events:none;padding:10px}table.with-details td:only-child>div::after{content:"";display:block;clear:both}table.with-details td:only-child>div>div{pointer-events:auto;margin-top:36px}.consul-auth-method-binding-list dl,.consul-auth-method-view dl,.consul-auth-method-view section dl{display:flex;flex-wrap:wrap}.consul-auth-method-binding-list dl dd,.consul-auth-method-binding-list dl dt,.consul-auth-method-view dl dd,.consul-auth-method-view dl dt{padding:12px 0;margin:0;border-top:1px solid!important}.consul-auth-method-binding-list dl dt,.consul-auth-method-view dl dt{width:20%;font-weight:var(--token-typography-font-weight-bold)}.consul-auth-method-binding-list dl dd,.consul-auth-method-view dl dd{margin-left:auto;width:80%;display:flex}.consul-auth-method-binding-list dl dd>ul li,.consul-auth-method-view dl dd>ul li{display:flex}.consul-auth-method-binding-list dl dd>ul li:not(:last-of-type),.consul-auth-method-view dl dd>ul li:not(:last-of-type){padding-bottom:12px}.consul-auth-method-binding-list dl dt.check+dd,.consul-auth-method-view dl dt.check+dd{padding-top:16px}.consul-auth-method-binding-list dl>dd:last-of-type,.consul-auth-method-binding-list dl>dt:last-of-type,.consul-auth-method-view dl>dd:last-of-type,.consul-auth-method-view dl>dt:last-of-type{border-bottom:1px solid!important;border-color:var(--token-color-palette-neutral-300)!important}.consul-auth-method-binding-list dl dd,.consul-auth-method-binding-list dl dt,.consul-auth-method-view dl dd,.consul-auth-method-view dl dt{border-color:var(--token-color-palette-neutral-300)!important;color:var(--token-color-hashicorp-brand)!important}.consul-auth-method-binding-list dl dd .copy-button button::before,.consul-auth-method-view dl dd .copy-button button::before{background-color:var(--token-color-hashicorp-brand)}.consul-auth-method-binding-list dl dt.type+dd span::before,.consul-auth-method-view dl dt.type+dd span::before{margin-left:4px;background-color:var(--token-color-foreground-faint)}.tooltip-panel dt{cursor:pointer}.tooltip-panel dd>div::before{width:12px;height:12px;background-color:var(--token-color-surface-primary);border-top:1px solid var(--token-color-palette-neutral-300);border-right:1px solid var(--token-color-palette-neutral-300);transform:rotate(-45deg);position:absolute;left:16px;top:-7px}.tooltip-panel,.tooltip-panel dt{display:flex;flex-direction:column}.tooltip-panel dd>div.menu-panel{top:auto;overflow:visible}.tooltip-panel dd{display:none;position:relative;z-index:1;padding-top:10px;margin-bottom:-10px}.tooltip-panel:hover dd{display:block}.tooltip-panel dd>div{width:250px}.app-view>header .title{display:grid;grid-template-columns:1fr auto;grid-template-areas:"title actions";position:relative;z-index:5;padding-bottom:1.4em}.app-view>div form:not(.filter-bar) fieldset{border-bottom:var(--decor-border-200)}.app-view>header h1>em{color:var(--token-color-foreground-faint)}.app-view>header dd>a{color:var(--token-color-hashicorp-brand)}.app-view>div div>dl>dd{color:var(--token-color-foreground-disabled)}.app-view>div form:not(.filter-bar) fieldset,.app-view>header .title{border-color:var(--token-color-surface-interactive-active)}.app-view>header .title .title-left-container{grid-area:title;display:flex;flex-wrap:wrap;align-items:center;white-space:normal}.app-view>header .title .title-left-container>:first-child{flex-basis:100%}.app-view>header .title .title-left-container>:not(:first-child){margin-right:8px}.app-view>header .actions{grid-area:actions;align-self:end;display:flex;align-items:flex-start;margin-left:auto;margin-top:9px}.app-view>div form:not(.filter-bar) fieldset{padding-bottom:.3em;margin-bottom:2em}[for=toolbar-toggle]{background-position:0 4px;display:inline-block;width:26px;height:26px;cursor:pointer;color:var(--token-color-foreground-action)}#toolbar-toggle{display:none}@media (max-width:849px){.app-view>header .actions{margin-top:9px}}@media (min-width:996px){[for=toolbar-toggle]{display:none}}@media (max-width:995px){.app-view>header h1{display:inline-block}html[data-route$="dc.services.instance.show"] h1{display:block}#toolbar-toggle+*{display:none}#toolbar-toggle:checked+*{display:flex}}.brand-loader{position:absolute;top:50%;margin-top:-26px;left:50%}.app .notifications{position:fixed;z-index:100;bottom:2rem;left:1.5rem;pointer-events:none}.app .notifications .app-notification>*{min-width:400px}.app .notifications .app-notification{transition-property:opacity;width:-moz-fit-content;width:fit-content;max-width:80%;pointer-events:auto}.hashicorp-consul .consul-side-nav li.consul-disabled-nav{width:100%;min-height:var(--token-side-nav-body-list-item-height);padding:var(--token-side-nav-body-list-item-padding-vertical) var(--token-side-nav-body-list-item-padding-horizontal);color:var(--token-color-foreground-disabled)}.hashicorp-consul .consul-side-nav li.consul-side-nav__selector .consul-side-nav__selector-toggle{min-width:15.5rem}.hashicorp-consul .consul-side-nav li.consul-side-nav__selector .consul-side-nav__selector-toggle:disabled{color:var(--token-color-foreground-disabled);border-color:var(--token-color-border-primary)}.hashicorp-consul .consul-side-nav li.consul-side-nav__selector .consul-side-nav__selector-toggle:disabled:hover{background-color:transparent}.hashicorp-consul .consul-side-nav li.consul-side-nav__selector .hds-dropdown__content{min-width:15.5rem;max-height:500px}.hashicorp-consul .consul-side-nav .hds-side-nav__wrapper-body{overflow-y:unset;overflow-x:unset}.hashicorp-consul .consul-side-nav li.consul-side-nav__datacenter{display:flex;gap:.5rem;align-items:center;padding-left:.5rem}.hashicorp-consul .consul-side-nav .consul-side-nav__selector-group{margin-bottom:1.5rem}.hashicorp-consul .consul-side-nav .consul-datacenter-selector__dc-name{display:flex;align-items:center;gap:.5rem}.hashicorp-consul .consul-side-nav .consul-datacenter-selector__dc-name .consul-datacenter-selector__badges{display:flex;gap:.25rem}.hashicorp-consul .consul-side-nav .consul-side-nav__selector-title{margin-top:.5rem}.hashicorp-consul .consul-side-nav .consul-side-nav__selector-description{padding-top:.5rem}.disclosure-menu [aria-expanded]~*>div+ul,.menu-panel>div+ul,.more-popover-menu>[type=checkbox]+label+div>div+ul,.popover-menu>[type=checkbox]+label+div>div+ul,table.has-actions tr>.actions>[type=checkbox]+label+div>div+ul,table.with-details tr>.actions>[type=checkbox]+label+div>div+ul{border-top:var(--decor-border-100);border-color:var(--token-form--base-border-color-default)}.disclosure-menu [aria-expanded]~* [role=separator]:first-child:not(:empty),.menu-panel [role=separator]:first-child:not(:empty),.more-popover-menu>[type=checkbox]+label+div [role=separator]:first-child:not(:empty),.popover-menu>[type=checkbox]+label+div [role=separator]:first-child:not(:empty),table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator]:first-child:not(:empty),table.with-details tr>.actions>[type=checkbox]+label+div [role=separator]:first-child:not(:empty){border:none}.disclosure-menu [aria-expanded]~*>ul>li,.menu-panel>ul>li,.more-popover-menu>[type=checkbox]+label+div>ul>li,.popover-menu>[type=checkbox]+label+div>ul>li,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li{list-style-type:none}.disclosure-menu [aria-expanded]~*>ul .informed-action,.menu-panel>ul .informed-action,.more-popover-menu>[type=checkbox]+label+div>ul .informed-action,.popover-menu>[type=checkbox]+label+div>ul .informed-action,table.has-actions tr>.actions>[type=checkbox]+label+div>ul .informed-action,table.with-details tr>.actions>[type=checkbox]+label+div>ul .informed-action{border:0!important}.disclosure-menu [aria-expanded]~*>div,.menu-panel>div,.more-popover-menu>[type=checkbox]+label+div>div,.popover-menu>[type=checkbox]+label+div>div,table.has-actions tr>.actions>[type=checkbox]+label+div>div,table.with-details tr>.actions>[type=checkbox]+label+div>div{padding:.625rem var(--padding-x);white-space:normal;max-width:-moz-fit-content;max-width:fit-content}@supports not ((max-width:-moz-fit-content) or (max-width:fit-content)){.disclosure-menu [aria-expanded]~*>div,.menu-panel>div,.more-popover-menu>[type=checkbox]+label+div>div,.popover-menu>[type=checkbox]+label+div>div,table.has-actions tr>.actions>[type=checkbox]+label+div>div,table.with-details tr>.actions>[type=checkbox]+label+div>div{max-width:200px}}.disclosure-menu [aria-expanded]~*>div::before,.menu-panel>div::before,.more-popover-menu>[type=checkbox]+label+div>div::before,.popover-menu>[type=checkbox]+label+div>div::before,table.has-actions tr>.actions>[type=checkbox]+label+div>div::before,table.with-details tr>.actions>[type=checkbox]+label+div>div::before{position:absolute;left:15px;top:calc(10px + .1em)}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem]+*,.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem]+*,.disclosure-menu [aria-expanded]~*>ul>li>[role=option]+*,.menu-panel-deprecated>ul>li>div[role=menu],.menu-panel>ul>[role=treeitem]+*,.menu-panel>ul>li>[role=menuitem]+*,.menu-panel>ul>li>[role=option]+*,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]+*,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]+*,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option]+*,.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]+*,.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]+*,.popover-menu>[type=checkbox]+label+div>ul>li>[role=option]+*,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]+*,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]+*,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]+*,table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]+*,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]+*,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]+*{position:absolute;top:0;left:calc(100% + 10px)}.disclosure-menu [aria-expanded]~*>ul,.menu-panel>ul,.more-popover-menu>[type=checkbox]+label+div>ul,.popover-menu>[type=checkbox]+label+div>ul,table.has-actions tr>.actions>[type=checkbox]+label+div>ul,table.with-details tr>.actions>[type=checkbox]+label+div>ul{margin:0;padding:calc(var(--padding-y) - .625rem) 0;transition:transform 150ms}.disclosure-menu [aria-expanded]~*>ul,.disclosure-menu [aria-expanded]~*>ul>li,.disclosure-menu [aria-expanded]~*>ul>li>*,.menu-panel>ul,.menu-panel>ul>li,.menu-panel>ul>li>*,.more-popover-menu>[type=checkbox]+label+div>ul,.more-popover-menu>[type=checkbox]+label+div>ul>li,.more-popover-menu>[type=checkbox]+label+div>ul>li>*,.popover-menu>[type=checkbox]+label+div>ul,.popover-menu>[type=checkbox]+label+div>ul>li,.popover-menu>[type=checkbox]+label+div>ul>li>*,table.has-actions tr>.actions>[type=checkbox]+label+div>ul,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>*,table.with-details tr>.actions>[type=checkbox]+label+div>ul,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>*{width:100%}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem],.disclosure-menu [aria-expanded]~*>ul>li>[role=option],.menu-panel>ul>[role=treeitem],.menu-panel>ul>li>[role=menuitem],.menu-panel>ul>li>[role=option],.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option],.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem],.popover-menu>[type=checkbox]+label+div>ul>li>[role=option],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option],table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem],table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]{display:flex}.disclosure-menu [aria-expanded]~*>ul>[role=treeitem]::after,.disclosure-menu [aria-expanded]~*>ul>li>[role=menuitem]::after,.disclosure-menu [aria-expanded]~*>ul>li>[role=option]::after,.menu-panel>ul>[role=treeitem]::after,.menu-panel>ul>li>[role=menuitem]::after,.menu-panel>ul>li>[role=option]::after,.more-popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]::after,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]::after,.more-popover-menu>[type=checkbox]+label+div>ul>li>[role=option]::after,.popover-menu>[type=checkbox]+label+div>ul>[role=treeitem]::after,.popover-menu>[type=checkbox]+label+div>ul>li>[role=menuitem]::after,.popover-menu>[type=checkbox]+label+div>ul>li>[role=option]::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]::after,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>[role=treeitem]::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=menuitem]::after,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li>[role=option]::after{margin-left:auto;padding-right:var(--padding-x);transform:translate(calc(var(--padding-x)/ 2),0)}.disclosure-menu [aria-expanded]~* [role=separator],.menu-panel [role=separator],.more-popover-menu>[type=checkbox]+label+div [role=separator],.popover-menu>[type=checkbox]+label+div [role=separator],table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator],table.with-details tr>.actions>[type=checkbox]+label+div [role=separator]{text-transform:uppercase;color:var(--token-color-foreground-faint);padding-top:.375rem}.disclosure-menu [aria-expanded]~* [role=separator]:not(:first-child),.menu-panel [role=separator]:not(:first-child),.more-popover-menu>[type=checkbox]+label+div [role=separator]:not(:first-child),.popover-menu>[type=checkbox]+label+div [role=separator]:not(:first-child),table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator]:not(:first-child),table.with-details tr>.actions>[type=checkbox]+label+div [role=separator]:not(:first-child){margin-top:.275rem}.disclosure-menu [aria-expanded]~* [role=separator]:not(:empty),.menu-panel [role=separator]:not(:empty),.more-popover-menu>[type=checkbox]+label+div [role=separator]:not(:empty),.popover-menu>[type=checkbox]+label+div [role=separator]:not(:empty),table.has-actions tr>.actions>[type=checkbox]+label+div [role=separator]:not(:empty),table.with-details tr>.actions>[type=checkbox]+label+div [role=separator]:not(:empty){padding-left:var(--padding-x);padding-right:var(--padding-x);padding-bottom:.125rem}.disclosure-menu [aria-expanded]~.menu-panel-confirming,.menu-panel-confirming.menu-panel,.more-popover-menu>[type=checkbox]+label+div.menu-panel-confirming,.popover-menu>[type=checkbox]+label+div.menu-panel-confirming,table.has-actions tr>.actions>[type=checkbox]+label+div.menu-panel-confirming,table.with-details tr>.actions>[type=checkbox]+label+div.menu-panel-confirming{overflow:hidden}.disclosure-menu [aria-expanded]~.menu-panel-confirming>ul,.menu-panel-confirming.menu-panel>ul,.more-popover-menu>[type=checkbox]+label+div.menu-panel-confirming>ul,.popover-menu>[type=checkbox]+label+div.menu-panel-confirming>ul,table.has-actions tr>.actions>[type=checkbox]+label+div.menu-panel-confirming>ul,table.with-details tr>.actions>[type=checkbox]+label+div.menu-panel-confirming>ul{transform:translateX(calc(-100% - 10px))}.disclosure-menu [aria-expanded]~*,.menu-panel,.more-popover-menu>[type=checkbox]+label+div,.popover-menu>[type=checkbox]+label+div,table.has-actions tr>.actions>[type=checkbox]+label+div,table.with-details tr>.actions>[type=checkbox]+label+div{overflow:hidden}.menu-panel-deprecated{position:absolute;transition:max-height 150ms;transition:min-height 150ms,max-height 150ms;min-height:0}.menu-panel-deprecated [type=checkbox]{display:none}.menu-panel-deprecated:not(.confirmation) [type=checkbox]~*{transition:transform 150ms}.confirmation.menu-panel-deprecated [role=menu]{min-height:205px!important}.menu-panel-deprecated [type=checkbox]:checked~*{transform:translateX(calc(-100% - 10px));min-height:143px;max-height:143px}.menu-panel-deprecated [id$="-"]:first-child:checked~ul label[for$="-"] * [role=menu],.menu-panel-deprecated [id$="-"]:first-child:checked~ul>li>[role=menu]{display:block}.menu-panel-deprecated>ul>li>:not(div[role=menu]),.tippy-box{position:relative}.menu-panel-deprecated:not(.left){right:0!important;left:auto!important}.left.menu-panel-deprecated{left:0}.menu-panel-deprecated:not(.above){top:28px}.above.menu-panel-deprecated{bottom:42px}.consul-upstream-instance-list dl.local-bind-socket-mode dt::after{display:inline;content:var(--horizontal-kv-list-key-separator)}.consul-bucket-list,.consul-exposed-path-list>ul>li>.detail dl,.consul-instance-checks,.consul-lock-session-list dl,.consul-lock-session-list ul>li:not(:first-child)>.detail dl,.consul-upstream-instance-list dl,.consul-upstream-instance-list li>.detail dl,.list-collection>ul>li:not(:first-child)>.detail dl,.tag-list,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl,section[data-route="dc.show.license"] .validity dl,td.tags{display:inline-flex;flex-wrap:nowrap;align-items:center}.consul-bucket-list:empty,.consul-exposed-path-list>ul>li>.detail dl:empty,.consul-instance-checks:empty,.consul-lock-session-list dl:empty,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:empty,.consul-upstream-instance-list dl:empty,.list-collection>ul>li:not(:first-child)>.detail dl:empty,.tag-list:empty,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:empty,section[data-route="dc.show.license"] .validity dl:empty,td.tags:empty{display:none}.consul-bucket-list>*>*,.consul-exposed-path-list>ul>li>.detail dl>*>*,.consul-instance-checks>*>*,.consul-lock-session-list dl>*>*,.consul-lock-session-list ul>li:not(:first-child)>.detail dl>*>*,.consul-upstream-instance-list dl>*>*,.consul-upstream-instance-list li>.detail dl>*>*,.list-collection>ul>li:not(:first-child)>.detail dl>*>*,.tag-list>*>*,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl>*>*,section[data-route="dc.show.license"] .validity dl>*>*,td.tags>*>*{display:inline-block}.consul-bucket-list>*,.consul-exposed-path-list>ul>li>.detail dl>*,.consul-instance-checks>*,.consul-lock-session-list dl>*,.consul-lock-session-list ul>li:not(:first-child)>.detail dl>*,.consul-upstream-instance-list dl>*,.consul-upstream-instance-list li>.detail dl>*,.list-collection>ul>li:not(:first-child)>.detail dl>*,.tag-list>*,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl>*,section[data-route="dc.show.license"] .validity dl>*,td.tags>*{white-space:nowrap}.consul-bucket-list>dd,.consul-exposed-path-list>ul>li>.detail dl>dd,.consul-instance-checks>dd,.consul-lock-session-list dl>dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl>dd,.consul-upstream-instance-list dl>dd,.consul-upstream-instance-list li>.detail dl>dd,.list-collection>ul>li:not(:first-child)>.detail dl>dd,.tag-list>dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl>dd,section[data-route="dc.show.license"] .validity dl>dd,td.tags>dd{flex-wrap:wrap}.consul-upstream-instance-list dl.local-bind-socket-mode dt{display:inline-flex;min-width:18px;overflow:hidden}.consul-lock-session-list .checks dd,.discovery-chain .resolver-card ol,.filter-bar,.filter-bar>div,.modal-dialog,.tag-list dd,td.tags dd{display:flex}.consul-bucket-list .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-bucket-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-bucket-list .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.consul-bucket-list .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.consul-bucket-list .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-bucket-list .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-bucket-list .tag-list:not([class]) dd+dt:not([class])+dd,.consul-bucket-list dd+dt,.consul-bucket-list td.tags:not([class]) dd+dt:not([class])+dd,.consul-bucket-list+.consul-bucket-list:not(:first-of-type),.consul-bucket-list+.consul-instance-checks:not(:first-of-type),.consul-bucket-list+.tag-list:not(:first-of-type),.consul-bucket-list+td.tags:not(:first-of-type),.consul-bucket-list:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,.consul-bucket-list:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-bucket-list:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,.consul-bucket-list:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-bucket-list:not([class]) .tag-list dd+dt:not([class])+dd,.consul-bucket-list:not([class]) dd+dt:not([class])+dd,.consul-bucket-list:not([class]) td.tags dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail .consul-bucket-list+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail .consul-instance-checks+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail .consul-lock-session-list dl+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail .consul-upstream-instance-list dl+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.consul-exposed-path-list>ul>li>.detail .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.consul-exposed-path-list>ul>li>.detail .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail .tag-list+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail dl .consul-bucket-list:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl .consul-instance-checks:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl .consul-lock-session-list dl:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl .consul-upstream-instance-list dl:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl .tag-list:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl dd+dt,.consul-exposed-path-list>ul>li>.detail dl section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl td.tags:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl+.consul-bucket-list:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail dl+.consul-instance-checks:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail dl+.tag-list:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail dl+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail dl+td.tags:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail dl:not([class]) .consul-bucket-list dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) .consul-instance-checks dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) .consul-lock-session-list dl dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) .consul-upstream-instance-list dl dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) .tag-list dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) section[data-route="dc.show.license"] .validity dl dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) td.tags dd+dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),.consul-exposed-path-list>ul>li>.detail td.tags+dl:not(:first-of-type),.consul-instance-checks .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-instance-checks .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-instance-checks .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.consul-instance-checks .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.consul-instance-checks .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-instance-checks .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-instance-checks .tag-list:not([class]) dd+dt:not([class])+dd,.consul-instance-checks dd+dt,.consul-instance-checks td.tags:not([class]) dd+dt:not([class])+dd,.consul-instance-checks+.consul-bucket-list:not(:first-of-type),.consul-instance-checks+.consul-instance-checks:not(:first-of-type),.consul-instance-checks+.tag-list:not(:first-of-type),.consul-instance-checks+td.tags:not(:first-of-type),.consul-instance-checks:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,.consul-instance-checks:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-instance-checks:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,.consul-instance-checks:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-instance-checks:not([class]) .tag-list dd+dt:not([class])+dd,.consul-instance-checks:not([class]) dd+dt:not([class])+dd,.consul-instance-checks:not([class]) td.tags dd+dt:not([class])+dd,.consul-lock-session-list .consul-bucket-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-bucket-list+dl:not(:first-of-type),.consul-lock-session-list .consul-bucket-list:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list .consul-exposed-path-list>ul>li>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-exposed-path-list>ul>li>.detail dl+dl:not(:first-of-type),.consul-lock-session-list .consul-exposed-path-list>ul>li>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list .consul-instance-checks ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-instance-checks+dl:not(:first-of-type),.consul-lock-session-list .consul-instance-checks:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list dl li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list dl ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list dl+dl:not(:first-of-type),.consul-lock-session-list .consul-upstream-instance-list dl.local-bind-address dl dd+dt+dd,.consul-lock-session-list .consul-upstream-instance-list dl.local-bind-socket-path dl dd+dt+dd,.consul-lock-session-list .consul-upstream-instance-list dl:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list dl:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list li>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list li>.detail dl+dl:not(:first-of-type),.consul-lock-session-list .consul-upstream-instance-list li>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .consul-upstream-instance-list ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),.consul-lock-session-list .consul-upstream-instance-list ul>li:not(:first-child)>.detail dl.local-bind-address dd+dt+dd,.consul-lock-session-list .consul-upstream-instance-list ul>li:not(:first-child)>.detail dl.local-bind-socket-path dd+dt+dd,.consul-lock-session-list .consul-upstream-instance-list ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list .list-collection>ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .list-collection>ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),.consul-lock-session-list .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list .list-collection>ul>li:not(:first-child)>.detail ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .tag-list dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .tag-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list .tag-list+dl:not(:first-of-type),.consul-lock-session-list .tag-list:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list .tag-list:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list dl .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.consul-lock-session-list dl .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.consul-lock-session-list dl .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl .tag-list:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl dd+dt,.consul-lock-session-list dl td.tags:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl+.consul-bucket-list:not(:first-of-type),.consul-lock-session-list dl+.consul-instance-checks:not(:first-of-type),.consul-lock-session-list dl+.tag-list:not(:first-of-type),.consul-lock-session-list dl+dl:not(:first-of-type),.consul-lock-session-list dl+td.tags:not(:first-of-type),.consul-lock-session-list dl:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) .tag-list dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) td.tags dd+dt:not([class])+dd,.consul-lock-session-list dl:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),.consul-lock-session-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list section[data-route="dc.show.license"] .validity dl ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),.consul-lock-session-list section[data-route="dc.show.license"] .validity dl:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list td.tags dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list td.tags ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list td.tags+dl:not(:first-of-type),.consul-lock-session-list td.tags:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list td.tags:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail .consul-bucket-list+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail .consul-instance-checks+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail .consul-upstream-instance-list dl+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail .tag-list+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail dl .consul-bucket-list:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl .consul-instance-checks:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl .consul-upstream-instance-list dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl .tag-list:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt,.consul-lock-session-list ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl td.tags:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl+.consul-bucket-list:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail dl+.consul-instance-checks:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail dl+.tag-list:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail dl+td.tags:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) .consul-bucket-list dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) .consul-instance-checks dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) .consul-upstream-instance-list dl dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) .tag-list dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) section[data-route="dc.show.license"] .validity dl dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) td.tags dd+dt:not([class])+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),.consul-lock-session-list ul>li:not(:first-child)>.detail td.tags+dl:not(:first-of-type),.consul-upstream-instance-list .consul-bucket-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-bucket-list+dl:not(:first-of-type),.consul-upstream-instance-list .consul-bucket-list:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-exposed-path-list>ul>li>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-exposed-path-list>ul>li>.detail dl+dl:not(:first-of-type),.consul-upstream-instance-list .consul-exposed-path-list>ul>li>.detail dl.local-bind-address dd+dt+dd,.consul-upstream-instance-list .consul-exposed-path-list>ul>li>.detail dl.local-bind-socket-path dd+dt+dd,.consul-upstream-instance-list .consul-exposed-path-list>ul>li>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-instance-checks li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-instance-checks+dl:not(:first-of-type),.consul-upstream-instance-list .consul-instance-checks:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-lock-session-list dl li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-lock-session-list dl+dl:not(:first-of-type),.consul-upstream-instance-list .consul-lock-session-list dl:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),.consul-upstream-instance-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl.local-bind-address dd+dt+dd,.consul-upstream-instance-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl.local-bind-socket-path dd+dt+dd,.consul-upstream-instance-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-upstream-instance-list .list-collection>ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .list-collection>ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),.consul-upstream-instance-list .list-collection>ul>li:not(:first-child)>.detail dl.local-bind-address dd+dt+dd,.consul-upstream-instance-list .list-collection>ul>li:not(:first-child)>.detail dl.local-bind-socket-path dd+dt+dd,.consul-upstream-instance-list .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-upstream-instance-list .list-collection>ul>li:not(:first-child)>.detail li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .tag-list dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .tag-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list .tag-list+dl:not(:first-of-type),.consul-upstream-instance-list .tag-list:not([class]) dl dd+dt:not([class])+dd,.consul-upstream-instance-list .tag-list:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list dl .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl .tag-list:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl dd+dt,.consul-upstream-instance-list dl li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl td.tags:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl+.consul-bucket-list:not(:first-of-type),.consul-upstream-instance-list dl+.consul-instance-checks:not(:first-of-type),.consul-upstream-instance-list dl+.tag-list:not(:first-of-type),.consul-upstream-instance-list dl+dl:not(:first-of-type),.consul-upstream-instance-list dl+td.tags:not(:first-of-type),.consul-upstream-instance-list dl.local-bind-address .consul-bucket-list dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address .consul-instance-checks dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address .consul-lock-session-list dl dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address .tag-list dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address section[data-route="dc.show.license"] .validity dl dd+dt+dd,.consul-upstream-instance-list dl.local-bind-address td.tags dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path .consul-bucket-list dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path .consul-instance-checks dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path .consul-lock-session-list dl dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path .tag-list dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path section[data-route="dc.show.license"] .validity dl dd+dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path td.tags dd+dt+dd,.consul-upstream-instance-list dl:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) .tag-list dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list dl:not([class]) td.tags dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail .consul-bucket-list+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail .consul-instance-checks+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail .consul-lock-session-list dl+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail .tag-list+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail dl .consul-bucket-list:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl .consul-instance-checks:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl .consul-lock-session-list dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl .tag-list:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl dd+dt,.consul-upstream-instance-list li>.detail dl dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl td.tags:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl+.consul-bucket-list:not(:first-of-type),.consul-upstream-instance-list li>.detail dl+.consul-instance-checks:not(:first-of-type),.consul-upstream-instance-list li>.detail dl+.tag-list:not(:first-of-type),.consul-upstream-instance-list li>.detail dl+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail dl+td.tags:not(:first-of-type),.consul-upstream-instance-list li>.detail dl.local-bind-address dd+dt+dd,.consul-upstream-instance-list li>.detail dl.local-bind-socket-path dd+dt+dd,.consul-upstream-instance-list li>.detail dl:not([class]) .consul-bucket-list dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) .consul-instance-checks dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) .consul-lock-session-list dl dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) .tag-list dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) dl dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) section[data-route="dc.show.license"] .validity dl dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail dl:not([class]) td.tags dd+dt:not([class])+dd,.consul-upstream-instance-list li>.detail section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),.consul-upstream-instance-list li>.detail td.tags+dl:not(:first-of-type),.consul-upstream-instance-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),.consul-upstream-instance-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list section[data-route="dc.show.license"] .validity dl li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),.consul-upstream-instance-list section[data-route="dc.show.license"] .validity dl:not([class]) li>.detail dl dd+dt:not([class])+dd,.consul-upstream-instance-list td.tags dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list td.tags li>.detail dl:not([class]) dd+dt:not([class])+dd,.consul-upstream-instance-list td.tags+dl:not(:first-of-type),.consul-upstream-instance-list td.tags:not([class]) dl dd+dt:not([class])+dd,.consul-upstream-instance-list td.tags:not([class]) li>.detail dl dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail .consul-bucket-list+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail .consul-instance-checks+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail .consul-lock-session-list dl+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail .consul-upstream-instance-list dl+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.list-collection>ul>li:not(:first-child)>.detail .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.list-collection>ul>li:not(:first-child)>.detail .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail .tag-list+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail dl .consul-bucket-list:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl .consul-instance-checks:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl .consul-lock-session-list dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl .consul-upstream-instance-list dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl .tag-list:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl dd+dt,.list-collection>ul>li:not(:first-child)>.detail dl section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl td.tags:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl+.consul-bucket-list:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail dl+.consul-instance-checks:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail dl+.tag-list:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail dl+td.tags:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) .consul-bucket-list dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) .consul-instance-checks dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) .consul-lock-session-list dl dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) .consul-upstream-instance-list dl dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) .tag-list dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) section[data-route="dc.show.license"] .validity dl dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) td.tags dd+dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),.list-collection>ul>li:not(:first-child)>.detail td.tags+dl:not(:first-of-type),.tag-list .consul-bucket-list:not([class]) dd+dt:not([class])+dd,.tag-list .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,.tag-list .consul-instance-checks:not([class]) dd+dt:not([class])+dd,.tag-list .consul-lock-session-list dl:not([class]) dd+dt:not([class])+dd,.tag-list .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.tag-list .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,.tag-list .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,.tag-list .consul-upstream-instance-list dl:not([class]) dd+dt:not([class])+dd,.tag-list .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,.tag-list .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,.tag-list dd+dt,.tag-list section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,.tag-list section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,.tag-list td.tags:not([class]) dd+dt:not([class])+dd,.tag-list+.consul-bucket-list:not(:first-of-type),.tag-list+.consul-instance-checks:not(:first-of-type),.tag-list+.tag-list:not(:first-of-type),.tag-list+td.tags:not(:first-of-type),.tag-list:not([class]) .consul-bucket-list dd+dt:not([class])+dd,.tag-list:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,.tag-list:not([class]) .consul-instance-checks dd+dt:not([class])+dd,.tag-list:not([class]) .consul-lock-session-list dl dd+dt:not([class])+dd,.tag-list:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.tag-list:not([class]) .consul-upstream-instance-list dl dd+dt:not([class])+dd,.tag-list:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,.tag-list:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,.tag-list:not([class]) dd+dt:not([class])+dd,.tag-list:not([class]) section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt:not([class])+dd,.tag-list:not([class]) section[data-route="dc.show.license"] .validity dl dd+dt:not([class])+dd,.tag-list:not([class]) td.tags dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-bucket-list+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-exposed-path-list>ul>li>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-exposed-path-list>ul>li>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-exposed-path-list>ul>li>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-instance-checks+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-lock-session-list dl ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-lock-session-list dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-lock-session-list dl:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-lock-session-list ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-lock-session-list ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list dl li>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list dl.local-bind-address dl dd+dt+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list dl.local-bind-socket-path dl dd+dt+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list dl:not([class]) li>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list li>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list li>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .consul-upstream-instance-list li>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .list-collection>ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .list-collection>ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .tag-list dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header .tag-list+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header .tag-list:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl .tag-list:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl td.tags:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+.consul-bucket-list:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+.consul-instance-checks:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+.tag-list:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl+td.tags:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) .tag-list dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) td.tags dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header td.tags dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header td.tags+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section header td.tags:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section[data-route="dc.show.license"] .validity header dl+dl:not(:first-of-type),section[data-route="dc.show.serverstatus"] .redundancy-zones section[data-route="dc.show.license"] header .validity dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-bucket-list+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-exposed-path-list>ul>li>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-exposed-path-list>ul>li>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-exposed-path-list>ul>li>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-instance-checks+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-lock-session-list dl ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-lock-session-list dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-lock-session-list dl:not([class]) ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-lock-session-list ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-lock-session-list ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-upstream-instance-list dl li>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-upstream-instance-list dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-upstream-instance-list dl.local-bind-address dl dd+dt+dd,section[data-route="dc.show.license"] .validity .consul-upstream-instance-list dl.local-bind-socket-path dl dd+dt+dd,section[data-route="dc.show.license"] .validity .consul-upstream-instance-list dl:not([class]) li>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-upstream-instance-list li>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .consul-upstream-instance-list li>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .consul-upstream-instance-list li>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .list-collection>ul>li:not(:first-child)>.detail dl dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .list-collection>ul>li:not(:first-child)>.detail dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .tag-list dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity .tag-list+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity .tag-list:not([class]) dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,section[data-route="dc.show.license"] .validity dl .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,section[data-route="dc.show.license"] .validity dl .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl .tag-list:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl dd+dt,section[data-route="dc.show.license"] .validity dl td.tags:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl+.consul-bucket-list:not(:first-of-type),section[data-route="dc.show.license"] .validity dl+.consul-instance-checks:not(:first-of-type),section[data-route="dc.show.license"] .validity dl+.tag-list:not(:first-of-type),section[data-route="dc.show.license"] .validity dl+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity dl+td.tags:not(:first-of-type),section[data-route="dc.show.license"] .validity dl:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) .tag-list dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) td.tags dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity td.tags dl:not([class]) dd+dt:not([class])+dd,section[data-route="dc.show.license"] .validity td.tags+dl:not(:first-of-type),section[data-route="dc.show.license"] .validity td.tags:not([class]) dl dd+dt:not([class])+dd,td.tags .consul-bucket-list:not([class]) dd+dt:not([class])+dd,td.tags .consul-exposed-path-list>ul>li>.detail dl:not([class]) dd+dt:not([class])+dd,td.tags .consul-instance-checks:not([class]) dd+dt:not([class])+dd,td.tags .consul-lock-session-list dl:not([class]) dd+dt:not([class])+dd,td.tags .consul-lock-session-list ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,td.tags .consul-upstream-instance-list dl.local-bind-address dd+dt+dd,td.tags .consul-upstream-instance-list dl.local-bind-socket-path dd+dt+dd,td.tags .consul-upstream-instance-list dl:not([class]) dd+dt:not([class])+dd,td.tags .consul-upstream-instance-list li>.detail dl:not([class]) dd+dt:not([class])+dd,td.tags .list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dd+dt:not([class])+dd,td.tags .tag-list:not([class]) dd+dt:not([class])+dd,td.tags dd+dt,td.tags section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dd+dt:not([class])+dd,td.tags section[data-route="dc.show.license"] .validity dl:not([class]) dd+dt:not([class])+dd,td.tags+.consul-bucket-list:not(:first-of-type),td.tags+.consul-instance-checks:not(:first-of-type),td.tags+.tag-list:not(:first-of-type),td.tags+td.tags:not(:first-of-type),td.tags:not([class]) .consul-bucket-list dd+dt:not([class])+dd,td.tags:not([class]) .consul-exposed-path-list>ul>li>.detail dl dd+dt:not([class])+dd,td.tags:not([class]) .consul-instance-checks dd+dt:not([class])+dd,td.tags:not([class]) .consul-lock-session-list dl dd+dt:not([class])+dd,td.tags:not([class]) .consul-lock-session-list ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,td.tags:not([class]) .consul-upstream-instance-list dl dd+dt:not([class])+dd,td.tags:not([class]) .consul-upstream-instance-list li>.detail dl dd+dt:not([class])+dd,td.tags:not([class]) .list-collection>ul>li:not(:first-child)>.detail dl dd+dt:not([class])+dd,td.tags:not([class]) .tag-list dd+dt:not([class])+dd,td.tags:not([class]) dd+dt:not([class])+dd,td.tags:not([class]) section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dd+dt:not([class])+dd,td.tags:not([class]) section[data-route="dc.show.license"] .validity dl dd+dt:not([class])+dd{margin-left:var(--horizontal-kv-list-separator-width)}.consul-bucket-list dt+dd,.consul-exposed-path-list>ul>li>.detail dl dt+dd,.consul-instance-checks dt+dd,.consul-lock-session-list dl dt+dd,.consul-lock-session-list ul>li:not(:first-child)>.detail dl dt+dd,.consul-upstream-instance-list dl dt+dd,.consul-upstream-instance-list li>.detail dl dt+dd,.list-collection>ul>li:not(:first-child)>.detail dl dt+dd,.tag-list dt+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl dt+dd,section[data-route="dc.show.license"] .validity dl dt+dd,td.tags dt+dd{margin-left:4px}.consul-bucket-list:not([class]) dt:not([class])+dd,.consul-exposed-path-list>ul>li>.detail dl:not([class]) dt:not([class])+dd,.consul-instance-checks:not([class]) dt:not([class])+dd,.consul-lock-session-list dl:not([class]) dt:not([class])+dd,.consul-upstream-instance-list dl.local-bind-address dt+dd,.consul-upstream-instance-list dl.local-bind-socket-path dt+dd,.consul-upstream-instance-list dl:not([class]) dt:not([class])+dd,.list-collection>ul>li:not(:first-child)>.detail dl:not([class]) dt:not([class])+dd,.tag-list:not([class]) dt:not([class])+dd,section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not([class]) dt:not([class])+dd,section[data-route="dc.show.license"] .validity dl:not([class]) dt:not([class])+dd,td.tags:not([class]) dt:not([class])+dd{margin-left:0!important}.consul-lock-session-list .checks dd>:not(:last-child)::after,.discovery-chain .resolver-card ol>:not(:last-child)::after,.tag-list dd>:not(:last-child)::after,td.tags dd>:not(:last-child)::after{display:inline;content:var(--csv-list-separator);vertical-align:initial;margin-right:.3em}.freetext-filter_label::after,.tippy-box .tippy-arrow::before{content:"";position:absolute}.tag-list dt::before,td.tags dt::before{color:inherit;color:var(--token-color-foreground-faint)}.consul-exposed-path-list>ul>li>.detail dl>dt>*,.consul-lock-session-list ul>li:not(:first-child)>.detail dl>dt>*,.consul-upstream-instance-list li>.detail dl>dt>*,.list-collection>ul>li:not(:first-child)>.detail dl>dt>*{display:none}.consul-exposed-path-list>ul>li>.detail dl.passing dt::before,.consul-exposed-path-list>ul>li>.header .passing dd::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.passing dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .passing dd::before,.consul-upstream-instance-list li>.detail dl.passing dt::before,.consul-upstream-instance-list li>.header .passing dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.passing dt::before,.list-collection>ul>li:not(:first-child)>.header .passing dd::before{color:var(--token-color-foreground-success)}.consul-exposed-path-list>ul>li>.detail dl.warning dt::before,.consul-exposed-path-list>ul>li>.header .warning dd::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.warning dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .warning dd::before,.consul-upstream-instance-list li>.detail dl.warning dt::before,.consul-upstream-instance-list li>.header .warning dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.warning dt::before,.list-collection>ul>li:not(:first-child)>.header .warning dd::before{color:var(--token-color-foreground-warning)}.consul-exposed-path-list>ul>li>.detail dl.critical dt::before,.consul-exposed-path-list>ul>li>.header .critical dd::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.critical dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .critical dd::before,.consul-upstream-instance-list li>.detail dl.critical dt::before,.consul-upstream-instance-list li>.header .critical dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.critical dt::before,.list-collection>ul>li:not(:first-child)>.header .critical dd::before{color:var(--token-color-foreground-critical)}.consul-exposed-path-list>ul>li>.detail dl.empty dt::before,.consul-exposed-path-list>ul>li>.detail dl.unknown dt::before,.consul-exposed-path-list>ul>li>.header .empty dd::before,.consul-exposed-path-list>ul>li>.header .unknown dd::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.empty dt::before,.consul-lock-session-list ul>li:not(:first-child)>.detail dl.unknown dt::before,.consul-lock-session-list ul>li:not(:first-child)>.header .empty dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header .unknown dd::before,.consul-upstream-instance-list li>.detail dl.empty dt::before,.consul-upstream-instance-list li>.detail dl.unknown dt::before,.consul-upstream-instance-list li>.header .empty dd::before,.consul-upstream-instance-list li>.header .unknown dd::before,.list-collection>ul>li:not(:first-child)>.detail dl.empty dt::before,.list-collection>ul>li:not(:first-child)>.detail dl.unknown dt::before,.list-collection>ul>li:not(:first-child)>.header .empty dd::before,.list-collection>ul>li:not(:first-child)>.header .unknown dd::before{color:var(--token-color-foreground-faint)}.consul-exposed-path-list>ul>li>.header [rel=me] dd::before,.consul-lock-session-list ul>li:not(:first-child)>.header [rel=me] dd::before,.consul-upstream-instance-list li>.header [rel=me] dd::before,.list-collection>ul>li:not(:first-child)>.header [rel=me] dd::before{color:var(--token-color-foreground-action)}.app-view>div form:not(.filter-bar) [role=radiogroup] label>em>code,.modal-dialog [role=document] .type-password>em>code,.modal-dialog [role=document] .type-select>em>code,.modal-dialog [role=document] .type-text>em>code,.modal-dialog [role=document] [role=radiogroup] label>em>code,.modal-dialog [role=document] form button+em>code,.modal-dialog [role=document] p code,.oidc-select label>em>code,.type-toggle>em>code,main .type-password>em>code,main .type-select>em>code,main .type-text>em>code,main form button+em>code,main p code{border:1px solid;color:var(--token-color-consul-brand);background-color:var(--token-color-surface-strong);border-color:var(--token-color-surface-interactive-active);display:inline-block;padding:0 4px}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{outline:0;background-color:var(--token-color-surface-primary);border-radius:var(--decor-radius-100)}[data-animation=fade][data-state=hidden].tippy-box{opacity:0}[data-inertia][data-state=visible].tippy-box{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-box .tippy-arrow{--size:5px}[data-placement^=top].tippy-box>.tippy-arrow{bottom:0}[data-placement^=top].tippy-box>.tippy-arrow::before{left:0;bottom:calc(0px - var(--size));transform-origin:center top}[data-placement^=bottom].tippy-box>.tippy-arrow{top:0}[data-placement^=bottom].tippy-box>.tippy-arrow::before{left:0;top:calc(0px - var(--size));transform-origin:center bottom}[data-placement^=left].tippy-box>.tippy-arrow{right:0}[data-placement^=left].tippy-box>.tippy-arrow::before{right:calc(0px - var(--size));transform-origin:center left}[data-placement^=right].tippy-box>.tippy-arrow{left:0}[data-placement^=right].tippy-box>.tippy-arrow::before{left:calc(0px - var(--size));transform-origin:center right}[data-theme~=square-tail] .tippy-arrow{--size:18px;left:calc(0px - var(--size)/ 2)!important}[data-theme~=square-tail] .tippy-arrow::before{background-color:var(--token-color-surface-primary);width:calc(1px + var(--size));height:calc(1px + var(--size));border:var(--decor-border-100);border-color:var(--token-color-palette-neutral-300)}[data-theme~=square-tail] .tippy-arrow::after{position:absolute;left:1px}[data-theme~=square-tail][data-placement^=top]{bottom:-10px}[data-theme~=square-tail][data-placement^=top] .informed-action{border-bottom-left-radius:0!important}[data-theme~=square-tail][data-placement^=top] .tippy-arrow::before{border-bottom-left-radius:var(--decor-radius-200);border-bottom-right-radius:var(--decor-radius-200);border-top:0!important}[data-theme~=square-tail][data-placement^=top] .tippy-arrow::after{bottom:calc(0px - var(--size))}[data-theme~=square-tail][data-placement^=bottom]{top:-10px}[data-theme~=square-tail][data-placement^=bottom] .informed-action{border-top-left-radius:0!important}[data-theme~=square-tail][data-placement^=bottom] .tippy-arrow::before{border-top-left-radius:var(--decor-radius-200);border-top-right-radius:var(--decor-radius-200);border-bottom:0!important}[data-theme~=square-tail][data-placement^=bottom] .tippy-arrow::after{top:calc(0px - var(--size))}.tippy-box[data-theme~=tooltip] .tippy-content{padding:12px;max-width:224px;position:relative;z-index:1}.tippy-box[data-theme~=tooltip]{background-color:var(--token-color-foreground-faint)}.tippy-box[data-theme~=tooltip] .tippy-arrow{--size:5px;color:var(--token-color-foreground-faint);width:calc(var(--size) * 2);height:calc(var(--size) * 2)}.tippy-box[data-theme~=tooltip] .tippy-arrow::before{border-color:transparent;border-style:solid}.tippy-box[data-theme~=tooltip][data-placement^=top]>.tippy-arrow::before{border-width:var(--size) var(--size) 0;border-top-color:initial}.tippy-box[data-theme~=tooltip][data-placement^=bottom]>.tippy-arrow::before{border-width:0 var(--size) var(--size);border-bottom-color:initial}.tippy-box[data-theme~=tooltip][data-placement^=left]>.tippy-arrow::before{border-width:var(--size) 0 var(--size) var(--size);border-left-color:initial}.tippy-box[data-theme~=tooltip][data-placement^=right]>.tippy-arrow::before{border-width:var(--size) var(--size) var(--size) 0;border-right-color:initial}.warning.modal-dialog header{background-color:var(--token-color-vault-gradient-faint-start);border-color:var(--token-color-vault-brand);color:var(--token-color-vault-foreground)}.warning.modal-dialog header::before{color:var(--token-color-vault-brand);float:left;margin-top:2px;margin-right:3px}.modal-dialog>div:first-child{background-color:var(--token-color-surface-interactive);opacity:.9}.modal-dialog [role=document]>footer,.modal-dialog [role=document]>header,.modal-dialog-body{border-color:var(--token-color-palette-neutral-300)}.modal-dialog-body{border-style:solid;border-left-width:1px;border-right-width:1px}.modal-layer{height:0}.modal-dialog [role=document] table{height:150px!important}.modal-dialog [role=document] tbody{max-height:100px}.modal-dialog table{min-height:149px}.modal-dialog,.modal-dialog>div:first-child{position:fixed;top:0;right:0;bottom:0;left:0}.modal-dialog{z-index:500;align-items:center;justify-content:center;height:100%}[aria-hidden=true].modal-dialog{display:none}.modal-dialog [role=document]{background-color:var(--token-color-surface-primary);margin:auto;z-index:2;max-width:855px;position:relative}.modal-dialog [role=document]>*{padding-left:15px;padding-right:15px}.modal-dialog [role=document]>div{overflow-y:auto;max-height:80vh;padding:20px 23px}.modal-dialog [role=document]>footer,.modal-dialog [role=document]>header{border-width:1px;padding-top:12px;padding-bottom:10px}.modal-dialog [role=document]>header{position:relative}.modal-dialog [role=document]>header button{float:right;margin-top:-3px}.list-collection>ul{border-top:1px solid;border-color:var(--token-color-surface-interactive-active)}.list-collection>button{cursor:pointer;background-color:var(--token-color-surface-strong);color:var(--token-color-foreground-action);width:100%;padding:15px}.list-collection-scroll-virtual,.list-collection>ul>li{position:relative}.list-collection-scroll-virtual{height:500px}.filter-bar{background-color:var(--token-color-foreground-high-contrast);border-bottom:var(--decor-border-100);border-color:var(--token-color-surface-interactive-active);padding:4px 8px}.filter-bar .filters .popover-menu>[type=checkbox]:checked+label button,.filter-bar .sort .popover-menu>[type=checkbox]:checked+label button{color:var(--token-color-foreground-action);background-color:var(--token-color-foreground-high-contrast)}.filter-bar .sort{margin-left:auto}.filter-bar .popover-select{position:relative;z-index:3}.filter-bar .popover-menu>[type=checkbox]+label button{padding-left:1.5rem!important;padding-right:1.5rem!important}.filter-bar .popover-menu [role=menuitem]{justify-content:normal!important}@media (max-width:1379px){.filter-bar,.filter-bar>div{flex-wrap:wrap}.filter-bar .search{position:relative;z-index:4;width:100%;margin-bottom:.3rem}}@media (max-width:995px){.filter-bar .filters,.filter-bar .sort{display:none}}html[data-route^="dc.acls.index"] .filter-bar{color:inherit}.freetext-filter{border:var(--decor-border-100);border-radius:var(--decor-radius-100);background-color:var(--token-color-surface-primary);border-color:var(--token-color-surface-interactive-active);color:var(--token-color-foreground-disabled)}.freetext-filter:hover,.freetext-filter:hover *{border-color:var(--token-color-foreground-disabled)}.freetext-filter_input::-moz-placeholder{cursor:inherit;color:inherit;border-color:inherit}.freetext-filter *,.freetext-filter_input::placeholder{cursor:inherit;color:inherit;border-color:inherit}.freetext-filter_input{-webkit-appearance:none;border:none}.freetext-filter_label::after{visibility:visible;--icon-name:icon-search;top:50%;left:50%;width:16px;height:16px;margin-left:-8px;margin-top:-8px}.freetext-filter .popover-menu{background-color:var(--token-color-surface-strong);color:var(--token-color-foreground-primary);border-left:1px solid;border-color:inherit}.freetext-filter .popover-menu>[type=checkbox]:checked+label button{background-color:var(--token-color-surface-interactive-active)}.freetext-filter{--height:2.2rem;display:flex;position:relative;height:var(--height);width:100%}.freetext-filter_input,.freetext-filter_label{height:100%}.freetext-filter_input{padding:8px 10px;padding-left:var(--height);min-width:12.7rem;width:100%}.freetext-filter_label{visibility:hidden;position:absolute;z-index:1;width:var(--height)}.informed-action{border-radius:var(--decor-radius-200);border:var(--decor-border-100);border-color:var(--token-color-palette-neutral-300);background-color:var(--token-color-surface-primary);min-width:190px}.informed-action>div{border-top-left-radius:var(--decor-radius-200);border-top-right-radius:var(--decor-radius-200);cursor:default;padding:1rem}.informed-action p{color:var(--token-color-hashicorp-brand)}.informed-action>ul>li>:focus,.informed-action>ul>li>:hover{background-color:var(--token-color-surface-strong)}.info.informed-action header{color:var(--token-color-foreground-action-active)}.info.informed-action header::before{background-color:var(--token-color-foreground-action);margin-right:5px}.info.informed-action>div{background-color:var(--token-color-surface-action)}.dangerous.informed-action header{color:var(--token-color-palette-red-400)}.dangerous.informed-action header::before{background-color:var(--token-color-foreground-critical)}.dangerous.informed-action>div{background-color:var(--token-color-surface-critical)}.warning.informed-action header{color:var(--token-color-foreground-warning-on-surface)}.warning.informed-action header::before{background-color:var(--token-color-vault-brand);margin-right:5px}.warning.informed-action>div{background-color:var(--token-color-vault-gradient-faint-start)}.copyable-code::after,.tab-nav li:not(.selected)>:active,.tab-nav li:not(.selected)>:focus,.tab-nav li:not(.selected)>:hover{background-color:var(--token-color-surface-strong)}.informed-action>ul>.action>*{color:var(--token-color-foreground-action)}.documentation.informed-action{min-width:270px}.informed-action header::before{float:left;margin-right:5px}.informed-action>ul{list-style:none;display:flex;margin:0;padding:4px}.informed-action>ul>li{width:50%}.informed-action>ul>li>*{width:100%}.tab-nav ul{list-style-type:none;display:inline-flex;align-items:center;position:relative;padding:0;margin:0}.tab-nav li>:not(:disabled){cursor:pointer}.tab-nav{border-bottom:var(--decor-border-100)}.animatable.tab-nav ul::after,.tab-nav li>*{border-bottom:var(--decor-border-300)}.tab-nav{border-color:var(--token-color-surface-interactive-active);clear:both;overflow:auto}.tab-nav li>*{white-space:nowrap;text-decoration:none;transition-property:background-color,border-color;border-color:transparent;color:var(--token-color-foreground-faint);display:inline-block;padding:16px 13px}.tab-nav li:not(.selected)>:focus,.tab-nav li:not(.selected)>:hover{border-color:var(--token-color-palette-neutral-300)}.animatable.tab-nav .selected a{border-color:transparent!important}.animatable.tab-nav ul::after{position:absolute;bottom:0;height:0;border-top:0;width:calc(var(--selected-width,0) * 1px);transform:translate(calc(var(--selected-left,0) * 1px),0);transition-property:transform,width}.search-bar-status{border-bottom:var(--decor-border-100);border-bottom-color:var(--token-color-surface-interactive-active);padding:.5rem 0 .5rem .5rem}.search-bar-status li:not(.remove-all) button::before{color:var(--token-color-foreground-faint);margin-top:1px;margin-right:.2rem}.search-bar-status dt::after{content:":";padding-right:.3rem}.search-bar-status>dl>dt{float:left}.search-bar-status dt{white-space:nowrap}.search-bar-status li{display:inline-flex}.search-bar-status li:not(:last-child){margin-right:.3rem;margin-bottom:.3rem}.search-bar-status li:not(.remove-all){border:var(--decor-border-100);border-color:var(--token-color-surface-interactive-active);color:var(--token-color-foreground-faint);padding:0 .2rem}.search-bar-status li:not(.remove-all) dl{display:flex}.search-bar-status li:not(.remove-all) button{cursor:pointer;padding:0}.copyable-code{display:flex;align-items:flex-start;position:relative;width:100%;padding:8px 14px 3px;border:var(--decor-border-100);border-color:var(--token-color-surface-interactive-active);border-radius:var(--decor-radius-200)}.copyable-code.obfuscated{padding-left:4px}.copyable-code::after{position:absolute;top:0;right:0;width:40px;height:100%;display:block;content:""}.copyable-code .copy-button{position:absolute;top:0;right:0;z-index:1}.copyable-code .copy-button button{width:40px;height:40px}.copyable-code .copy-button button:empty::after{display:none}.copyable-code button[aria-expanded]{margin-top:1px;margin-right:4px;cursor:pointer}.copyable-code button[aria-expanded]::before{content:"";--icon-size:icon-000;--icon-color:var(--token-color-foreground-faint)}.copyable-code button[aria-expanded=true]::before{--icon-name:icon-eye-off}.copyable-code button[aria-expanded=false]::before{--icon-name:icon-eye}.copyable-code pre{padding-right:30px}.copyable-code code{display:inline-block;overflow:hidden;text-overflow:ellipsis;width:100%}.copyable-code hr{width:calc(100% - 80px);margin:8px 0 13px;border:3px dashed var(--token-color-palette-neutral-300);background-color:var(--token-color-surface-primary)}.consul-loader circle{fill:var(--token-color-consul-gradient-faint-stop);animation:loader-animation 1.5s infinite ease-in-out;transform-origin:50% 50%}.consul-loader g:nth-last-child(2) circle{animation-delay:.2s}.consul-loader g:nth-last-child(3) circle{animation-delay:.3s}.consul-loader g:nth-last-child(4) circle{animation-delay:.4s}.consul-loader g:nth-last-child(5) circle{animation-delay:.5s}@keyframes loader-animation{0%,100%{transform:scale3D(1,1,1)}33%{transform:scale3D(0,0,1)}}.consul-loader{display:flex;align-items:center;justify-content:center;height:100%;position:absolute;width:100%;top:0;margin-top:0!important}.tomography-graph .background{fill:var(--token-color-surface-strong)}.tomography-graph .axis{fill:none;stroke:var(--token-color-palette-neutral-300);stroke-dasharray:4 4}.tomography-graph .border{fill:none;stroke:var(--token-color-palette-neutral-300)}.tomography-graph .point{stroke:var(--token-color-foreground-disabled);fill:var(--token-color-consul-foreground)}.tomography-graph .lines rect{fill:var(--token-color-consul-foreground);stroke:transparent;stroke-width:5px}.tomography-graph .lines rect:hover{fill:var(--token-color-palette-neutral-300);height:3px;y:-1px}.tomography-graph .tick line{stroke:var(--token-color-palette-neutral-300)}.tomography-graph .tick text{text-anchor:start}.discovery-chain .resolver-card,.discovery-chain .route-card,.discovery-chain .splitter-card,.discovery-chain path{transition-duration:.1s;transition-timing-function:linear;cursor:pointer}.discovery-chain path{transition-property:stroke;fill:none;stroke:var(--token-color-foreground-disabled);stroke-width:2;vector-effect:non-scaling-stroke}#downstream-lines svg circle,#upstream-lines svg circle,.discovery-chain circle{fill:var(--token-color-surface-primary)}.discovery-chain .resolver-card,.discovery-chain .resolver-card a,.discovery-chain .route-card,.discovery-chain .route-card a,.discovery-chain .splitter-card,.discovery-chain .splitter-card a{color:var(--token-color-foreground-strong)!important}.discovery-chain path:focus,.discovery-chain path:hover{stroke:var(--token-color-foreground-strong)}.discovery-chain .resolvers,.discovery-chain .routes,.discovery-chain .splitters{border-radius:var(--decor-radius-100);border:1px solid;border-color:var(--token-color-surface-interactive-active);background-color:var(--token-color-surface-strong);pointer-events:none}.discovery-chain .resolver-card,.discovery-chain .resolvers>header span,.discovery-chain .route-card,.discovery-chain .routes>header span,.discovery-chain .splitter-card,.discovery-chain .splitters>header span{pointer-events:all}.discovery-chain .resolvers>header>*,.discovery-chain .routes>header>*,.discovery-chain .splitters>header>*{text-transform:uppercase}.discovery-chain .resolvers>header span::after,.discovery-chain .routes>header span::after,.discovery-chain .splitters>header span::after{width:1.2em;height:1.2em;opacity:.6}.discovery-chain .resolver-card,.discovery-chain .route-card,.discovery-chain .splitter-card{transition-property:opacity background-color border-color;margin-top:0!important}.discovery-chain [id*=":"]:not(path):hover{opacity:1;background-color:var(--token-color-surface-primary);border-color:var(--token-color-foreground-faint)}.discovery-chain .route-card header:not(.short) dd{overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.discovery-chain .route-card section header>*{visibility:hidden}.discovery-chain .route-card .match-headers header ::before{content:"H"}.discovery-chain .route-card .match-queryparams header>::before{content:"Q"}.discovery-chain .resolver-card dt::before{content:"";--icon-size:icon-999}.discovery-chain .resolver-card dl.failover dt::before{--icon-name:icon-cloud-cross}.discovery-chain .resolver-card dl.redirect dt::before{--icon-name:icon-redirect}.discovery-chain circle{stroke-width:2;stroke:var(--token-color-foreground-disabled)}.discovery-chain{position:relative;display:flex;justify-content:space-between}.discovery-chain svg{position:absolute}.discovery-chain .resolvers,.discovery-chain .routes,.discovery-chain .splitters{padding:10px 1%;width:32%}.discovery-chain .resolvers>header,.discovery-chain .routes>header,.discovery-chain .splitters>header{height:18px}.discovery-chain .resolvers>header span,.discovery-chain .routes>header span,.discovery-chain .splitters>header span{position:relative;z-index:1;margin-left:2px}.discovery-chain .resolvers [role=group],.discovery-chain .routes [role=group],.discovery-chain .splitters [role=group]{position:relative;z-index:1;display:flex;flex-direction:column;justify-content:space-around;height:100%}.discovery-chain .resolver-card dl,.discovery-chain .route-card dl,.discovery-chain .splitter-card dl{margin:0;float:none}.discovery-chain .resolver-card,.discovery-chain .route-card,.discovery-chain .splitter-card{margin-bottom:20px}.discovery-chain .route-card header.short dl{display:flex}.discovery-chain .route-card header.short dt::after{content:" ";display:inline-block}.discovery-chain .route-card>header ul{float:right;margin-top:-2px}.discovery-chain .route-card>header ul li{margin-left:5px}.discovery-chain .route-card section{display:flex}.discovery-chain .route-card section header{display:block;width:19px;margin-right:14px}.discovery-chain .resolver-card a{display:block}.discovery-chain .resolver-card dl{display:flex;flex-wrap:wrap;margin-top:5px}.discovery-chain .resolver-card dt{font-size:0;margin-right:6px;margin-top:1px;width:23px;height:20px}.discovery-chain .resolver-card ol{display:flex;flex-wrap:wrap;list-style-type:none}.discovery-chain .route-card,.discovery-chain .splitter-card{position:relative}.discovery-chain .route-card::before,.discovery-chain .splitter-card::before{background-color:var(--token-color-surface-primary);border-radius:var(--decor-radius-full);border:2px solid;border-color:var(--token-color-foreground-disabled);position:absolute;z-index:1;right:-5px;top:50%;margin-top:-5px;width:10px;height:10px}.discovery-chain .resolver-inlets,.discovery-chain .splitter-inlets{width:10px;height:100%;z-index:1}.discovery-chain .splitter-inlets{left:50%;margin-left:calc(-15% - 3px)}.discovery-chain .resolver-inlets{right:calc(31% - 7px)}.consul-bucket-list dd:not(:last-child)::after{display:inline-block;content:"/";margin:0 6px 0 3px}.consul-bucket-list .service+dd,.consul-bucket-list dd+dt{margin-left:0!important}.consul-upstream-instance-list dl.local-bind-socket-mode dt{text-transform:lowercase;font-weight:var(--token-typography-font-weight-semibold)}.consul-health-check-list .health-check-output::before{min-width:20px;min-height:20px;margin-right:15px}@media (max-width:650px){.consul-health-check-list .health-check-output::before{min-width:18px;min-height:18px;margin-right:8px}}.consul-health-check-list .health-check-output dd em{background-color:var(--token-color-surface-strong);cursor:default;font-style:normal;margin-top:-2px;margin-left:.5em}.consul-health-check-list .passing.health-check-output::before{color:var(--token-color-foreground-success)}.consul-health-check-list .warning.health-check-output::before{color:var(--token-color-foreground-warning)}.consul-health-check-list .critical.health-check-output::before{color:var(--token-color-foreground-critical)}.consul-health-check-list .health-check-output,.consul-health-check-list .health-check-output pre{border-radius:var(--decor-radius-100)}.consul-health-check-list .health-check-output dd:first-of-type{color:var(--token-color-foreground-disabled)}.consul-health-check-list .health-check-output pre{background-color:var(--token-color-surface-strong);color:var(--token-color-foreground-faint)}.consul-health-check-list .health-check-output{border-width:1px 1px 1px 4px;color:var(--token-color-foreground-strong);border-color:var(--token-color-surface-interactive-active);border-style:solid;display:flex;padding:20px 24px 20px 16px}.consul-health-check-list .passing.health-check-output{border-left-color:var(--token-color-foreground-success)}.consul-health-check-list .warning.health-check-output{border-left-color:var(--token-color-vault-brand)}.consul-health-check-list .critical.health-check-output{border-left-color:var(--token-color-foreground-critical)}.consul-health-check-list .health-check-output:not(:last-child){margin-bottom:24px}.consul-health-check-list .health-check-output dl:last-of-type,.consul-health-check-list .health-check-output header{width:100%}.consul-health-check-list .health-check-output header{margin-bottom:.9em}.consul-health-check-list .health-check-output>div{flex:1 1 auto;width:calc(100% - 26px);display:flex;flex-wrap:wrap;justify-content:space-between}.consul-health-check-list .health-check-output dl{min-width:110px}.consul-health-check-list .health-check-output dl>*{display:block;width:auto;position:static;padding-left:0}.consul-health-check-list .health-check-output dt{margin-bottom:0}.consul-health-check-list .health-check-output dd{position:relative}.consul-health-check-list .health-check-output dl:nth-last-of-type(2){width:50%}.consul-health-check-list .health-check-output dl:last-of-type{margin-top:1em;margin-bottom:0}.consul-health-check-list .health-check-output dl:last-of-type dt{margin-bottom:.3em}.consul-health-check-list .health-check-output pre{padding:12px 40px 12px 12px;white-space:pre-wrap;position:relative}.consul-health-check-list .health-check-output pre code{word-wrap:break-word}.consul-health-check-list .health-check-output .copy-button{position:absolute;right:.5em;top:.7em}@media (max-width:650px){.consul-health-check-list .health-check-output{padding:15px 19px 15px 14px}.consul-health-check-list .health-check-output::before{margin-right:8px}.consul-health-check-list .health-check-output dl:nth-last-of-type(2){width:100%}.consul-health-check-list .health-check-output dl:not(:last-of-type){margin-right:0}}.consul-instance-checks.passing dt::before{color:var(--token-color-foreground-success)}.consul-instance-checks.warning dt::before{color:var(--token-color-foreground-warning)}.consul-instance-checks.critical dt::before{color:var(--token-color-foreground-critical)}.consul-instance-checks.empty dt::before{color:var(--token-color-foreground-faint)}.consul-exposed-path-list>ul{border-top:1px solid var(--token-color-surface-interactive-active)}.consul-external-source::before,.consul-kind::before{--icon-size:icon-300}.consul-intention-list td.intent- strong::before,.consul-intention-list td.intent-allow strong::before,.consul-intention-list td.intent-deny strong::before,.consul-intention-permission-list .intent-allow::before,.consul-intention-permission-list .intent-deny::before,.consul-intention-search-bar .value- span::before,.consul-intention-search-bar .value-allow span::before,.consul-intention-search-bar .value-deny span::before{margin-right:5px}.consul-intention-list td.intent- strong,.consul-intention-list td.intent-allow strong,.consul-intention-list td.intent-deny strong,.consul-intention-permission-list .intent-allow,.consul-intention-permission-list .intent-deny,.consul-intention-search-bar .value- span,.consul-intention-search-bar .value-allow span,.consul-intention-search-bar .value-deny span{font-weight:var(--token-typography-font-weight-regular);font-size:var(--token-typography-body-200-font-size);display:inline-block}.consul-intention-list td.intent-allow strong,.consul-intention-permission-list .intent-allow,.consul-intention-search-bar .value-allow span{color:var(--token-color-foreground-success-on-surface);background-color:var(--token-color-border-success)}.consul-intention-list td.intent-deny strong,.consul-intention-permission-list .intent-deny,.consul-intention-search-bar .value-deny span{color:var(--token-color-foreground-critical-on-surface);background-color:var(--token-color-border-critical)}.consul-intention-list td.permissions{color:var(--token-color-foreground-action)}.consul-intention-list em{--word-spacing:0.25rem}.consul-intention-list em span::before,.consul-intention-list em span:first-child{margin-right:var(--word-spacing)}.consul-intention-list em span:last-child{margin-left:var(--word-spacing)}.consul-intention-list td{height:59px}.consul-intention-list tr>:nth-child(1){width:calc(30% - 50px)}.consul-intention-list tr>:nth-child(2){width:120px}.consul-intention-list tr>:nth-child(3){width:calc(30% - 50px)}.consul-intention-list tr>:nth-child(4){width:calc(40% - 240px)}.consul-intention-list tr>:nth-child(5){width:160px}.consul-intention-list tr>:last-child{width:60px}.consul-intention-list .menu-panel.confirmation{width:200px}@media (max-width:849px){.consul-intention-list tr>:not(.source):not(.destination):not(.intent){display:none}}.consul-intention-action-warn-modal .modal-dialog-window{max-width:450px}.consul-intention-fieldsets [role=radiogroup]{overflow:visible!important;display:grid;grid-gap:12px;grid-template-columns:repeat(auto-fit,minmax(270px,auto))}.consul-intention-fieldsets .radio-card header>*{display:inline}.consul-intention-fieldsets .permissions>button{float:right}.consul-intention-permission-modal [role=dialog]{width:100%}.consul-intention-permission-list dl.permission-methods dt::before{content:"M"}.consul-intention-permission-list dl.permission-path dt::before{content:"P"}.consul-intention-permission-header-list dt::before,.consul-intention-permission-list dl.permission-header dt::before{content:"H"}.consul-intention-permission-list .detail>div{display:flex;width:100%}.consul-intention-permission-list strong{margin-right:8px}.consul-intention-permission-form h2{border-top:1px solid var(--token-color-foreground-action);padding-top:1.4em;margin-top:.2em;margin-bottom:.6em}.consul-intention-permission-form .consul-intention-permission-header-form{margin-top:10px}.consul-intention-permission-form .consul-intention-permission-header-form fieldset>div,.consul-intention-permission-form fieldset:nth-child(2)>div{display:grid;grid-template-columns:repeat(auto-fit,minmax(160px,1fr));grid-gap:12px}.consul-intention-permission-form fieldset:nth-child(2)>div label:last-child{grid-column:span 2}.consul-intention-permission-form .ember-basic-dropdown-trigger{padding:5px}.consul-intention-permission-form .checkbox-group{flex-direction:column}.consul-intention-permission-header-list{max-height:200px;overflow:auto}.consul-lock-session-list button{margin-right:var(--horizontal-padding)}.consul-lock-session-form{overflow:hidden}.consul-server-list ul{display:grid;grid-template-columns:repeat(4,minmax(215px,25%));gap:12px}.consul-server-list a:hover div{--tone-border:var(--token-color-foreground-faint)}.consul-server-card .name+dd{color:var(--token-color-hashicorp-brand);animation-name:typo-truncate}.voting-status-non-voter.consul-server-card .health-status+dd{background-color:var(--token-color-surface-strong);color:var(--token-color-foreground-faint)}.consul-server-card:not(.voting-status-non-voter) .health-status.healthy+dd{background-color:var(--token-color-surface-success);color:var(--token-color-palette-green-400)}.consul-server-card:not(.voting-status-non-voter) .health-status:not(.healthy)+dd{background-color:var(--token-color-surface-critical);color:var(--token-color-foreground-critical)}.consul-server-card .health-status+dd::before{--icon-size:icon-000;content:""}.consul-server-card .health-status.healthy+dd::before{--icon-name:icon-check}.consul-server-card .health-status:not(.healthy)+dd::before{--icon-name:icon-x}.consul-server-card{position:relative;overflow:hidden;--padding-x:24px;--padding-y:24px;padding:var(--padding-y) var(--padding-x);--tile-size:3rem}.consul-auth-method-binding-list h2,.consul-auth-method-view section h2{padding-bottom:12px}.voting-status-leader.consul-server-card .name{position:absolute!important}.consul-server-card dd:not(:last-of-type){margin-bottom:calc(var(--padding-y)/ 2)}.voting-status-leader.consul-server-card dd{margin-left:calc(var(--tile-size) + 1rem)}.consul-auth-method-list ul .locality::before{margin-right:4px}.consul-auth-method-view{margin-bottom:32px}.consul-auth-method-view section{width:100%;position:relative;overflow-y:auto}.consul-auth-method-view section table thead td{color:var(--token-color-foreground-faint)}.consul-auth-method-view section table tbody td{color:var(--token-color-hashicorp-brand)}.consul-auth-method-view section table tbody tr{cursor:default}.consul-auth-method-view section table tbody tr:hover{box-shadow:none}.consul-auth-method-view section dt{width:30%}.consul-auth-method-view section dd{width:70%}.consul-auth-method-binding-list p{margin-bottom:4px!important}.consul-auth-method-binding-list code{background-color:var(--token-color-surface-strong);padding:0 12px}.consul-auth-method-nspace-list thead td{color:var(--token-color-foreground-faint)!important}.consul-auth-method-nspace-list tbody td{color:var(--token-color-hashicorp-brand)}.consul-auth-method-nspace-list tbody tr{cursor:default}.consul-auth-method-nspace-list tbody tr:hover{box-shadow:none}.role-selector [name="role[state]"],.role-selector [name="role[state]"]+*{display:none}.role-selector [name="role[state]"]:checked+*{display:block}.topology-notices button{color:var(--token-color-foreground-action);float:right;margin-top:16px;margin-bottom:32px}#metrics-container .link a,.topology-container{color:var(--token-color-foreground-faint)}#downstream-container .topology-metrics-card:not(:last-child),#upstream-column #upstream-container:not(:last-child),#upstream-container .topology-metrics-card:not(:last-child){margin-bottom:8px}#downstream-container,#metrics-container,#upstream-container{border-radius:var(--decor-radius-100);border:1px solid;border-color:var(--token-color-surface-interactive-active)}#downstream-container,#upstream-container{background-color:var(--token-color-surface-strong);padding:12px}#downstream-container>div:first-child{display:inline-flex}#downstream-container>div:first-child span::before{background-color:var(--token-color-foreground-faint)}#metrics-container div:first-child{background-color:var(--token-color-surface-primary);padding:12px;border:none}#metrics-container .link{background-color:var(--token-color-surface-strong);padding:18px}#metrics-container .link a:hover{color:var(--token-color-foreground-action)}#downstream-lines svg path,#upstream-lines svg path{fill:transparent}#downstream-lines svg .allow-arrow,#upstream-lines svg .allow-arrow{fill:var(--token-color-palette-neutral-300);stroke-linejoin:round}#downstream-lines svg .allow-arrow,#downstream-lines svg .allow-dot,#downstream-lines svg path,#upstream-lines svg .allow-arrow,#upstream-lines svg .allow-dot,#upstream-lines svg path{stroke:var(--token-color-palette-neutral-300);stroke-width:2}#downstream-lines svg path[data-permission=empty],#downstream-lines svg path[data-permission=not-defined],#upstream-lines svg path[data-permission=empty],#upstream-lines svg path[data-permission=not-defined]{stroke-dasharray:4}#downstream-lines svg path[data-permission=deny],#upstream-lines svg path[data-permission=deny]{stroke:var(--token-color-foreground-critical)}#downstream-lines svg .deny-dot,#upstream-lines svg .deny-dot{stroke:var(--token-color-foreground-critical);stroke-width:2}#downstream-lines svg .deny-arrow,#upstream-lines svg .deny-arrow{fill:var(--token-color-foreground-critical);stroke:var(--token-color-foreground-critical);stroke-linejoin:round}.topology-notices{display:flow-root}.topology-container{display:grid;height:100%;align-items:start;grid-template-columns:2fr 1fr 2fr 1fr 2fr;grid-template-rows:50px 1fr 50px;grid-template-areas:"down-cards down-lines . up-lines up-cards" "down-cards down-lines metrics up-lines up-cards" "down-cards down-lines . up-lines up-cards"}#downstream-container{grid-area:down-cards}#downstream-lines{grid-area:down-lines;margin-left:-20px}#upstream-lines{grid-area:up-lines;margin-right:-20px}#upstream-column{grid-area:up-cards}#downstream-lines,#upstream-lines{position:relative}#metrics-container{grid-area:metrics}#metrics-container .link a::before{background-color:var(--token-color-foreground-faint);margin-right:4px}#downstream-container .topology-metrics-card,#upstream-container .topology-metrics-card{display:block;color:var(--token-color-foreground-faint);overflow:hidden;background-color:var(--token-color-surface-primary);border-radius:var(--decor-radius-100);border:1px solid;border-color:var(--token-color-surface-interactive-active)}#downstream-container .topology-metrics-card p,#upstream-container .topology-metrics-card p{padding:12px 12px 0;margin-bottom:0!important}#downstream-container .topology-metrics-card p.empty,#upstream-container .topology-metrics-card p.empty{padding:12px!important}#downstream-container .topology-metrics-card div dl,#upstream-container .topology-metrics-card div dl{display:inline-flex;margin-right:8px}#downstream-container .topology-metrics-card div dd,#upstream-container .topology-metrics-card div dd{color:var(--token-color-foreground-faint)}#downstream-container .topology-metrics-card div span,#upstream-container .topology-metrics-card div span{margin-right:8px}#downstream-container .topology-metrics-card div dt::before,#downstream-container .topology-metrics-card div span::before,#upstream-container .topology-metrics-card div dt::before,#upstream-container .topology-metrics-card div span::before{margin-right:4px}#downstream-container .topology-metrics-card div .health dt::before,#downstream-container .topology-metrics-card div .nspace dt::before,#upstream-container .topology-metrics-card div .health dt::before,#upstream-container .topology-metrics-card div .nspace dt::before{margin-top:2px}#downstream-container .topology-metrics-card div .health dt::before,#downstream-container .topology-metrics-card div .nspace dt::before,#downstream-container .topology-metrics-card div .partition dt::before,#upstream-container .topology-metrics-card div .health dt::before,#upstream-container .topology-metrics-card div .nspace dt::before,#upstream-container .topology-metrics-card div .partition dt::before{--icon-color:var(--token-color-foreground-faint)}#downstream-container .topology-metrics-card div .passing::before,#upstream-container .topology-metrics-card div .passing::before{--icon-color:var(--token-color-foreground-success)}#downstream-container .topology-metrics-card div .warning::before,#upstream-container .topology-metrics-card div .warning::before{--icon-color:var(--token-color-foreground-warning)}#downstream-container .topology-metrics-card div .critical::before,#upstream-container .topology-metrics-card div .critical::before{--icon-color:var(--token-color-foreground-critical)}#downstream-container .topology-metrics-card div .empty::before,#upstream-container .topology-metrics-card div .empty::before{--icon-color:var(--token-color-foreground-faint)}#downstream-container .topology-metrics-card .details,#upstream-container .topology-metrics-card .details{padding:0 12px 12px}#downstream-container .topology-metrics-card .details>:not(:last-child),#upstream-container .topology-metrics-card .details>:not(:last-child){padding-bottom:6px}#downstream-container .topology-metrics-card .details .group,#upstream-container .topology-metrics-card .details .group{display:grid;grid-template-columns:20px 1fr;grid-template-rows:repeat(2,1fr);grid-template-areas:"partition partition" "union namespace"}#downstream-container .topology-metrics-card .details .group span,#upstream-container .topology-metrics-card .details .group span{display:inline-block;grid-area:union;padding-left:7px;margin-right:0}#downstream-container .topology-metrics-card .details .group span::before,#upstream-container .topology-metrics-card .details .group span::before{margin-right:0;--icon-color:var(--token-color-foreground-faint)}#downstream-container .topology-metrics-card .details .group dl:first-child,#upstream-container .topology-metrics-card .details .group dl:first-child{grid-area:partition;padding-bottom:6px}#downstream-container .topology-metrics-card .details .group dl:nth-child(2),#upstream-container .topology-metrics-card .details .group dl:nth-child(2){grid-area:namespace}.topology-metrics-source-type{margin:6px 0 6px 12px;display:table}.topology-metrics-popover>button{position:absolute;transform:translate(-50%,-50%);background-color:var(--token-color-surface-primary);padding:1px}.topology-metrics-popover>button:hover{cursor:pointer}.topology-metrics-popover>button:disabled,html[data-route^="dc.nodes.show.metadata"] table tr{cursor:default}.topology-metrics-popover>button:active,.topology-metrics-popover>button:focus{outline:0}.topology-metrics-popover.deny .informed-action header::before{display:none}.topology-metrics-popover.deny .tippy-arrow::after,.topology-metrics-popover.deny>button::before{--icon-color:var(--token-color-foreground-critical)}.topology-metrics-popover.not-defined .tippy-arrow::after,.topology-metrics-popover.not-defined>button::before{--icon-color:var(--token-color-vault-brand)}#metrics-container .sparkline-wrapper svg path{stroke-width:0}#metrics-container .sparkline-wrapper .tooltip{padding:0 0 10px;border:1px solid var(--token-color-palette-neutral-300);background:#fff;border-radius:2px;box-sizing:border-box;box-shadow:var(--token-elevation-higher-box-shadow)}#metrics-container .sparkline-wrapper .tooltip .sparkline-time{padding:8px 10px;color:#000;border-bottom:1px solid var(--token-color-surface-interactive-active);margin-bottom:4px;text-align:center}#metrics-container .sparkline-wrapper .tooltip .sparkline-tt-legend,#metrics-container .sparkline-wrapper .tooltip .sparkline-tt-sum{border:0;padding:3px 10px 0}#metrics-container .sparkline-wrapper .tooltip .sparkline-tt-sum{border-top:1px solid var(--token-color-surface-interactive-active);margin-top:4px;padding:8px 10px 0}#metrics-container .sparkline-wrapper .tooltip .sparkline-tt-legend-color{width:12px;height:12px;border-radius:2px;margin:0 5px 0 0;padding:0}#metrics-container .sparkline-wrapper .tooltip .sparkline-tt-legend-value,#metrics-container .sparkline-wrapper .tooltip .sparkline-tt-sum-value{float:right}#metrics-container .sparkline-wrapper div.tooltip:before{content:"";display:block;position:absolute;width:12px;height:12px;left:15px;bottom:-7px;border:1px solid var(--token-color-palette-neutral-300);border-top:0;border-left:0;background:#fff;transform:rotate(45deg)}.sparkline-key h3::before{margin:2px 3px 0 0;font-size:var(--token-typography-body-200-font-size)}.sparkline-key h3{color:var(--token-color-foreground-strong)}.sparkline-key .sparkline-key-content dd,.sparkline-key-link{color:var(--token-color-foreground-faint)}.sparkline-key-link:hover{color:var(--token-color-foreground-action)}#metrics-container:hover .sparkline-key-link::before{margin:1px 3px 0 0;font-size:12px}#metrics-container div .sparkline-wrapper,#metrics-container div .sparkline-wrapper svg.sparkline{width:100%;height:70px;padding:0;margin:0}#metrics-container div .sparkline-wrapper{position:relative}#metrics-container div .sparkline-wrapper .tooltip{visibility:hidden;position:absolute;z-index:10;bottom:78px;width:217px}#metrics-container div .sparkline-wrapper .sparkline-tt-legend-color{display:inline-block}#metrics-container div .sparkline-wrapper .topology-metrics-error,#metrics-container div .sparkline-wrapper .topology-metrics-loader{padding-top:15px}.sparkline-key .sparkline-key-content{width:500px;min-height:100px}.sparkline-key .sparkline-key-content dl{padding:10px 0 0}.sparkline-key .sparkline-key-content dt{font-weight:var(--token-typography-font-weight-semibold);width:125px;float:left}.sparkline-key .sparkline-key-content dd{margin:0 0 12px 135px}.sparkline-key-link{visibility:hidden;float:right;margin-top:-35px;margin-right:12px}#metrics-container:hover .sparkline-key-link{visibility:visible}.topology-metrics-stats{padding:12px 12px 0;display:flex;flex-flow:row wrap;justify-content:space-between;align-items:stretch;width:100%;border-top:1px solid var(--token-color-surface-interactive-active)}.topology-metrics-stats dl{display:flex;padding-bottom:12px}.topology-metrics-stats dt{margin-right:5px;line-height:1.5em!important}.topology-metrics-stats dd{color:var(--token-color-foreground-disabled)!important}.topology-metrics-stats span{padding-bottom:12px}.topology-metrics-status-error,.topology-metrics-status-loader{color:var(--token-color-foreground-faint);text-align:center;margin:0 auto!important;display:block}.topology-metrics-status-error span::before,.topology-metrics-status-loader span::before{background-color:var(--token-color-foreground-faint)}span.topology-metrics-status-loader::after{--icon-name:var(--icon-loading);content:"";margin-left:.5rem}.consul-node-peer-info .consul-node-peer-info__name,.consul-peer-info .consul-peer-info__description{margin-left:4px}.consul-intention-list-table__meta-info{display:flex}.consul-intention-list-table__meta-info .consul-intention-list-table__meta-info__peer{display:flex;align-items:center}.consul-node-peer-info,.peerings-badge{align-items:center;display:flex}.consul-peer-search-bar .value-active span::before,.consul-peer-search-bar .value-deleting span::before,.consul-peer-search-bar .value-establishing span::before,.consul-peer-search-bar .value-failing span::before,.consul-peer-search-bar .value-pending span::before,.consul-peer-search-bar .value-terminated span::before{--icon-size:icon-000;content:""}.consul-peer-search-bar .value-active span,.consul-peer-search-bar .value-deleting span,.consul-peer-search-bar .value-establishing span,.consul-peer-search-bar .value-failing span,.consul-peer-search-bar .value-pending span,.consul-peer-search-bar .value-terminated span{font-size:var(--token-typography-body-200-font-size)}.consul-peer-search-bar .value-pending span::before{--icon-name:icon-running;--icon-color:var(--token-color-palette-neutral-600)}.consul-peer-search-bar .value-pending span{background-color:var(--token-color-consul-surface);color:var(--token-color-consul-foreground)}.consul-peer-search-bar .value-establishing span::before{--icon-name:icon-running;--icon-color:var(--token-color-palette-neutral-600)}.consul-peer-search-bar .value-establishing span{background-color:var(--token-color-palette-blue-50);color:var(--token-color-palette-blue-200)}.consul-peer-search-bar .value-active span::before{--icon-name:icon-check;--icon-color:var(--token-color-palette-green-400)}.consul-peer-search-bar .value-active span{background-color:var(--token-color-palette-green-50);color:var(--token-color-palette-green-200)}.consul-peer-search-bar .value-failing span::before{--icon-name:icon-x;--icon-color:var(--token-color-palette-red-200)}.consul-peer-search-bar .value-failing span{background-color:var(--token-color-palette-red-50);color:var(--token-color-palette-red-200)}.consul-peer-search-bar .value-terminated span::before{--icon-name:icon-x-square;--icon-color:var(--token-color-palette-neutral-600)}.consul-peer-search-bar .value-terminated span{background-color:var(--token-color-palette-neutral-200);color:var(--token-color-palette-neutral-600)}.consul-peer-search-bar .value-deleting span::before{--icon-name:icon-loading;--icon-color:var(--token-color-foreground-warning-on-surface)}.consul-peer-search-bar .value-deleting span{background-color:var(--token-color-surface-warning);color:var(--token-color-foreground-warning-on-surface)}.peers__list__peer-detail{display:flex;align-content:center;gap:18px}.border-bottom-primary{border-bottom:1px solid var(--token-color-border-primary)}.peerings-badge{justify-content:center;padding:2px 8px;border-radius:5px;gap:4px}.peerings-badge.active{background:var(--token-color-surface-success);color:var(--token-color-foreground-success)}.peerings-badge.pending{background:var(--token-color-consul-surface);color:var(--token-color-consul-brand)}.peerings-badge.establishing{background:var(--token-color-surface-action);color:var(--token-color-foreground-action)}.peerings-badge.failing{background:var(--token-color-surface-critical);color:var(--token-color-foreground-critical)}.peerings-badge.deleting{background:var(--token-color-surface-warning);color:var(--token-color-foreground-warning-on-surface)}.peerings-badge.terminated,.peerings-badge.undefined{background:var(--token-color-surface-interactive-active);color:var(--token-color-foreground-primary)}.consul-peer-info,section[data-route="dc.show.serverstatus"] .server-failure-tolerance dt{color:var(--token-color-foreground-faint)}.consul-peer-info{background:var(--token-color-surface-faint);padding:0 8px;border-radius:2px;display:flex;align-items:center}.consul-peer-form{width:416px}.consul-peer-form nav{margin-bottom:20px}.consul-peer-form-generate{width:416px;min-height:200px}.consul-peer-form-generate ol{list-style-position:outside;list-style-type:none;counter-reset:hexagonal-counter;position:relative}.consul-peer-form-generate ol::before{content:"";border-left:var(--decor-border-100);border-color:var(--token-color-palette-neutral-300);height:100%;position:absolute;left:2rem}.consul-peer-form-generate li{counter-increment:hexagonal-counter;position:relative;margin-left:60px;margin-bottom:1rem}.consul-peer-form-generate li .copyable-code{margin-top:1rem}.consul-peer-form-generate li::before{--icon-name:icon-hexagon;--icon-size:icon-600;content:"";position:absolute;z-index:2}.consul-peer-form-generate li::after{content:counter(hexagonal-counter);position:absolute;top:0;background-color:var(--token-color-palette-neutral-0);z-index:1;text-align:center}.consul-peer-form-generate li::after,.consul-peer-form-generate li::before{left:-2.4rem;width:20px;height:20px}.agentless-node-notice .hds-alert__title{display:flex;justify-content:space-between}.definition-table dt{line-height:var(--token-typography-body-300-line-height)}.app-view>div form:not(.filter-bar) [role=radiogroup] label,.modal-dialog [role=document] [role=radiogroup] label{line-height:var(--token-typography-body-100-line-height)}.app-view h1 em,.app-view>div form:not(.filter-bar) [role=radiogroup] label>em,.consul-intention-list td.destination em,.consul-intention-list td.source em,.modal-dialog [role=document] .type-password>em,.modal-dialog [role=document] .type-select>em,.modal-dialog [role=document] .type-text>em,.modal-dialog [role=document] [role=radiogroup] label>em,.modal-dialog [role=document] form button+em,.modal-dialog [role=document] table th em,.oidc-select label>em,.type-toggle>em,main .type-password>em,main .type-select>em,main .type-text>em,main form button+em,main table th em{font-style:normal}.consul-exposed-path-list>ul>li>.header :not(button),.consul-lock-session-list ul>li:not(:first-child)>.header :not(button),.consul-upstream-instance-list li>.header :not(button),.list-collection>ul>li:not(:first-child)>.header :not(button){font-size:inherit;font-weight:inherit}@media (max-width:420px) and (-webkit-min-device-pixel-ratio:0){input{font-size:var(--token-typography-body-300-font-size)!important}}#wrapper,#wrapper>footer>*,.modal-dialog>*,main>*{box-sizing:border-box}html[data-route$=create] main,html[data-route$=edit] main{max-width:1260px}fieldset [role=group]{display:flex;flex-wrap:wrap;flex-direction:row}.outlet[data-state=loading],html.ember-loading .view-loader,html:not(.has-nspaces) [class*=nspace-],html:not(.has-partitions) [class*=partition-],html[data-state=idle] .view-loader{display:none}[role=group] fieldset{width:50%}[role=group] fieldset:not(:first-of-type){padding-left:20px;border-left:1px solid;border-left:var(--token-color-foreground-faint)}[role=group] fieldset:not(:last-of-type){padding-right:20px}.app-view{margin-top:50px}@media (max-width:849px){html:not(.with-breadcrumbs) .app-view{margin-top:10px}}html body>.brand-loader{transition-property:transform,opacity;transform:translate(0,0);opacity:1}html[data-state]:not(.ember-loading) body>.brand-loader{opacity:0}@media (min-width:900px){html[data-state] body>.brand-loader{transform:translate(calc(var(--chrome-width)/ 2),0)}}html[data-route$=create] .app-view>header+div>:first-child,html[data-route$=edit] .app-view>header+div>:first-child{margin-top:1.8em}.app-view>div .container,.app-view>div .tab-section .consul-health-check-list,.app-view>div .tab-section>.search-bar+p,.app-view>div .tab-section>:first-child:not(.filter-bar):not(table){margin-top:1.25em}.consul-upstream-instance-list,html[data-route^="dc.nodes.show.sessions"] .consul-lock-session-list{margin-top:0!important}.consul-auth-method-list ul,.consul-node-list ul,.consul-nspace-list ul,.consul-peer-list ul,.consul-policy-list ul,.consul-role-list ul,.consul-service-instance-list ul,.consul-token-list ul,html[data-route="dc.services.index"] .consul-service-list ul,html[data-route^="dc.nodes.show.sessions"] .consul-lock-session-list ul{border-top-width:0!important}#wrapper{display:flex;min-height:100vh}main{padding:0 48px;position:relative;flex:1}html:not([data-route$=index]):not([data-route$=instances]) main{margin-bottom:2em}@media (max-width:849px){.actions button.copy-btn{margin-top:-56px;padding:0}}.modal-dialog [role=document] p:not(:last-child),main p:not(:last-child){margin-bottom:1em}.modal-dialog [role=document] form+div .with-confirmation,.modal-dialog [role=document] form:not(.filter-bar),main form+div .with-confirmation,main form:not(.filter-bar){margin-bottom:2em}@media (max-width:420px){main form [type=reset]{float:right;margin-right:0!important}}html[data-route^="dc.services.show"] .app-view .actions .external-dashboard{position:absolute;top:50px;right:0}html[data-route^="dc.services.instance"] .app-view>header dl{float:left;margin-top:19px;margin-bottom:23px;margin-right:50px}html[data-route^="dc.services.instance"] .app-view>header dt{font-weight:var(--token-typography-font-weight-bold)}html[data-route^="dc.services.instance"] .tab-nav{border-top:var(--decor-border-100)}html[data-route^="dc.services.instance"] .tab-section section:not(:last-child){border-bottom:var(--decor-border-100);padding-bottom:24px}html[data-route^="dc.services.instance"] .tab-nav,html[data-route^="dc.services.instance"] .tab-section section:not(:last-child){border-color:var(--token-color-surface-interactive-active)}html[data-route^="dc.services.instance.metadata"] .tab-section section h2{margin:24px 0 12px}html[data-route^="dc.kv"] .type-toggle{float:right;margin-bottom:0!important}html[data-route^="dc.kv.edit"] h2{border-bottom:var(--decor-border-200);border-color:var(--token-color-surface-interactive-active);padding-bottom:.2em;margin-bottom:.5em}html[data-route^="dc.acls.index"] main td strong{margin-right:3px}@media (max-width:420px){html[data-route^="dc.acls.create"] main header .actions,html[data-route^="dc.acls.edit"] main header .actions{float:none;display:flex;justify-content:space-between;margin-bottom:1em}html[data-route^="dc.acls.create"] main header .actions .with-feedback,html[data-route^="dc.acls.edit"] main header .actions .with-feedback{position:absolute;right:0}html[data-route^="dc.acls.create"] main header .actions .with-confirmation,html[data-route^="dc.acls.edit"] main header .actions .with-confirmation{margin-top:0}}html[data-route^="dc.intentions.edit"] .definition-table{margin-bottom:1em}section[data-route="dc.show.serverstatus"] .server-failure-tolerance{box-shadow:none;padding:var(--padding-y) var(--padding-x);max-width:770px;display:flex;flex-wrap:wrap}section[data-route="dc.show.serverstatus"] .server-failure-tolerance>header{width:100%;display:flex;flex-direction:row;justify-content:space-between;align-items:center;padding-bottom:.5rem;margin-bottom:1rem;border-bottom:var(--decor-border-100);border-color:var(--tone-border)}section[data-route="dc.show.serverstatus"] .server-failure-tolerance header em{background-color:var(--token-color-surface-interactive-active);text-transform:uppercase;font-style:normal}section[data-route="dc.show.serverstatus"] .server-failure-tolerance>section{width:50%}section[data-route="dc.show.serverstatus"] .server-failure-tolerance dl,section[data-route="dc.show.serverstatus"] .server-failure-tolerance>section{display:flex;flex-direction:column}section[data-route="dc.show.serverstatus"] .server-failure-tolerance dl{flex-grow:1;justify-content:space-between}section[data-route="dc.show.serverstatus"] .server-failure-tolerance dl.warning dd::before{--icon-name:icon-alert-circle;--icon-size:icon-800;--icon-color:var(--token-color-foreground-warning);content:"";margin-right:.5rem}section[data-route="dc.show.serverstatus"] .server-failure-tolerance section:first-of-type dl{padding-right:1.5rem}section[data-route="dc.show.serverstatus"] .server-failure-tolerance dd{display:flex;align-items:center;color:var(--token-color-hashicorp-brand)}section[data-route="dc.show.serverstatus"] .server-failure-tolerance header span::before{--icon-name:icon-info;--icon-size:icon-300;--icon-color:var(--token-color-foreground-faint);vertical-align:unset;content:""}section[data-route="dc.show.serverstatus"] section:not([class*=-tolerance]) h2{margin-top:1.5rem;margin-bottom:1.5rem}section[data-route="dc.show.serverstatus"] section:not([class*=-tolerance]) header{margin-top:18px;margin-bottom:18px}section[data-route="dc.show.serverstatus"] .redundancy-zones section header{display:flow-root}section[data-route="dc.show.serverstatus"] .redundancy-zones section header h3{float:left;margin-right:.5rem}section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl:not(.warning){background-color:var(--token-color-surface-strong)}section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl.warning{background-color:var(--token-color-border-warning);color:var(--token-color-palette-amber-400)}section[data-route="dc.show.serverstatus"] .redundancy-zones section header dl.warning::before{--icon-name:icon-alert-circle;--icon-size:icon-000;margin-right:.312rem;content:""}section[data-route="dc.show.serverstatus"] .redundancy-zones section header dt::after{content:":";display:inline-block;vertical-align:revert;background-color:transparent}section[data-route="dc.show.license"] .validity p{color:var(--token-color-foreground-faint)}section[data-route="dc.show.license"] .validity dl dt::before{content:"";margin-right:.25rem}section[data-route="dc.show.license"] .validity dl .expired::before{--icon-name:icon-x-circle;--icon-color:var(--token-color-foreground-critical)}section[data-route="dc.show.license"] .validity dl .warning::before{--icon-name:icon-alert-circle;--icon-color:var(--token-color-foreground-warning)}section[data-route="dc.show.license"] .validity dl .valid:not(.warning)::before{--icon-name:icon-check-circle;--icon-color:var(--token-color-foreground-success)}section[data-route="dc.show.license"] aside{box-shadow:none;padding:var(--padding-y) var(--padding-x);width:40%;min-width:413px;margin-top:1rem}section[data-route="dc.show.license"] aside header{margin-bottom:1rem}.prefers-reduced-motion{--icon-loading:icon-loading}@media (prefers-reduced-motion){:root{--hds-app-sidenav-animation-duration:0;--icon-loading:icon-loading}}.consul-intention-fieldsets .value->:last-child::before,.consul-intention-fieldsets .value-allow>:last-child::before,.consul-intention-fieldsets .value-deny>:last-child::before{--icon-size:icon-500;--icon-resolution:0.5}.consul-intention-fieldsets .value-allow>:last-child::before,.consul-intention-list td.intent-allow strong::before,.consul-intention-permission-list .intent-allow::before,.consul-intention-search-bar .value-allow span::before{--icon-name:icon-arrow-right;--icon-color:var(--token-color-foreground-success-on-surface)}.consul-intention-fieldsets .value-deny>:last-child::before,.consul-intention-list td.intent-deny strong::before,.consul-intention-permission-list .intent-deny::before,.consul-intention-search-bar .value-deny span::before{--icon-name:icon-skip;--icon-color:var(--token-color-foreground-critical-on-surface)}.consul-intention-fieldsets .value->:last-child::before,.consul-intention-list td.intent- strong::before,.consul-intention-search-bar .value- span::before{--icon-name:icon-layers}*{border-width:0}.animatable.tab-nav ul::after,.consul-auth-method-type,.consul-external-source,.consul-intention-action-warn-modal button.dangerous,.consul-intention-action-warn-modal button.dangerous:disabled,.consul-intention-action-warn-modal button.dangerous:focus,.consul-intention-action-warn-modal button.dangerous:hover:active,.consul-intention-action-warn-modal button.dangerous:hover:not(:disabled):not(:active),.consul-intention-list td.intent- strong,.consul-intention-permission-form button.type-submit,.consul-intention-permission-form button.type-submit:disabled,.consul-intention-permission-form button.type-submit:focus:not(:disabled),.consul-intention-permission-form button.type-submit:hover:not(:disabled),.consul-intention-search-bar .value- span,.consul-kind,.consul-source,.consul-transparent-proxy,.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:first-child,.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:focus:first-child,.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:hover:first-child,.discovery-chain .route-card>header ul li,.informed-action>ul>.dangerous>*,.informed-action>ul>.dangerous>:focus,.informed-action>ul>.dangerous>:hover,.leader,.menu-panel>ul>li.dangerous>:first-child,.menu-panel>ul>li.dangerous>:focus:first-child,.menu-panel>ul>li.dangerous>:hover:first-child,.modal-dialog [role=document]>footer,.modal-dialog [role=document]>header,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:first-child,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child,.tab-nav .selected>*,.topology-metrics-source-type,html[data-route^="dc.acls.index"] main td strong,span.policy-node-identity,span.policy-service-identity,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:first-child,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child{border-style:solid}.consul-auth-method-type,.consul-external-source,.consul-kind,.consul-source,.consul-transparent-proxy,.leader,.topology-metrics-source-type,span.policy-node-identity,span.policy-service-identity{background-color:var(--token-color-surface-strong);border-color:var(--token-color-foreground-faint);color:var(--token-color-foreground-primary)}.consul-intention-list td.intent- strong,.consul-intention-search-bar .value- span,.discovery-chain .route-card>header ul li,.modal-dialog [role=document]>footer,.modal-dialog [role=document]>header,html[data-route^="dc.acls.index"] main td strong{background-color:var(--token-color-surface-strong);border-color:var(--token-color-palette-neutral-300);color:var(--token-color-foreground-strong)}.animatable.tab-nav ul::after,.consul-intention-permission-form button.type-submit,.consul-intention-permission-form button.type-submit:disabled,.tab-nav .selected>*{background-color:var(--token-color-surface-primary);border-color:var(--token-color-foreground-action);color:var(--token-color-foreground-action)}.consul-intention-permission-form button.type-submit:focus:not(:disabled),.consul-intention-permission-form button.type-submit:hover:not(:disabled){background-color:var(--token-color-surface-action);border-color:var(--token-color-foreground-action);color:var(--token-color-palette-blue-500)}.consul-intention-action-warn-modal button.dangerous,.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:first-child,.informed-action>ul>.dangerous>*,.menu-panel>ul>li.dangerous>:first-child,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:first-child,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:first-child{background-color:transparent;border-color:var(--token-color-foreground-critical);color:var(--token-color-foreground-critical)}.consul-intention-action-warn-modal button.dangerous:disabled{background-color:var(--token-color-border-critical);border-color:var(--token-color-foreground-disabled);color:var(--token-color-surface-primary)}.consul-intention-action-warn-modal button.dangerous:focus,.consul-intention-action-warn-modal button.dangerous:hover:not(:disabled):not(:active),.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:focus:first-child,.disclosure-menu [aria-expanded]~*>ul>li.dangerous>:hover:first-child,.informed-action>ul>.dangerous>:focus,.informed-action>ul>.dangerous>:hover,.menu-panel>ul>li.dangerous>:focus:first-child,.menu-panel>ul>li.dangerous>:hover:first-child,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,.more-popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,.popover-menu>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,table.has-actions tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:focus:first-child,table.with-details tr>.actions>[type=checkbox]+label+div>ul>li.dangerous>:hover:first-child{background-color:var(--token-color-foreground-critical);border-color:var(--token-color-foreground-critical-high-contrast);color:var(--token-color-surface-primary)}.consul-intention-action-warn-modal button.dangerous:hover:active{background-color:var(--token-color-palette-red-400);border-color:var(--token-color-foreground-critical-high-contrast);color:var(--token-color-surface-primary)}.hover\:scale-125:hover{--tw-scale-x:1.25;--tw-scale-y:1.25}.group:hover .group-hover\:opacity-100{opacity:1} \ No newline at end of file diff --git a/agent/uiserver/dist/assets/consul-ui/routes-e47ed633758c4b43c40de4ae84cdf564.js b/agent/uiserver/dist/assets/consul-ui/routes-447282731d763ac6ad60d55ed447a4a6.js similarity index 97% rename from agent/uiserver/dist/assets/consul-ui/routes-e47ed633758c4b43c40de4ae84cdf564.js rename to agent/uiserver/dist/assets/consul-ui/routes-447282731d763ac6ad60d55ed447a4a6.js index c63b984723..9c841e4bfc 100644 --- a/agent/uiserver/dist/assets/consul-ui/routes-e47ed633758c4b43c40de4ae84cdf564.js +++ b/agent/uiserver/dist/assets/consul-ui/routes-447282731d763ac6ad60d55ed447a4a6.js @@ -1 +1 @@ -((e,t=("undefined"!=typeof document?document.currentScript.dataset:module.exports))=>{t.routes=JSON.stringify(e)})({dc:{_options:{path:"/:dc"},index:{_options:{path:"/",redirect:"../services"}},show:{_options:{path:"/overview",abilities:["access overview"]},serverstatus:{_options:{path:"/server-status",abilities:["read servers"]}},cataloghealth:{_options:{path:"/catalog-health",abilities:["access overview"]}},license:{_options:{path:"/license",abilities:["read license"]}}},services:{_options:{path:"/services"},index:{_options:{path:"/",queryParams:{sortBy:"sort",status:"status",source:"source",kind:"kind",searchproperty:{as:"searchproperty",empty:[["Partition","Name","Tags","PeerName"]]},search:{as:"filter",replace:!0}}}},show:{_options:{path:"/:name"},instances:{_options:{path:"/instances",queryParams:{sortBy:"sort",status:"status",source:"source",searchproperty:{as:"searchproperty",empty:[["Name","Node","Tags","ID","Address","Port","Service.Meta","Node.Meta"]]},search:{as:"filter",replace:!0}}}},intentions:{_options:{path:"/intentions"},index:{_options:{path:"",queryParams:{sortBy:"sort",access:"access",searchproperty:{as:"searchproperty",empty:[["SourceName","DestinationName"]]},search:{as:"filter",replace:!0}}}},edit:{_options:{path:"/:intention_id"}},create:{_options:{template:"../edit",path:"/create"}}},topology:{_options:{path:"/topology"}},services:{_options:{path:"/services",queryParams:{sortBy:"sort",instance:"instance",searchproperty:{as:"searchproperty",empty:[["Name","Tags"]]},search:{as:"filter",replace:!0}}}},upstreams:{_options:{path:"/upstreams",queryParams:{sortBy:"sort",instance:"instance",searchproperty:{as:"searchproperty",empty:[["Name","Tags"]]},search:{as:"filter",replace:!0}}}},routing:{_options:{path:"/routing"}},tags:{_options:{path:"/tags"}}},instance:{_options:{path:"/:name/instances/:node/:id",redirect:"./healthchecks"},healthchecks:{_options:{path:"/health-checks",queryParams:{sortBy:"sort",status:"status",check:"check",searchproperty:{as:"searchproperty",empty:[["Name","Node","CheckID","Notes","Output","ServiceTags"]]},search:{as:"filter",replace:!0}}}},upstreams:{_options:{path:"/upstreams",queryParams:{sortBy:"sort",search:{as:"filter",replace:!0},searchproperty:{as:"searchproperty",empty:[["DestinationName","LocalBindAddress","LocalBindPort"]]}}}},exposedpaths:{_options:{path:"/exposed-paths"}},addresses:{_options:{path:"/addresses"}},metadata:{_options:{path:"/metadata"}}},notfound:{_options:{path:"/:name/:node/:id"}}},nodes:{_options:{path:"/nodes"},index:{_options:{path:"",queryParams:{sortBy:"sort",status:"status",version:"version",searchproperty:{as:"searchproperty",empty:[["Node","Address","Meta","PeerName"]]},search:{as:"filter",replace:!0}}}},show:{_options:{path:"/:name"},healthchecks:{_options:{path:"/health-checks",queryParams:{sortBy:"sort",status:"status",kind:"kind",check:"check",searchproperty:{as:"searchproperty",empty:[["Name","Service","CheckID","Notes","Output","ServiceTags"]]},search:{as:"filter",replace:!0}}}},services:{_options:{path:"/service-instances",queryParams:{sortBy:"sort",status:"status",source:"source",searchproperty:{as:"searchproperty",empty:[["Name","Tags","ID","Address","Port","Service.Meta"]]},search:{as:"filter",replace:!0}}}},rtt:{_options:{path:"/round-trip-time"}},metadata:{_options:{path:"/metadata"}}}},intentions:{_options:{path:"/intentions"},index:{_options:{path:"/",queryParams:{sortBy:"sort",access:"access",searchproperty:{as:"searchproperty",empty:[["SourceName","DestinationName"]]},search:{as:"filter",replace:!0}}}},edit:{_options:{path:"/:intention_id",abilities:["read intentions"]}},create:{_options:{template:"../edit",path:"/create",abilities:["create intentions"]}}},kv:{_options:{path:"/kv"},index:{_options:{path:"/",queryParams:{sortBy:"sort",kind:"kind",search:{as:"filter",replace:!0}}}},folder:{_options:{template:"../index",path:"/*key"}},edit:{_options:{path:"/*key/edit"}},create:{_options:{template:"../edit",path:"/*key/create",abilities:["create kvs"]}},"root-create":{_options:{template:"../edit",path:"/create",abilities:["create kvs"]}}},acls:{_options:{path:"/acls",abilities:["access acls"]},policies:{_options:{path:"/policies",abilities:["read policies"]},edit:{_options:{path:"/:id"}},create:{_options:{path:"/create",abilities:["create policies"]}}},roles:{_options:{path:"/roles",abilities:["read roles"]},edit:{_options:{path:"/:id"}},create:{_options:{path:"/create",abilities:["create roles"]}}},tokens:{_options:{path:"/tokens",abilities:["access acls"]},edit:{_options:{path:"/:id"}},create:{_options:{path:"/create",abilities:["create tokens"]}}},"auth-methods":{_options:{path:"/auth-methods",abilities:["read auth-methods"]},show:{_options:{path:"/:id"},"auth-method":{_options:{path:"/auth-method"}},"binding-rules":{_options:{path:"/binding-rules"}},"nspace-rules":{_options:{path:"/nspace-rules"}}}}},"routing-config":{_options:{path:"/routing-config/:name"}}},index:{_options:{path:"/"}},settings:{_options:{path:"/settings"}},setting:{_options:{path:"/setting",redirect:"../settings"}},notfound:{_options:{path:"/*notfound"}}}) +((e,t=("undefined"!=typeof document?document.currentScript.dataset:module.exports))=>{t.routes=JSON.stringify(e)})({dc:{_options:{path:"/:dc"},index:{_options:{path:"/",redirect:"../services"}},show:{_options:{path:"/overview",abilities:["access overview"]},serverstatus:{_options:{path:"/server-status",abilities:["read servers"]}},cataloghealth:{_options:{path:"/catalog-health",abilities:["access overview"]}},license:{_options:{path:"/license",abilities:["read license"]}}},services:{_options:{path:"/services"},index:{_options:{path:"/",queryParams:{sortBy:"sort",status:"status",source:"source",kind:"kind",searchproperty:{as:"searchproperty",empty:[["Partition","Name","Tags","PeerName"]]},search:{as:"filter",replace:!0}}}},show:{_options:{path:"/:name"},instances:{_options:{path:"/instances",queryParams:{sortBy:"sort",status:"status",source:"source",searchproperty:{as:"searchproperty",empty:[["Name","Node","Tags","ID","Address","Port","Service.Meta","Node.Meta"]]},search:{as:"filter",replace:!0}}}},intentions:{_options:{path:"/intentions"},index:{_options:{path:"",queryParams:{sortBy:"sort",access:"access",searchproperty:{as:"searchproperty",empty:[["SourceName","DestinationName"]]},search:{as:"filter",replace:!0}}}},edit:{_options:{path:"/:intention_id"}},create:{_options:{template:"../edit",path:"/create"}}},topology:{_options:{path:"/topology"}},services:{_options:{path:"/services",queryParams:{sortBy:"sort",instance:"instance",searchproperty:{as:"searchproperty",empty:[["Name","Tags"]]},search:{as:"filter",replace:!0}}}},upstreams:{_options:{path:"/upstreams",queryParams:{sortBy:"sort",instance:"instance",searchproperty:{as:"searchproperty",empty:[["Name","Tags"]]},search:{as:"filter",replace:!0}}}},routing:{_options:{path:"/routing"}},tags:{_options:{path:"/tags"}}},instance:{_options:{path:"/:name/instances/:node/:id",redirect:"./healthchecks"},healthchecks:{_options:{path:"/health-checks",queryParams:{sortBy:"sort",status:"status",check:"check",searchproperty:{as:"searchproperty",empty:[["Name","Node","CheckID","Notes","Output","ServiceTags"]]},search:{as:"filter",replace:!0}}}},upstreams:{_options:{path:"/upstreams",queryParams:{sortBy:"sort",search:{as:"filter",replace:!0},searchproperty:{as:"searchproperty",empty:[["DestinationName","LocalBindAddress","LocalBindPort"]]}}}},exposedpaths:{_options:{path:"/exposed-paths"}},addresses:{_options:{path:"/addresses"}},metadata:{_options:{path:"/metadata"}}},notfound:{_options:{path:"/:name/:node/:id"}}},nodes:{_options:{path:"/nodes"},index:{_options:{path:"",queryParams:{sortBy:"sort",status:"status",version:"version",searchproperty:{as:"searchproperty",empty:[["Node","Address","Meta","PeerName"]]},search:{as:"filter",replace:!0}}}},show:{_options:{path:"/:name"},healthchecks:{_options:{path:"/health-checks",queryParams:{sortBy:"sort",status:"status",kind:"kind",check:"check",searchproperty:{as:"searchproperty",empty:[["Name","Service","CheckID","Notes","Output","ServiceTags"]]},search:{as:"filter",replace:!0}}}},services:{_options:{path:"/service-instances",queryParams:{sortBy:"sort",status:"status",source:"source",searchproperty:{as:"searchproperty",empty:[["Name","Tags","ID","Address","Port","Service.Meta"]]},search:{as:"filter",replace:!0}}}},rtt:{_options:{path:"/round-trip-time"}},metadata:{_options:{path:"/metadata"}}}},intentions:{_options:{path:"/intentions"},index:{_options:{path:"/",queryParams:{sortBy:"sort",access:"access",searchproperty:{as:"searchproperty",empty:[["SourceName","DestinationName"]]},search:{as:"filter",replace:!0}}}},edit:{_options:{path:"/:intention_id",abilities:["read intentions"]}},create:{_options:{template:"../edit",path:"/create",abilities:["create intentions"]}}},kv:{_options:{path:"/kv"},index:{_options:{path:"/",queryParams:{sortBy:"sort",kind:"kind",search:{as:"filter",replace:!0}}}},folder:{_options:{template:"../index",path:"/*key"}},edit:{_options:{path:"/*key/edit"}},create:{_options:{template:"../edit",path:"/*key/create",abilities:["create kvs"]}},"root-create":{_options:{template:"../edit",path:"/create",abilities:["create kvs"]}}},acls:{_options:{path:"/acls",abilities:["access acls"]},policies:{_options:{path:"/policies",abilities:["read policies"]},edit:{_options:{path:"/:id"}},create:{_options:{path:"/create",abilities:["create policies"]}}},roles:{_options:{path:"/roles",abilities:["read roles"]},edit:{_options:{path:"/:id"}},create:{_options:{path:"/create",abilities:["create roles"]}}},tokens:{_options:{path:"/tokens",abilities:["access acls"]},edit:{_options:{path:"/:id"}},create:{_options:{path:"/create",abilities:["create tokens"]}}},"auth-methods":{_options:{path:"/auth-methods",abilities:["read auth-methods"]},show:{_options:{path:"/:id"},"auth-method":{_options:{path:"/auth-method"}},"binding-rules":{_options:{path:"/binding-rules"}},"nspace-rules":{_options:{path:"/nspace-rules"}}}}},"routing-config":{_options:{path:"/routing-config/:name"}}},index:{_options:{path:"/"}},settings:{_options:{path:"/settings"}},setting:{_options:{path:"/setting",redirect:"../settings"}},unavailable:{_options:{path:"/unavailable"}},notfound:{_options:{path:"/*notfound"}}}) diff --git a/agent/uiserver/dist/index.html b/agent/uiserver/dist/index.html index 438fc074ed..18dbc01051 100644 --- a/agent/uiserver/dist/index.html +++ b/agent/uiserver/dist/index.html @@ -13,16 +13,16 @@ - + - - - + + @@ -63,7 +63,7 @@ } - + @@ -81,20 +81,16 @@ {{if .NamespacesEnabled}} {{end}} -{{if .HCPEnabled}} - - -{{end}} - + {{ range .ExtraScripts }} {{ end }} - + diff --git a/agent/uiserver/ui_template_data.go b/agent/uiserver/ui_template_data.go index 34d3a453b0..726207b148 100644 --- a/agent/uiserver/ui_template_data.go +++ b/agent/uiserver/ui_template_data.go @@ -31,14 +31,6 @@ func uiTemplateDataFromConfig(cfg *config.RuntimeConfig) (map[string]interface{} uiCfg["metrics_provider_options"] = json.RawMessage(cfg.UIConfig.MetricsProviderOptionsJSON) } - v2CatalogEnabled := false - for _, experiment := range cfg.Experiments { - if experiment == "resource-apis" { - v2CatalogEnabled = true - break - } - } - d := map[string]interface{}{ "ContentPath": cfg.UIConfig.ContentPath, "ACLsEnabled": cfg.ACLsEnabled, @@ -47,7 +39,6 @@ func uiTemplateDataFromConfig(cfg *config.RuntimeConfig) (map[string]interface{} "LocalDatacenter": cfg.Datacenter, "PrimaryDatacenter": cfg.PrimaryDatacenter, "PeeringEnabled": cfg.PeeringEnabled, - "V2CatalogEnabled": v2CatalogEnabled, } // Also inject additional provider scripts if needed, otherwise strip the diff --git a/agent/uiserver/uiserver_test.go b/agent/uiserver/uiserver_test.go index d86baf1f48..c1e21ce745 100644 --- a/agent/uiserver/uiserver_test.go +++ b/agent/uiserver/uiserver_test.go @@ -13,10 +13,11 @@ import ( "strings" "testing" - "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" "golang.org/x/net/html" + "github.com/hashicorp/go-hclog" + "github.com/hashicorp/consul/agent/config" "github.com/hashicorp/consul/sdk/testutil" ) @@ -51,8 +52,7 @@ func TestUIServerIndex(t *testing.T) { "metrics_provider": "", "metrics_proxy_enabled": false, "dashboard_url_templates": null - }, - "V2CatalogEnabled": false + } }`, }, { @@ -91,8 +91,7 @@ func TestUIServerIndex(t *testing.T) { }, "metrics_proxy_enabled": false, "dashboard_url_templates": null - }, - "V2CatalogEnabled": false + } }`, }, { @@ -113,8 +112,7 @@ func TestUIServerIndex(t *testing.T) { "metrics_provider": "", "metrics_proxy_enabled": false, "dashboard_url_templates": null - }, - "V2CatalogEnabled": false + } }`, }, { @@ -135,30 +133,7 @@ func TestUIServerIndex(t *testing.T) { "metrics_provider": "", "metrics_proxy_enabled": false, "dashboard_url_templates": null - }, - "V2CatalogEnabled": false - }`, - }, - { - name: "v2 catalog enabled", - cfg: basicUIEnabledConfig(withV2CatalogEnabled()), - path: "/", - wantStatus: http.StatusOK, - wantContains: []string{" - `-ui-content-path` ((#\_ui\_content\_path)) - This flag provides the option to change the path the Consul UI loads from and will be displayed in the browser. By default, the path is `/ui/`, for example `http://localhost:8500/ui/`. Only alphanumerics, - `-`, and `_` are allowed in a custom path.`/v1/` is not allowed as it would overwrite + `-`, and `_` are allowed in a custom path. `/v1/` is not allowed as it would overwrite the API endpoint. - +{/* list of reference-style links */} [go-sockaddr]: https://godoc.org/github.com/hashicorp/go-sockaddr/template diff --git a/website/content/docs/agent/index.mdx b/website/content/docs/agent/index.mdx index b5a06b39e6..468e9087c2 100644 --- a/website/content/docs/agent/index.mdx +++ b/website/content/docs/agent/index.mdx @@ -382,8 +382,8 @@ The `client_addr` configuration specifies IP addresses used for HTTP, HTTPS, DNS ```hcl -node_name = "consul-server" -server = true +node_name = "consul-client" +server = false bootstrap = true ui_config { enabled = true @@ -405,8 +405,8 @@ advertise_addr = "{{ GetInterfaceIP \"en0\" }}" ```json { - "node_name": "consul-server", - "server": true, + "node_name": "consul-client", + "server": false, "bootstrap": true, "ui_config": { "enabled": true diff --git a/website/content/docs/agent/monitor/alerts.mdx b/website/content/docs/agent/monitor/alerts.mdx new file mode 100644 index 0000000000..d8dcc90247 --- /dev/null +++ b/website/content/docs/agent/monitor/alerts.mdx @@ -0,0 +1,83 @@ +--- +layout: docs +page_title: Consul monitoring and alerts recommendations +description: >- + Apply best practices towards Consul monitoring and alerts. +--- + +# Consul monitoring and alerts recommendations + +This document will guide you through which host resources to monitor and how monitoring tools can help you set up alerts to notify you when your Consul cluster exceeds its limits. By monitoring Consul and setting up alerts, you can ensure Consul works as expected for all your service discovery and service mesh needs. + +## Instance level monitoring + +While each host environment and Consul deployment is unique, these recommendations can serve as a starting point for you to reference to meet the unique needs of your deployment. + +A Consul datacenter is the smallest unit of Consul infrastructure that can perform basic Consul operations like service discovery or service mesh. A datacenter contains at least one Consul server agent, but a real-world deployment contains three or five server agents and several Consul client agents. + +Consul server agents store all state information, including service and node IP addresses, health checks, and configuration. Consul clients report node and service health status to the Consul cluster. In a typical deployment, you must run client agents on every compute node in your datacenter. If you have Kubernetes workloads, you can also run Consul with an alternate service mesh configuration that deploys Envoy proxies but not client agents. Refer to [Simplified service mesh with Consul dataplanes](/consul/docs/connect/dataplane) for more information. + +We recommend monitoring the following parameters for Consul agents health: +- Disk space and file handles +- [RAM utilization](/consul/docs/agent/telemetry#memory-usage) +- CPU utilization +- Network activity and utilization + +We recommend using an [application performance monitoring (APM) system](#monitoring-tools) to track these metrics. For a full list of key metrics, visit the [Key metrics](/consul/docs/agent/telemetry#key-metrics) section of Telemetry documentation. + +## Recommendations for host-level alerts + +We recommend starting with a small cluster for most initial production deployments or for testing environments. For production environments with a consistently high workload, we recommend large clusters . Refer to the [Consul capacity planning](/well-architected-framework/reliability/reliability-consul-capacity-planning#minimum-hardware-requirements) article for more information. + +When collecting metrics, it is important to establish a baseline. This baseline ensures your Consul deployment is healthy, and serves as a reference point when troubleshooting abnormal Cluster behavior. Complete the [Monitor Consul datacenter health](/consul/tutorials/day-2-operations/monitor-datacenter-health#how-to-collect-metrics) tutorial to learn how to collect metrics. + +Once you have established a baseline for your metrics, use them and the following recommendations to configure reasonable alerts for your Consul agent. + +### Memory alert recommendations + +Consul uses RAM as the primary storage for data on its leader node, while periodically flushing it to disk. Reference the [Memory usage](/consul/docs/agent/telemetry#memory-usage) section of the Telemetry documentation for more details. The recommended instance type depends on your hosting provider. Refer to the [Hardware sizing for Consul servers](/consul/tutorials/production-deploy/reference-architecture#hardware-sizing-for-consul-servers) for recommended instance types for most cloud providers along with other up-to-date hardware recommendations. + +When determining how much RAM you should allocate, we recommend enough RAM for your server agents to contain between 2 to 4 times the working set size. You can determine the working set size by noting the value of `consul.runtime.alloc_bytes` in the telemetry data. + +Set up an alert if your RAM usage exceeds a reasonable threshold (for example, 90% of your allocated RAM). + +### CPU alert recommendations + +Your Consul servers should scale up to handle peak CPU load, not idle load. When idle, Consul servers are waiting to react to changes in service health, placement, or other configuration. If there are any service state changes, the Consul server has to notify all impacted Consul clients simultaneously. For example, if the Consul server has to notify hundreds or thousands of Consul clients of a service state update, the Consul server CPU may spike. + +If this happens, your monitoring dashboard will show a CPU spike on all servers immediately after a big registration/deregistration operation. This should not happen — you should be able to do a rollout or other high-change operation without taxing the Consul servers. + +Set up an alert to detect CPU spikes on your Consul server agents. When this happens, evaluate the size of your Consul servers and upgrade them accordingly. + +### Network recommendations + +The data sent between all Consul agents must follow latency requirements for total round trip time (RTT): + +Average RTT for all traffic cannot exceed 50ms. +RTT for 99 percent of traffic cannot exceed 100ms. + +Refer to the [Reference architecture](/consul/tutorials/production-deploy/reference-architecture#network-latency-and-bandwidth) to learn more about network latency and bandwidth guidance. + +Set an alert to detect when the RTT exceeds these values. When this happens, Therefore, you should monitor metrics related to the host's network latency so the RTT does not exceed these values. + +### Monitoring Consul using Prometheus and Grafana + +Time series based observability tools, such as Grafana and Prometheus, help you monitor the health of Consul clusters over long intervals of time. Refer to the +[Monitoring for Layer 7 observability with Prometheus, Grafana, and Kubernetes](/consul/tutorials/day-2-operations/kubernetes-layer7-observability) tutorial for additional information. + +### Monitoring Consul using Datadog + +Datadog is a SaaS-based monitoring and analytics platform for large-scale applications and infrastructure. It is one of the supported platforms for monitoring Consul. Datadogs agents run on your host reporting logs, metrics and traces. By configuring Datadog agents on your Consul server and client instances, you can monitor your Consul cluster's health. + +Refer to the following resources for more information: + +- [Setup Consul logging with DataDog](https://www.datadoghq.com/blog/consul-datadog/) +- [Datadog monitoring solutions brief](https://www.datocms-assets.com/2885/1576713622-datadog-consul.pdf) +- [Hashicorp partner portal for Consul support on Datadog](https://www.hashicorp.com/partners/tech/datadog#consul) + +## Next steps + +In this guide, you learned which host resources to monitor and how monitoring tools can help you set up alerts to notify you when your Consul cluster exceeds its limits. + +- To learn about monitoring the Consul control and data plane, visit our [Monitoring Consul components](/well-architected-framework/reliability/reliability-consul-monitoring-consul-components) documentation. +- Complete the [Monitor Consul datacenter health with Telegraf](/consul/tutorials/day-2-operations/monitor-health-telegraf) tutorial for additional metrics and alerting recommendations. diff --git a/website/content/docs/agent/monitor/components.mdx b/website/content/docs/agent/monitor/components.mdx new file mode 100644 index 0000000000..1c3d49270e --- /dev/null +++ b/website/content/docs/agent/monitor/components.mdx @@ -0,0 +1,121 @@ +--- +layout: docs +page_title: Monitoring Consul components +description: >- + Apply best practices monitoring your Consul control and data plane. +--- + +# Monitoring Consul components + +This document will guide you recommendations for monitoring your Consul control and data plane. By keeping track of these components and setting up alerts, you can better maintain the overall health and resilience of your service mesh. + +## Background + +A Consul datacenter is the smallest unit of Consul infrastructure that can perform basic Consul operations like service discovery or service mesh. A datacenter contains at least one Consul server agent, but a real-world deployment contains three or five server agents and several Consul client agents. + +The Consul control plane consists of server agents that store all state information, including service and node IP addresses, health checks, and configuration. In addition, the control plane is responsible for securing the mesh, facilitating service discovery, health checking, policy enforcement, and other similar operational concerns. In addition, the control plane contains client agents that report node and service health status to the Consul cluster. In a typical deployment, you must run client agents on every compute node in your datacenter. + +The Consul data plane consists of proxies deployed locally alongside each service instance. These proxies, called sidecar proxies, receive mesh configuration data from the control plane, and control network communication between their local service instance and other services in the network. The sidecar proxy handles inbound and outbound service connections, and ensures TLS connections between services are both verified and encrypted. + +If you have Kubernetes workloads, you can also run Consul with an alternate service mesh configuration that deploys Envoy proxies but not client agents. Refer to [Simplified service mesh with Consul dataplanes](/consul/docs/connect/dataplane) for more information. + +## Consul control plane monitoring + +The Consul control plane consists of the following components: + +- RPC Communication between Consul servers and clients. +- Data plane routing instructions for the Envoy Layer 7 proxy. +- Serf Traffic: LAN and WAN +- Consul cluster peering and server federation + +It is important to monitor and establish baseline and alert thresholds for Consul control plane components for abnormal behavior detection. Note that these alerts can also be triggered by some planned events like Consul cluster upgrades, configuration changes, or leadership change. + +To help monitor your Consul control plane, we recommend to establish a baseline and standard deviation for the following: + +- [Server health](/consul/docs/agent/telemetry#server-health) +- [Leadership changes](/consul/docs/agent/telemetry#leadership-changes) +- [Key metrics](/consul/docs/agent/telemetry#key-metrics) +- [Autopilot](/consul/docs/agent/telemetry#autopilot) +- [Network activity](/consul/docs/agent/telemetry#network-activity-rpc-count) +- [Certificate authority expiration](/consul/docs/agent/telemetry#certificate-authority-expiration) + +It is important to have a highly performant network with low network latency. Ensure network latency for gossip in all datacenters are within the 8ms latency budget for all Consul agents. View the [Production server requirements](/consul/docs/install/performance#production-server-requirements) for more information. + +### Raft recommendations + +Consul uses [Raft for consensus protocol](/consul/docs/architecture/consensus). High saturation of the Raft goroutines can lead to elevated latency in the rest of the system and may cause the Consul cluster to be unstable. As a result, it is important to monitor Raft to track your control plane health. We recommend the following actions to keep control plane healthy: +- Create an alert that notifies you when [Raft thread saturation](/consul/docs/agent/telemetry#raft-thread-saturation) exceeds 50%. +- Monitor [Raft replication capacity](/consul/docs/agent/telemetry#raft-replication-capacity-issues) when Consul is handling large amounts of data and high write throughput. +- Lower [`raft_multiplier`](/consul/docs/install/performance#production) to keep your Consul cluster stable. The value of `raft_multiplier` defines the scaling factor for Consul. Default value for raft_multiplier is 5. + + A short multiplier minimizes failure detection and election time but may trigger frequently in high latency situations. This can cause constant leadership churn and associated unavailability. A high multiplier reduces the chances that spurious failures will cause leadership churn but it does this at the expense of taking longer to detect real failures and thus takes longer to restore Consul cluster availability. + + Wide networks with higher latency will perform better with larger `raft_multiplier` values. + +Raft uses BoltDB for storing data and maintaining its own state. Refer to the [Bolt DB performance metrics](/consul/docs/agent/telemetry#bolt-db-performance) when you are troubleshooting Raft performance issues. + +## Consul data plane monitoring + +The data plane of Consul consists of Consul clients or [Connect proxies](/consul/docs/connect/proxies) interacting with each other through service-to-service communication. Service-to-service traffic always stays within the data plane, while the control plane only enforces traffic rules. Monitoring service-to-service communication is important but may become extremely complex in an enterprise setup with multiple services communicating to each other across federated Consul clusters through mesh, ingress and terminating gateways. + +### Service monitoring + +You can extract the following service-related information: + +- Use the [`catalog`](/consul/commands/catalog) command or the Consul UI to query all registered services in a Consul datacenter. +- Use the [`/agent/service/:service_id`](/consul/api-docs/agent/service#get-service-configuration) API endpoint to query individual services. Connect proxies use this endpoint to discover embedded configuration. + +### Proxy monitoring + +Envoy is the supported Connect proxy for Consul service mesh. For virtual machines (VMs), Envoy starts as a sidecar service process. For Kubernetes, Envoy starts as a sidecar container in a Kubernetes service pod. +Refer to the [Supported Envoy versions](/consul/docs/connect/proxies/envoy#supported-versions) documentation to find the compatible Envoy versions for your version of Consul. + +For troubleshooting service mesh issues, set Consul logs to `trace` or `debug`. The following example annotation sets Envoy logging to `debug`. + +```yaml +annotations: + consul.hashicorp.com/envoy-extra-args: '--log-level debug --disable-hot-restart' +``` + +Refer to the [Enable logging on Envoy sidecar pods](/consul/docs/k8s/annotations-and-labels#consul-hashicorp-com-envoy-extra-args) documentation for more information. + +#### Envoy Admin Interface + +To troubleshoot service-to-service communication issues, monitor Envoy host statistics. Envoy exposes a local administration interface that can be used to query and modify different aspects of the server on port `19000` by default. Envoy also exposes a public listener port to receive mTLS connections from other proxies in the mesh on port `20000` by default. + +All endpoints exposed by Envoy are available at the node running Envoy on port `19000`. The node can either be a pod in Kubernetes or VM running Consul Service Mesh. For example, if you forward the Envoy port to your local machine, you can access the Envoy admin interface at `http://localhost:19000/`. + +The following Envoy admin interface endpoints are particularly useful: + +- The `listeners` endpoint lists all listeners running on `localhost`. This allows you to confirm whether the upstream services are binding correctly to Envoy. + +```shell-session +$ curl http://localhost:19000/listeners +public_listener:192.168.19.168:20000::192.168.19.168:20000 +Outbound_listener:127.0.0.1:15001::127.0.0.1:15001 +``` + +- The `/clusters` endpoint displays information about the xDS clusters, such as service requests and mTLS related data. The following example shows a truncated output. + +```shell-session +$ http://localhost:19000/clusters +`local_app::observability_name::local_app +local_app::default_priority::max_connections::1024 +local_app::default_priority::max_pending_requests::1024 +local_app::default_priority::max_requests::1024 +local_app::default_priority::max_retries::3 +local_app::high_priority::max_connections::1024 +local_app::high_priority::max_pending_requests::1024 +local_app::high_priority::max_requests::1024 +local_app::high_priority::max_retries::3 +local_app::added_via_api::true +## ... +``` + +Visit the main admin interface (`http://localhost:19000`) to find the full list of possible Consul admin endpoints. Refer to the [Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/operations/admin) for more information. + +## Next steps + +In this guide, you learned recommendations for monitoring your Consul control and data plane. + +To learn about monitoring the Consul host and instance resources, visit our [Monitoring best practices](/well-architected-framework/reliability/reliability-monitoring-service-to-service-communication-with-envoy) documentation. diff --git a/website/content/docs/agent/telemetry.mdx b/website/content/docs/agent/monitor/telemetry.mdx similarity index 100% rename from website/content/docs/agent/telemetry.mdx rename to website/content/docs/agent/monitor/telemetry.mdx diff --git a/website/content/docs/agent/wal-logstore/index.mdx b/website/content/docs/agent/wal-logstore/index.mdx index 77dc1dd058..b215db158c 100644 --- a/website/content/docs/agent/wal-logstore/index.mdx +++ b/website/content/docs/agent/wal-logstore/index.mdx @@ -32,7 +32,7 @@ To mitigate risks associated with sudden bursts of log data, Consul tries to lim But the larger the file, the more likely it is to have a large freelist or suddenly form one after a burst of writes. For this reason, the many of Consul's default options associated with snapshots, truncating logs, and keeping the log history aggressively keep BoltDT small rather than using disk IO more efficiently. -Other reliability issues, such as [raft replication capacity issues](/consul/docs/agent/telemetry#raft-replication-capacity-issues), are much simpler to solve without the performance concerns caused by storing more logs in BoltDB. +Other reliability issues, such as [raft replication capacity issues](/consul/docs/agent/monitor/telemetry#raft-replication-capacity-issues), are much simpler to solve without the performance concerns caused by storing more logs in BoltDB. ### WAL approaches storage issues differently diff --git a/website/content/docs/architecture/capacity-planning.mdx b/website/content/docs/architecture/capacity-planning.mdx new file mode 100644 index 0000000000..2f80c4cf28 --- /dev/null +++ b/website/content/docs/architecture/capacity-planning.mdx @@ -0,0 +1,188 @@ +--- +layout: docs +page_title: Consul capacity planning +description: >- + Learn how to maintain your Consul cluster in a healthy state by provisioning the correct resources. +--- + +# Consul capacity planning + +This page describes our capacity planning recommendations when deploying and maintaining a Consul cluster in production. When your organization designs a production environment, you should consider your available resources and their impact on network capacity. + +## Introduction + +It is important to select the correct size for your server instances. Consul server environments have a standard set of minimum requirements. However, these requirements may vary depending on what you are using Consul for. + +Insufficient resource allocations may cause network issues or degraded performance in general. When a slowdown in performance results in a Consul leader node that is unable to respond to requests in sufficient time, the Consul cluster triggers a new leader election. Consul pauses all network requests and Raft updates until the election ends. + +## Hardware requirements + +The minimum hardware requirements for Consul servers in production clusters as recommended by the [reference architecture](/consul/tutorials/production-deploy/reference-architecture#hardware-sizing-for-consul-servers) are: + +| CPU | Memory | Disk Capacity | Disk IO | Disk Throughput | Avg Round-Trip-Time | 99% Round-Trip-Time | +| --------- | ------------ | ------------- | ----------- | --------------- | ------------------- | ------------------- | +| 8-16 core | 32-64 GB RAM | 200+ GB | 7500+ IOPS | 250+ MB/s | Lower than 50ms | Lower than 100ms | + +For the major cloud providers, we recommend starting with one of the following instances that meet the minimum requirements. Then scale up as needed. We also recommend avoiding "burstable" CPU and storage options where performance may drop after a consistent load. + +| Provider | Size | Instance/VM Types | Disk Volume Specs | +| --------- | ----- | ------------------------------------- | --------------------------------- | +| **AWS** | Large | `m5.2xlarge`, `m5.4xlarge` | 200+GB `gp3`, 10000 IOPS, 250MB/s | +| **Azure** | Large | `Standard_D8s_v3`, `Standard_D16s_v3` | 2048GB `Premium SSD`, 7500 IOPS, 200MB/s | +| **GCP** | Large | `n2-standard-8`, `n2-standard-16` | 1000GB `pd-ssd`, 30000 IOPS, 480MB/s | + + +For HCP Consul Dedicated, cluster size is measured in the number of service instances supported. Find out more information in the [HCP Consul Dedicated pricing page](https://cloud.hashicorp.com/products/consul/pricing). + +## Workload input and output requirements + +Workloads are any actions that interact with the Consul cluster. These actions consist of key/value reads and writes, service registrations and deregistrations, adding or removing Consul client agents, and more. + +Input/output operations per second (IOPS) is a unit of measurement for the amount of reads and writes to non-adjacent storage locations. +For high workloads, ensure that the Consul server disks support a [high number of IOPS](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-io-characteristics.html#ebs-io-iops) to keep up with the rapid Raft log update rate. +Unlike bare-metal environments, IOPS for virtual instances in cloud environments is often tied to storage sizing. More storage GBs typically grants you more IOPS. Therefore, we recommend deploying on [IOPS-optimized instances](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/provisioned-iops.html). + +Consul server agents are generally I/O bound for writes and CPU bound for reads. For additional tuning recommendations, refer to [raft tuning](#raft-tuning). + +## Memory requirements + +You should allocate RAM for server agents so that they contain 2 to 4 times the working set size. You can determine the working set size of a running cluster by noting the value of `consul.runtime.alloc_bytes` in the leader node's telemetry data. Inspect your monitoring solution for the telemetry value, or run the following commands with the [jq](https://stedolan.github.io/jq/download/) tool installed on your Consul leader instance. + + + +For Kubernetes, execute the command from the leader pod. `jq` is available in the Consul server containers. + + + +Set `$CONSUL_HTTP_TOKEN` to an ACL token with valid permissions, then retrieve the working set size. + +```shell-session +$ curl --silent --header "X-Consul-Token: $CONSUL_HTTP_TOKEN" http://127.0.0.1:8500/v1/agent/metrics | jq '.Gauges[] | select(.Name=="consul.runtime.alloc_bytes") | .Value'` +616017920 +``` + +## Kubernetes storage requirements + +When you set up persistent volumes (PV) resources, you should define the correct server storage class parameter because the defaults are likely insufficient in performance. To set the [storageClass Helm chart parameter](/consul/docs/k8s/helm#v-server-storageclass), refer to the [Kubernetes documentation on storageClasses](https://kubernetes.io/docs/concepts/storage/storage-classes/) for more information about your specific cloud provider. + +## Read and write heavy workload recommendations + +In production, your use case may lead to Consul performing read-heavy workloads, write-heavy workloads, or both. Refer to the following table for specific resource recommendations for these types of workloads. + +| Workload type | Instance Recommendations | Workload element examples | Enterprise Feature Recommendations | +| ------------- | ------------------------- | ------------------------ | ------------------------ | +| Read-heavy | Instances of type `m5.4xlarge (AWS)`, `Standard_D16s_v3 (Azure)`, `n2-standard-16 (GCP)` | Raft RPCs calls, DNS queries, key/value retrieval | [Read replicas](/consul/docs/enterprise/read-scale) | +| Write-heavy | IOPS performance of `10 000+` | Consul agent joins and leaves, services registration and deregistration, key/value writes | [Network segments](/consul/docs/enterprise/network-segments/network-segments-overview) | + +For recommendations on troubleshooting issues with read-heavy or write-heavy workloads, refer to [Consul at Scale](/consul/docs/architecture/scale#resource-usage-and-metrics-recommendations). + +## Monitor performance + +Monitoring is critical to ensure that your Consul datacenter has sufficient resources to continue operations. A proactive monitoring strategy helps you find problems in your network before they impact your deployments. + +We recommend completing the [Monitor Consul server health and performance with metrics and logs](/consul/tutorials/observe-your-network/server-metrics-and-logs) tutorial as a starting point for Consul metrics and telemetry. The following tutorials guide you through specific monitoring solutions for your Consul cluster. + +- [Monitor Consul server health and performance with metrics and logs](/consul/tutorials/observe-your-network/server-metrics-and-logs) +- [Observe Consul service mesh traffic](/consul/tutorials/get-started-kubernetes/kubernetes-gs-observability) + +### Important metrics + +In production environments, create baselines for your Consul cluster's metrics. After you discover the baselines, you will be able to define alerts and receive notifications when there are unexpected values. For a detailed explanation on the metrics and their values, refer to [Consul Agent telemetry](/consul/docs/agent/telemetry). + +### Transaction metrics + +These metrics indicate how long it takes to complete write operations in various parts of the Consul cluster. + +- [`consul.kvs.apply`](/consul/docs/agent/monitor/telemetry#transaction-timing) measures the time it takes to complete an update to the KV store. +- [`consul.txn.apply`](/consul/docs/agent/monitor/telemetry#transaction-timing) measures the time spent applying a transaction operation. +- [`consul.raft.apply`](/consul/docs/agent/monitor/telemetry#transaction-timing) counts the number of Raft transactions applied during the measurement interval. This metric is only reported on the leader. +- [`consul.raft.commitTime`](/consul/docs/agent/monitor/telemetry#transaction-timing) measures the time it takes to commit a new entry to the Raft log on disk on the leader. + +### Memory metrics + +These performance indicators can help you diagnose if the current instance sizing is unable to handle the workload. + +- [`consul.runtime.alloc_bytes`](/consul/docs/agent/monitor/telemetry#memory-usage) measures the number of bytes allocated by the Consul process. +- [`consul.runtime.sys_bytes`](/consul/docs/agent/monitor/telemetry#memory-usage) measures the total number of bytes of memory obtained from the OS. +- [`consul.runtime.heap_objects`](/consul/docs/agent/monitor/telemetry#metrics-reference) measures the number of objects allocated on the heap and is a general memory pressure indicator. + +### Leadership metrics + +Leadership changes are not a cause for concern but frequent changes may be a symptom of a deeper problem. Frequent elections or leadership changes may indicate network issues between the Consul servers, or the Consul servers are unable to keep up with the load. + +- [`consul.raft.leader.lastContact`](/consul/docs/agent/monitor/telemetry#leadership-changes) measures the time since the leader was last able to contact the follower nodes when checking its leader lease. +- [`consul.raft.state.candidate`](/consul/docs/agent/monitor/telemetry#leadership-changes) increments whenever a Consul server starts an election. +- [`consul.raft.state.leader`](/consul/docs/agent/monitor/telemetry#leadership-changes) increments whenever a Consul server becomes a leader. +- [`consul.server.isLeader`](/consul/docs/agent/monitor/telemetry#leadership-changes) tracks whether a server is a leader. + +### Network metrics + +Network activity and RPC count measurements indicate the current load created from a Consul agent, including when the load becomes high enough to be rate limited. If an unusually high RPC count occurs, you should investigate before it overloads the cluster. + +- [`consul.client.rpc`](/consul/docs/agent/monitor/telemetry#network-activity-rpc-count) increments whenever a Consul agent in client mode makes an RPC request to a Consul server. +- [`consul.client.rpc.exceeded`](/consul/docs/agent/monitor/telemetry#network-activity-rpc-count) increments whenever a Consul agent in client mode makes an RPC request to a Consul server gets rate limited by that agent's limits configuration. +- [`consul.client.rpc.failed`](/consul/docs/agent/monitor/telemetry#network-activity-rpc-count) increments whenever a Consul agent in client mode makes an RPC request to a Consul server and fails. + +## Network constraints and alternate approaches + +If it is impossible for you to allocate the required resources, you can make changes to Consul's performance so that it operates with lower speed or resilience. These changes ensure that your cluster remains within its resource capacity. + +- Soft limits prevent your cluster from degrading due to overload. +- Raft tuning lets you compensate for unfavorable environments. + +### Soft limits + +The recommended maximum size for a single datacenter is 5,000 Consul client agents. This recommendation is based on a standard, non-tuned environment and considers a blast radius's risk management factor. The maximum number of agents may be lower, depending on how you use Consul. + +If you require more than 5,000 client agents, you should break up the single Consul datacenter into multiple smaller datacenters. + +- When the nodes are spread across separate physical locations such as different regions, you can model multiple datacenter structures based on physical locations. +- Use [network segments](/consul/docs/enterprise/network-segments/network-segments-overview) in a single available zone or region to lower overall resource usage in a single datacenter. + +When deploying [Consul in Kubernetes](/consul/docs/k8s), we recommend you set both _requests_ and _limits_ in the Helm chart. Refer to the [Helm chart documentation](/consul/docs/k8s/helm#v-server-resources) for more information. + +- Requests allocate the required resources for your Consul workloads. +- Limits prevent your pods from being terminated and restarted if they consume more resources than requested and Kubernetes needs to reclaim these resources. Limits can prevent outage situations where the Consul leader's container gets terminated and redeployed due to resource constraints. + +The following is an example Helm configuration that allocates 16 CPU cores and 64 gigabytes of memory: + + + +```yaml +global: + image: "hashicorp/consul" +## ... +resources: + requests: + memory: '64G' + cpu: '16000m' + limits: + memory: '64G' + cpu: '16000m' +``` + + + +### Raft tuning + +Consul uses the [Raft consensus algorithm](/consul/docs/architecture/consensus) to provide consistency. +You may need to adjust Raft to suit your specific environment. Adjust the [`raft_multiplier` configuration](/consul/docs/agent/config/config-files#raft_multiplier) to define the trade-off between leader stability and time to recover from a leader failure. + +- A lower multiplier minimizes failure detection and election time, but it may trigger frequently in high latency situations. +- A higher multiplier reduces the chances that failures cause leadership churn, but your cluster takes longer to detect real failures and restore availability. + +The value of `raft_multiplier` has a default value of 5. It is a scaling factor setting that directly affects the following parameters: + +| Parameter name | Default value | Derived from | +| --- | --- | --- | +| HeartbeatTimeout | 5000ms | 5 x 1000ms | +| ElectionTimeout | 5000ms | 5 x 1000ms | +| LeaderLeaseTimeout | 2500ms | 5 x 500ms | + +You can use the telemetry from [`consul.raft.leader.lastContact`](/consul/docs/agent/telemetry#leadership-changes) to observe Raft timing performance. + +Wide networks with more latency perform better with larger values of `raft_multiplier`, but cluster failure detection will take longer. If your network operates with low latency, we recommend that you do not set the Raft multiplier higher than 5. Instead, you should either replace the servers with more powerful ones or minimize the network latency between nodes. + +We recommend you start from a baseline and perform [chaos engineering testing](/consul/tutorials/resiliency/introduction-chaos-engineering?in=consul%2Fresiliency) with different values for the Raft multiplier to find the acceptable time for problem detection and recovery for the cluster. Then scale the cluster and its dedicated resources with the number of workloads handled. This approach gives you the best balance between pure resource growth and pure Raft tuning strategies because it lets you use Raft tuning as a backup plan if you cannot scale your resources. + +The types of workloads the Consul cluster handles also play an important role in Raft tuning. For example, if your Consul clusters are mostly static and do not handle many events, you should increase your Raft multiplier instead of scaling your resources because the risk of an important event happening while the cluster is converging or re-electing a leader is lower. diff --git a/website/content/docs/architecture/catalog.mdx b/website/content/docs/architecture/catalog.mdx index ba3ee8933f..dad1ef9ace 100644 --- a/website/content/docs/architecture/catalog.mdx +++ b/website/content/docs/architecture/catalog.mdx @@ -14,7 +14,9 @@ For more information about the information returned when querying the catalog, i Consul tracks information about registered services through its catalog API. This API records user-defined information about the external services, such as their partitions and required health checks. It also records information that Consul assigns for its own operations, such as an ID for each service instance and the [Raft indices](/consul/docs/architecture/consensus) when the instance is registered and modified. -Consul uses v1 of the catalog API by default. Consul v1.17.0 and later ships with version 2 (v2) of the catalog API. V2 is intended for testing and development purposes. V1 and V2 of the catalog APIs cannot run concurrently in a Consul deployment. There is no migration path between catalog versions. For more information, refer to [Consul v2 Catalog API](/consul/docs/architecture/v2/catalog). +### v2 Catalog + +Consul introduced an experimental v2 Catalog API in v1.17.0. This API supported multi-port Service configurations on Kubernetes, and it was made available for testing and development purposes. The v2 catalog and its support for multiport Kubernetes Services were deprecated in the v1.19.0 release. ## Catalog structure diff --git a/website/content/docs/architecture/coordinates.mdx b/website/content/docs/architecture/coordinates.mdx index ad8f7722e2..7bc37cc9c0 100644 --- a/website/content/docs/architecture/coordinates.mdx +++ b/website/content/docs/architecture/coordinates.mdx @@ -14,10 +14,10 @@ very simple calculation. This allows for many useful applications, such as findi the service node nearest a requesting node, or failing over to services in the next closest datacenter. -All of this is provided through the use of the [Serf library](https://www.serf.io/). +All of this is provided through the use of the [Serf library](https://github.com/hashicorp/serf/). Serf's network tomography is based on ["Vivaldi: A Decentralized Network Coordinate System"](http://www.cs.ucsb.edu/~ravenben/classes/276/papers/vivaldi-sigcomm04.pdf), with some enhancements based on other research. There are more details about -[Serf's network coordinates here](https://www.serf.io/docs/internals/coordinates.html). +[Serf's network coordinates here](https://github.com/hashicorp/serf/blob/master/docs/internals/coordinates.html.markdown). ## Network Coordinates in Consul diff --git a/website/content/docs/architecture/gossip.mdx b/website/content/docs/architecture/gossip.mdx index 09f85b19bd..12a4ef8de7 100644 --- a/website/content/docs/architecture/gossip.mdx +++ b/website/content/docs/architecture/gossip.mdx @@ -9,15 +9,15 @@ description: >- Consul uses a [gossip protocol](https://en.wikipedia.org/wiki/Gossip_protocol) to manage membership and broadcast messages to the cluster. The protocol, membership management, and message broadcasting is provided -through the [Serf library](https://www.serf.io/). The gossip protocol +through the [Serf library](https://github.com/hashicorp/serf/). The gossip protocol used by Serf is based on a modified version of the [SWIM (Scalable Weakly-consistent Infection-style Process Group Membership)](https://www.cs.cornell.edu/projects/Quicksilver/public_pdfs/SWIM.pdf) protocol. -Refer to the [Serf documentation](https://www.serf.io/docs/internals/gossip.html) for additional information about the gossip protocol. +Refer to the [Serf documentation](https://github.com/hashicorp/serf/blob/master/docs/internals/gossip.html.markdown) for additional information about the gossip protocol. ## Gossip in Consul Consul uses a LAN gossip pool and a WAN gossip pool to perform different functions. The pools -are able to perform their functions by leveraging an embedded [Serf](https://www.serf.io/) +are able to perform their functions by leveraging an embedded [Serf](https://github.com/hashicorp/serf/) library. The library is abstracted and masked by Consul to simplify the user experience, but developers may find it useful to understand how the library is leveraged. @@ -52,5 +52,5 @@ For more details about Lifeguard, please see the [Making Gossip More Robust with Lifeguard](https://www.hashicorp.com/blog/making-gossip-more-robust-with-lifeguard/) blog post, which provides a high level overview of the HashiCorp Research paper [Lifeguard : SWIM-ing with Situational Awareness](https://arxiv.org/abs/1707.00788). The -[Serf gossip protocol guide](https://www.serf.io/docs/internals/gossip.html#lifeguard) +[Serf gossip protocol guide](https://github.com/hashicorp/serf/blob/master/docs/internals/gossip.html.markdown#lifeguard-enhancements) also provides some lower-level details about the gossip protocol and Lifeguard. diff --git a/website/content/docs/architecture/improving-consul-resilience.mdx b/website/content/docs/architecture/improving-consul-resilience.mdx index bfcc9a8be4..aea40f3558 100644 --- a/website/content/docs/architecture/improving-consul-resilience.mdx +++ b/website/content/docs/architecture/improving-consul-resilience.mdx @@ -5,21 +5,19 @@ description: >- Fault tolerance is a system's ability to operate without interruption despite component failure. Learn how a set of Consul servers provide fault tolerance through use of a quorum, and how to further improve control plane resilience through use of infrastructure zones and Enterprise redundancy zones. --- -# Fault Tolerance +# Fault tolerance + + +You must give careful consideration to reliability in the architecture frameworks that you build. When you build a resilient platform, it minimizes the remediation actions you need to take when a failure occurs. This document provides useful information on how to design and operate a resilient Consul cluster, including the methods and functionalities for this goal. + +Consul has many features that operate both locally and remotely that can help you offer a resilient service across multiple datacenters. + + +## Introduction Fault tolerance is the ability of a system to continue operating without interruption -despite the failure of one or more components. -The most basic production deployment of Consul has 3 server agents and can lose a single -server without interruption. +despite the failure of one or more components. In Consul, the number of server agents determines the fault tolerance. -As you continue to use Consul, your circumstances may change. -Perhaps a datacenter becomes more business critical or risk management policies change, -necessitating an increase in fault tolerance. -The sections below discuss options for how to improve Consul's fault tolerance. - -## Fault Tolerance in Consul - -Consul's fault tolerance is determined by the configuration of its voting server agents. Each Consul datacenter depends on a set of Consul voting server agents. The voting servers ensure Consul has a consistent, fault-tolerant state @@ -42,28 +40,25 @@ number of servers, quorum, and fault tolerance, refer to the [consensus protocol documentation](/consul/docs/architecture/consensus#deployment_table). Effectively mitigating your risk is more nuanced than just increasing the fault tolerance -metric described above. You must consider: +because the infrastructure costs can outweigh the improved resiliency. You must also consider correlated risks at the infrastructure-level. There are occasions when multiple servers fail at the same time. That means that a single failure could cause a Consul outage, even if your server-level fault tolerance is 2. -### Correlated Risks +Different options for your resilient datacenter present trade-offs between operational complexity, computing cost, and Consul request performance. Consider these factors when designing your resilient architecture. -Are you protected against correlated risks? Infrastructure-level failures can cause multiple servers to fail at the same time. This means that a single infrastructure-level failure could cause a Consul outage, even if your server-level fault tolerance is 2. +## Fault tolerance -### Mitigation Costs +The following sections explore several options for increasing Consul's fault tolerance. For enhanced reliability, we recommend taking a holistic approach by layering these multiple functionalities together. -What are the costs of the mitigation? Different mitigation options present different trade-offs for operational complexity, computing cost, and Consul request performance. +- Spread servers across infrastructure [availability zones](#availability-zones). +- Use a [minimum quorum size](#quorum-size) to avoid performance impacts. +- Use [redundancy zones](#redundancy-zones) to improve fault tolerance. +- Use [Autopilot](#autopilot) to automatically prune failed servers and maintain quorum size. +- Use [cluster peering](#cluster-peering) to provide service redundancy. -## Strategies to Increase Fault Tolerance +### Availability zones -The following sections explore several options for increasing Consul's fault tolerance. -HashiCorp recommends all production deployments consider: -- [Spreading Consul servers across availability zones](#spread-servers-across-infrastructure-availability-zones) -- Using backup voting servers to replace lost voters +The cloud or on-premise infrastructure underlying your [Consul datacenter](/consul/docs/install/glossary#datacenter) can run across multiple availability zones. -### Spread Servers Across Infrastructure Availability Zones - -The cloud or on-premise infrastructure underlying your [Consul datacenter](/consul/docs/install/glossary#datacenter) -may be split into several "availability zones". An availability zone is meant to share no points of failure with other zones by: - Having power, cooling, and networking systems independent from other zones - Being physically distant enough from other zones so that large-scale disruptions @@ -79,25 +74,25 @@ To distribute your Consul servers across availability zones, modify your infrast Additionally, you should leverage resources that can automatically restore your compute instance, such as autoscaling groups, virtual machine scale sets, or compute engine autoscaler. -The autoscaling resources can be customized to re-deploy servers into specific availability zones -and ensure the desired numbers of servers are available at all time. +Customize autoscaling resources to re-deploy servers into specific availability zones and ensure the desired numbers of servers are available at all times. -### Add More Voting Servers +### Quorum size -For most production use cases, we recommend using either 3 or 5 voting servers, +For most production use cases, we recommend using a minimum quorum of either 3 or 5 voting servers, yielding a server-level fault tolerance of 1 or 2 respectively. Even though it would improve fault tolerance, adding voting servers beyond 5 is **not recommended** because it decreases Consul's performance— it requires Consul to involve more servers in every state change or consistent read. -Consul Enterprise provides a way to improve fault tolerance without this performance penalty: -[using backup voting servers to replace lost voters](#use-backup-voting-servers-to-replace-lost-voters). +Consul Enterprise users can use redundancy zones to improve fault tolerance without this performance penalty. -### Use Backup Voting Servers to Replace Lost Voters +### Redundancy zones -Consul Enterprise [redundancy zones](/consul/docs/enterprise/redundancy) -can be used to improve fault tolerance without the performance penalty of increasing the number of voting servers. +Use Consul Enterprise [redundancy zones](/consul/docs/enterprise/redundancy) to improve fault tolerance without the performance penalty of increasing the number of voting servers. + +![Reference architecture diagram for Consul Redundancy zones](/img/architecture/consul-redundancy-zones-light.png#light-theme-only) +![Reference architecture diagram for Consul Redundancy zones](/img/architecture/consul-redundancy-zones-dark.png#dark-theme-only) Each redundancy zone should be assigned 2 or more Consul servers. If all servers are healthy, only one server per redundancy zone will be an active voter; @@ -132,3 +127,51 @@ For more information on redundancy zones, refer to: for a more detailed explanation - [Redundancy zone tutorial](/consul/tutorials/enterprise/redundancy-zones) to learn how to use them + +### Autopilot + +Autopilot is a set of functions that introduce servers to a cluster, cleans up dead servers, and monitors the state of the Raft protocol in the Consul cluster. + +When you enable Autopilot's dead server cleanup, Autopilot marks failed servers as `Left` and removes them from the Raft peer set to prevent them from interfering with the quorum size. Autopilot does that as soon as a replacement Consul server comes online. This behavior is beneficial when server nodes failed and have been redeployed but Consul considers them as new nodes because their IP address and hostnames have changed. Autopilot keeps the cluster peer set size correct and the quorum requirement simple. + +To illustrate the Autopilot advantage, consider a scenario where Consul has a cluster of five server nodes. The quorum is three, which means the cluster can lose two server nodes before the cluster fails. The following events happen: + +1. Two server nodes fail. +1. Two replacement nodes are deployed with new hostnames and IPs. +1. The two replacement nodes rejoin the Consul cluster. +1. Consul treats the replacement nodes as extra nodes, unrelated to the previously failed nodes. + +_With Autopilot not enabled_, the following happens: + +1. Consul does not immediately clean up the failed nodes when the replacement nodes join the cluster. +1. The cluster now has the three surviving nodes, the two failed nodes, and the two replacement nodes, for a total of seven nodes. + - The quorum is increased to four, which means the cluster can only afford to lose one node until after the two failed nodes are deleted in seventy-two hours. + - The redundancy level has decreased from its initial state. + +_With Autopilot enabled_, the following happens: + +1. Consul immediately cleans up the failed nodes when the replacement nodes join the cluster. +1. The cluster now has the three surviving nodes and the two replacement nodes, for a total of five nodes. + - The quorum stays at three, which means the cluster can afford to lose two nodes before it fails. + - The redundancy level remains the same. + +### Cluster peering + +Linking multiple Consul clusters together to provide service redundancy is the most effective method to prevent disruption from failure. This method is enhanced when you design individual Consul clusters with resilience in mind. Consul clusters interconnect in two ways: WAN federation and cluster peering. We recommend using cluster peering whenever possible. + +Cluster peering lets you connect two or more independent Consul clusters using mesh gateways, so that services can communicate between non-identical partitions in different datacenters. + +![Reference architecture diagram for Consul cluster peering](/img/architecture/cluster-peering-diagram-light.png#light-theme-only) +![Reference architecture diagram for Consul cluster peering](/img/architecture/cluster-peering-diagram-dark.png#dark-theme-only) + +Cluster peering is the preferred way to interconnect clusters because it is operationally easier to configure and manage than WAN federation. Cluster peering communication between two datacenters runs only on one port on the related Consul mesh gateway, which makes it operationally easy to expose for routing purposes. + +When you use cluster peering to connect admin partitions between datacenters, use Consul’s dynamic traffic management functionalities `service-splitter`, `service-router` and `service-failover` to configure your service mesh to automatically forward or failover service traffic between peer clusters. Consul can then manage the traffic intended for the service and do [failover](/consul/docs/connect/config-entries/service-resolver#spec-failover), [load-balancing](/consul/docs/connect/config-entries/service-resolver#spec-loadbalancer), or [redirection](/consul/docs/connect/config-entries/service-resolver#spec-redirect). + +Cluster peering also extends service discovery across different datacenters independent of service mesh functions. After you peer datacenters, you can refer to services between datacenters with `.virtual.peer.consul` in Consul DNS. For Consul Enterprise, your query string may need to include the namespace, partition, or both. Refer to the [Consul DNS documentation](/consul/docs/services/discovery/dns-static-lookups#service-virtual-ip-lookups) for details on building virtual service lookups. + +For more information on cluster peering, refer to: +- [Cluster peering documentation](/consul/docs/connect/cluster-peering) + for a more detailed explanation +- [Cluster peering tutorial](/consul/tutorials/implement-multi-tenancy/cluster-peering) + to learn how to implement cluster peering diff --git a/website/content/docs/architecture/index.mdx b/website/content/docs/architecture/index.mdx index a4656a7718..dc3f7954bd 100644 --- a/website/content/docs/architecture/index.mdx +++ b/website/content/docs/architecture/index.mdx @@ -25,7 +25,7 @@ The Consul control plane contains one or more _datacenters_. A datacenter is the ### Clusters -A collection of Consul agents that are aware of each other is called a _cluster_. The terms _datacenter_ and _cluster_ are often used interchangeably. In some cases, however, _cluster_ refers only to Consul server agents, such as in [HCP Consul](https://cloud.hashicorp.com/products/consul). In other contexts, such as the [_admin partitions_](/consul/docs/enterprise/admin-partitions) feature included with Consul Enterprise, a cluster may refer to collection of client agents. +A collection of Consul agents that are aware of each other is called a _cluster_. The terms _datacenter_ and _cluster_ are often used interchangeably. In some cases, however, _cluster_ refers only to Consul server agents, such as in [HCP Consul Dedicated](https://cloud.hashicorp.com/products/consul). In other contexts, such as the [_admin partitions_](/consul/docs/enterprise/admin-partitions) feature included with Consul Enterprise, a cluster may refer to collection of client agents. ## Agents diff --git a/website/content/docs/architecture/scale.mdx b/website/content/docs/architecture/scale.mdx index d3aceeb0d4..119e05454a 100644 --- a/website/content/docs/architecture/scale.mdx +++ b/website/content/docs/architecture/scale.mdx @@ -77,7 +77,7 @@ Consul server agents are an important part of Consul’s architecture. This sect Consul servers can be deployed on a few different runtimes: -- **HashiCorp Cloud Platform (HCP) Consul (Managed)**. These Consul servers are deployed in a hosted environment managed by HCP. To get started with HCP Consul servers in Kubernetes or VM deployments, refer to the [Deploy HCP Consul tutorial](/consul/tutorials/get-started-hcp/hcp-gs-deploy). +- **HashiCorp Cloud Platform (HCP) Consul Dedicated**. These Consul servers are deployed in a hosted environment managed by HCP. To get started with HCP Consul Dedicated servers in Kubernetes or VM deployments, refer to the [Deploy HCP Consul Dedicated tutorial](/consul/tutorials/get-started-hcp/hcp-gs-deploy). - **VMs or bare metal servers (Self-managed)**. To get started with Consul on VMs or bare metal servers, refer to the [Deploy Consul server tutorial](/consul/tutorials/get-started-vms/virtual-machine-gs-deploy). For a full list of configuration options, refer to [Agents Overview](/consul/docs/agent). - **Kubernetes (Self-managed)**. To get started with Consul on Kubernetes, refer to the [Deploy Consul on Kubernetes tutorial](/consul/tutorials/get-started-kubernetes/kubernetes-gs-deploy). - **Other container environments, including Docker, Rancher, and Mesos (Self-managed)**. diff --git a/website/content/docs/architecture/v2/catalog.mdx b/website/content/docs/architecture/v2/catalog.mdx deleted file mode 100644 index aee72d0093..0000000000 --- a/website/content/docs/architecture/v2/catalog.mdx +++ /dev/null @@ -1,61 +0,0 @@ ---- -layout: docs -page_title: Consul catalog v2 architecture -description: Learn about version 2 of the Consul catalog, which uses GAMMA specified resources. Learn how the v2 catalog corresponds to the v1 catalog. ---- - - - -The v2 catalog API and Traffic Permissions API are currently in beta. This documentation supports testing and development scenarios. Do not use these APIs in secure production environments. - - - -# Consul catalog v2 architecture - -This topic provides information about version 2 (v2) of the Consul catalog API. The catalog tracks registered services and their locations for both service discovery and service mesh use cases. - -Consul supports the v2 catalog for service mesh use cases on Kubernetes deployments only. For more information about Consul’s default catalog, refer to [v1 Catalog API](/consul/docs/architecture/catalog). - -## Introduction - -When Consul registers services, it records [user-defined and Consul-assigned information](/consul/docs/architecture/catalog#catalog-structure). To determine a service’s identity, v1 of the catalog API records the following information: - -- IDs of the specific _service instances_ that are registered -- Locations of the _nodes_ the instances run on -- Names of the _services_ the instances are associated with - -This information enables Consul to associate service names with the individual instances and their unique network addresses, which makes it essential to Consul’s service discovery and service mesh operations. - -The [Consul v1 catalog API](/consul/docs/architecture/catalog) was designed prior to the introduction of Consul’s service mesh features. One major implication of this design is that communication in Consul’s service mesh is secured through Consul's ACL system, which requires that a Kubernetes ServiceAccount resource match the Service name. As a result, only one Kubernetes Service can represent a service instance in the v1 catalog. - -The v2 catalog API aligns more closely with the [Kubernetes Gateway API's GAMMA initiative](https://gateway-api.sigs.k8s.io/concepts/gamma/), which conceptualizes a Kubernetes Service as having two facets: - -- The Service _front end_ is a combination of cluster IP and DNS name -- The Service _back end_ is a collection of endpoint IPs - -For more information about the differences between the two facets and their impact on how Kubernetes directs requests, refer to [The Different Facets of a Service](https://gateway-api.sigs.k8s.io/concepts/service-facets/) in the Kubernetes documentation. - -Consul's v2 catalog API makes a similar distinction, enabling it associate Kubernetes Pods with multiple Kubernetes Services. As a direct result of this change in catalog structure, Consul can register Services and Pods with multiple ports. For more information about how the differences between the catalog API impacts other Consul operations, refer to [changes to Consul's existing architecture](#changes-to-consul-s-existing-architecture). - -The v2 catalog API is available alongside the existing v1 catalog API, but the catalogs cannot be used simultaneously. The v2 catalog is disabled by default. This beta release is for testing and development purposes only. We do not recommend implementing v2 in production environments or migrating to v2 until the API is generally available. - -## Catalog structure - -Consul v1.17 introduces a new version of the catalog API designed to bridge differences between the Consul and Kubernetes data models. The v2 catalog API continues to track services and nodes for Consul, but it replaces service instances with _workloads_ and _workload identites_. - -### Catalog resources - -The following table describes resources in the v2 catalog, how they generally compare to the v1 catalog and Kubernetes resources, and whether they are created by Kubernetes or computed by Consul when it registers a service. - -| Catalog v2 resource | Resource group | Description | Catalog v1 analogue | Kubernetes analogue | Source | -| :------------------ | :------------- | :---------- | :--------------------------- | :--------------------------- | :----- | -| Service | `catalog` | The name of the service Consul registers a workload under. | Service | [Kubernetes Service](https://kubernetes.io/docs/concepts/services-networking/service/) | Created by Kubernetes | -| Node | `catalog` | The address of the Consul node where the workload runs. | Node | [Kubernetes Node](https://kubernetes.io/docs/concepts/architecture/nodes/) | Computed by Consul | -| Workload | `catalog` | An application instance running in a set of one or more Pods scheduled according to a Kubernetes Workload resource such as a Deployment or StatefulSet. | Service instance | [Kubernetes Pod](https://kubernetes.io/docs/concepts/workloads/pods/) | Created by Kubernetes | -| Workload identity | `auth` | Provides a distinct identity for a workload to assume. Each workload identity is tied to an Envoy proxy. This identity is used when Consul generates mTLS certificates. | Service name | [Kubernetes Service Accounts](https://kubernetes.io/docs/concepts/security/service-accounts/) | Created by Kubernetes | -| Service endpoint | Maps services to workload addresses and endpoints. | None | [Kubernetes Endpoints](https://kubernetes.io/docs/reference/kubernetes-api/service-resources/endpoints-v1/) | Computed by Consul | -| Health status | `catalog` | A resource for reporting the health status of a workload. | Service instance health status | [PodStatus](https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/pod-v1/#PodStatus) | Created by Kubernetes | -| Health check | None | A resource for defining the health checks for a workload. | [Service instance health check](/consul/docs/services/usage/checks) | [Liveness, Readiness, and Startup Probes](https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#container-probes) | Created by Kubernetes | -| Proxy configuration | `mesh` | Represents a configuration for a sidecar or gateway proxy. | `Proxy` field in service definition | None | Created by Kubernetes or user CRD | -| Destinations | `catalog` | Represents explicit service upstreams. When using the v1 catalog, these upstreams are configured in Helm chart as [Upstream Service annotations](/consul/docs/k8s/annotations-and-labels#consul-hashicorp-com-connect-service-upstreams) | [Proxy Configuration](/consul/docs/connect/proxies/envoy#envoy-proxy-configuration-for-service-mesh) | None | Created by Kubernetes | -| Traffic permissions| `auth` | Enables L4 traffic authorization according to workload identity instead of service identity. | [Service intentions](/consul/docs/connect/intentions) | None | Created by user CRD | diff --git a/website/content/docs/architecture/v2/groups.mdx b/website/content/docs/architecture/v2/groups.mdx deleted file mode 100644 index c5d141de06..0000000000 --- a/website/content/docs/architecture/v2/groups.mdx +++ /dev/null @@ -1,51 +0,0 @@ ---- -layout: docs -page_title: Consul resource groups -description: Learn about resource groups in version 2 of Consul's internal architecture. The auth, catalog, and mesh groups structure Consul's ability to target individual workloads or an entire collection of workload endpoints. ---- - - - -The v2 catalog API and Traffic Permissions API are currently in beta. This documentation supports testing and development scenarios. Do not use these APIs in secure production environments. - - - -# Consul resource groups - -This topic provides an overview of resource groups in Consul's v2 architecture. - -Refer to the [`consul resource` CLI command reference](/consul/docs/commands/resource) to learn about using the Consul CLI to interact with resources. - -## Introduction - -Consul's v2 architecture manages workloads using _resources_. Each resource is part of a _resource group_. - -These resource groups structure Consul's ability to target either an _individual workload identity_ or an _entire collection of workload endpoints_ when managing service mesh traffic. There are three resource groups in the v2 API: - -- `auth` group: Resources apply to workload identity -- `catalog` group: Resources apply to all workloads associated with a service -- `mesh` group: Resources apply to either workload identities or all workloads - -For example, traffic permissions are part of the `auth` group. Permissions allow or deny traffic according to the other v2 catalog resource in the `auth` group, the workload identity. Meanwhile, when Consul routes service mesh traffic it applies rules to workloads based on the Service, which is a resource in the `catalog` group. - -One practical impact of resource groups is that the [HTTPRoute](/consul/docs/k8s/multiport/reference/httproute), [GRPCRoute](/consul/docs/k8s/multiport/reference/grpcroute), and [TCPRoute](/consul/docs/k8s/multiport/reference/tcproute) CRDs require you to specify a `name` and `type` in configuration blocks. The `catalog.v2beta1.Service` type indicates that the rules defined in these CRDs apply to all workloads registered in the Consul catalog under the given name. - -You can also use the `consul resource` command to return information about Consul resources in each group using a `group.groupVersion.kind` syntax. Refer to [`consul resource`](/consul/docs/commands/resource) for more information. - -## Resource group reference - -The following table describes the Consul resources that belong to each resource group and the resource's `group.groupVersion.kind` syntax. - -| Resource `group` | v2 resource | Consul resource syntax | -| :------------------ | :-------- | :---- | -| `auth` | Traffic permissions | `auth.v2beta1.TrafficPermissions` | -| `auth` | Workload identity | `auth.v2beta1.WorkloadIdentity` | -| `catalog` | Service | `catalog.v2beta1.Service` | -| `catalog` | Node | `catalog.v2beta1.Node` | -| `catalog` | Workload | `catalog.v2beta1.Workload` | -| `catalog` | Health status | `catalog.v2beta1.HealthStatus` | -| `catalog` | Destinations | `catalog.v2beta1.Destination` | -| `mesh` | GRPCRoute | `mesh.v2beta1.GRPCRoute` | -| `mesh` | HTTPRoute | `mesh.v2beta1.HTTPRoute` | -| `mesh` | Proxy configuration | `mesh.v2beta1.ProxyConfiguration` | -| `mesh` | TCPRoute | `mesh.v2beta1.TCPRoute` | diff --git a/website/content/docs/architecture/v2/index.mdx b/website/content/docs/architecture/v2/index.mdx deleted file mode 100644 index b4c7a1e0cc..0000000000 --- a/website/content/docs/architecture/v2/index.mdx +++ /dev/null @@ -1,76 +0,0 @@ ---- -layout: docs -page_title: Consul v2 architecture -description: Learn about version 2 of Consul's internal architecture, which replaces services and service instances with workloads and workload identies. ---- - - - -The v2 catalog API and Traffic Permissions API are currently in beta. This documentation supports testing and development scenarios. Do not use these APIs in secure production environments. - - - -# Consul v2 architecture - -This topic provides an overview of Consul's v2 architecture changes and their impact on Consul operations. - -In the v1.17.0 release, Consul introduced the option to enable the [v2 catalog API](/consul/docs/architecture/v2/catalog) for testing and development on Kubernetes deployments. The v2 catalog is one of several changes in development to simplify and streamline Consul's architecture. - -## Overview - -The v2 architecture changes include the following: - -- [v2 catalog API](/consul/docs/architecture/v2/catalog) -- [Resource groups](/consul/docs/architecture/v2/groups) - -## Differences between v1 and v2 - -The change in data models introduced by the v2 architecture impacts several aspects of Consul’s operations. - -### Traffic permissions resource replaces service intentions - -The most significant change to Consul’s architecture and operations when using the v2 catalog structure is the introduction of the traffic permissions resource. This resource replaces the service intentions configuration entry, and enables authorized service-to-service communication for both L4 and L7 applications. - -For more information about this resource, including example configurations, refer to [Traffic permissions configuration reference](/consul/docs/k8s/multiport/reference/trafficpermissions). - -### HTTPRoute, GRPCRoute, and TCPRoute resources for traffic management - -You can configure traffic management behavior such as service splitting in an `HTTPRoute`, `GRPCRoute`, or `TCPRoute` resource. In the v1 catalog, this behavior is defined in dedicated configuration entries. For examples, refer to [service splitter configuration entry reference](/consul/docs/connect/config-entries/service-splitter#examples). - -For more information about these resource, including specifications and example configurations, refer to [HTTPRoute resource configuration reference](/consul/docs/k8s/multiport/reference/httproute), [GRPCRoute resource configuration reference](/consul/docs/k8s/multiport/reference/grpcroute), and [TCPRoute resource configuration reference](/consul/docs/k8s/multiport/reference/tcproute). - -### New proxy configuration resource - -In the v1 catalog, a service’s sidecar proxy and its behavior is [defined in the `Proxy` field of the service definition](/consul/docs/services/usage/define-services). You can also separately [define a service mesh proxy](/consul/docs/connect/proxies/deploy-service-mesh-proxies) and [configure proxy defaults](/consul/docs/connect/config-entries/proxy-defaults). - -In the v2 catalog, the `ProxyConfiguration` resource configures a workload's sidecar proxy behavior according to Consul workload identity. Refer to [ProxyConfiguration resource configuration reference](/consul/docs/k8s/multiport/reference/proxyconfiguration) for more information. - -## Constraints and limitations - -Be aware of the following constraints and technical limitations on the v2 catalog API: - -- The v2 catalog API only supports deployments using [Consul dataplanes](/consul/docs/connect/dataplane) instead of client agents. Consul on Kubernetes uses dataplanes by default. -- The v1 and v2 catalog APIs cannot run concurrently. Because configuration entries are part of the v1 catalog, you cannot use them when the v2 catalog is enabled. Use v2 resources instead. -- The Consul UI does not support the v2 catalog API in this release. You must disable the UI in the Helm chart in order to use the v2 catalog API. -- Secondary datacenters in WAN-federated deployments cannot enable the v2 catalog API in this release. -- HCP Consul does not support the v2 catalog API in this release. You cannot [link a self-managed cluster to HCP Consul](/hcp/docs/consul/self-managed) to access its UI or view observability metrics when it uses the v2 catalog. -- We do not recommend updating existing clusters to enable the v2 catalog in this release. Instead, deploy a new Consul cluster and [enable the v2 catalog in the Helm chart](/consul/docs/k8s/multiport/configure#enable-the-v2-catalog). - -## Guidance - -The following resources are available to help you use the v2 catalog API: - -### Usage documentation - -- [use the v2 Catalog API](/consul/docs/k8s/multiport) -- [Configure multi-port services](/consul/docs/k8s/multiport/configure) -- [Split TCP traffic between multiple ports](/consul/docs/k8s/multiport/traffic-split) - -### Reference documentation - -- [`consul resource` CLI command](/consul/docs/k8s/multiport/reference/resource-command) -- [GRPCRoute configuration reference](/consul/docs/k8s/multiport/reference/grpcroute) -- [HTTPRoute configuration reference](/consul/docs/k8s/multiport/reference/httproute) -- [ProxyConfiguration configuration reference](/consul/docs/k8s/multiport/reference/proxyconfiguration) -- [TCPRoute configuration reference](/consul/docs/k8s/multiport/reference/tcproute) -- [TrafficPermissions configuration reference](/consul/docs/k8s/multiport/reference/trafficpermissions) diff --git a/website/content/docs/concepts/service-discovery.mdx b/website/content/docs/concepts/service-discovery.mdx index 4313ba3e15..44c83b7414 100644 --- a/website/content/docs/concepts/service-discovery.mdx +++ b/website/content/docs/concepts/service-discovery.mdx @@ -82,7 +82,7 @@ Consul's service discovery capabilities help you discover, track, and monitor th You can use Consul with virtual machines (VMs), containers, serverless technologies, or with container orchestration platforms, such as [Nomad](https://www.nomadproject.io/) and Kubernetes. Consul is platform agnostic which makes it a great fit for all environments, including legacy platforms. -Consul is available as a [self-managed](/consul/downloads) project or as a fully managed service mesh solution ([HCP Consul](https://portal.cloud.hashicorp.com/sign-in?utm_source=consul_docs)). HCP Consul enables users to discover and securely connect services without the added operational burden of maintaining a service mesh on their own. +Consul is available as a [self-managed](/consul/downloads) project or as a fully managed service mesh solution ([HCP Consul Dedicated](https://portal.cloud.hashicorp.com/sign-in?utm_source=consul_docs)). HCP Consul Dedicated enables users to discover and securely connect services without the added operational burden of maintaining a service mesh on their own. ## Next steps diff --git a/website/content/docs/concepts/service-mesh.mdx b/website/content/docs/concepts/service-mesh.mdx index 947984484e..33ebf1478d 100644 --- a/website/content/docs/concepts/service-mesh.mdx +++ b/website/content/docs/concepts/service-mesh.mdx @@ -107,12 +107,12 @@ In simple terms, Consul is the control plane of the service mesh. The data plane You can use Consul with virtual machines (VMs), containers, or with container orchestration platforms, such as [Nomad](https://www.nomadproject.io/) and Kubernetes. Consul is platform agnostic which makes it a great fit for all environments, including legacy platforms. -Consul is available as a [self-install](/consul/downloads) project or as a fully managed service mesh solution called [HCP Consul](https://portal.cloud.hashicorp.com/sign-in?utm_source=consul_docs). -HCP Consul enables users to discover and securely connect services without the added operational burden of maintaining a service mesh on their own. +Consul is available as a [self-install](/consul/downloads) project or as a fully managed service mesh solution called [HCP Consul Dedicated](https://portal.cloud.hashicorp.com/sign-in?utm_source=consul_docs). +HCP Consul Dedicated enables users to discover and securely connect services without the added operational burden of maintaining a service mesh on their own. You can learn more about Consul by visiting the Consul [tutorials](/consul/tutorials). ## Next -Get started today with a service mesh by leveraging [HCP Consul](https://portal.cloud.hashicorp.com/sign-in?utm_source=consul_docs). +Get started today with a service mesh by leveraging [HCP Consul Dedicated](https://portal.cloud.hashicorp.com/sign-in?utm_source=consul_docs). Prepare your organization for the future of multi-cloud and embrace a [zero-trust](https://www.hashicorp.com/solutions/zero-trust-security) architecture. diff --git a/website/content/docs/connect/cluster-peering/index.mdx b/website/content/docs/connect/cluster-peering/index.mdx index 864dadd418..2aa5147235 100644 --- a/website/content/docs/connect/cluster-peering/index.mdx +++ b/website/content/docs/connect/cluster-peering/index.mdx @@ -45,7 +45,6 @@ Regardless of whether you connect your clusters through WAN federation or cluste | Replicates exported services for service discovery | ❌ | ✅ | | Gossip protocol: Requires LAN gossip only | ❌ | ✅ | | Forwards service requests for service discovery | ✅ | ❌ | -| Shares key/value stores | ✅ | ❌ | | Can replicate ACL tokens, policies, and roles | ✅ | ❌ | ## Guidance @@ -71,11 +70,11 @@ The following resources are available to help you use Consul's cluster peering f - [Manage L7 traffic with cluster peering on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/l7-traffic) - [Create sameness groups on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/create-sameness-groups) -### HCP Consul documentation +### HCP Consul Central documentation - [Cluster peering](/hcp/docs/consul/usage/cluster-peering) - [Cluster peering topologies](/hcp/docs/consul/usage/cluster-peering/topologies) -- [Establish cluster peering connections on HCP Consul](/hcp/docs/consul/usage/cluster-peering/create-connections) +- [Establish cluster peering connections on HCP Consul Central](/hcp/docs/consul/usage/cluster-peering/create-connections) - [Cluster peering with HCP Consul Central](/hcp/docs/extend/cluster-peering/establish) ### Reference documentation diff --git a/website/content/docs/connect/cluster-peering/usage/establish-cluster-peering.mdx b/website/content/docs/connect/cluster-peering/usage/establish-cluster-peering.mdx index 9dc315c65a..4e0128bb3e 100644 --- a/website/content/docs/connect/cluster-peering/usage/establish-cluster-peering.mdx +++ b/website/content/docs/connect/cluster-peering/usage/establish-cluster-peering.mdx @@ -16,7 +16,7 @@ This page details the process for establishing a cluster peering connection betw Cluster peering between services cannot be established until all four steps are complete. If you want to establish cluster peering connections and create sameness groups at the same time, refer to the guidance in [create sameness groups](/consul/docs/connect/cluster-peering/usage/create-sameness-groups). -For Kubernetes guidance, refer to [Establish cluster peering connections on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/establish-peering). For HCP Consul guidance, refer to [Establish cluster peering connections on HCP Consul](/hcp/docs/consul/usage/cluster-peering/create-connections). +For Kubernetes guidance, refer to [Establish cluster peering connections on Kubernetes](/consul/docs/k8s/connect/cluster-peering/usage/establish-peering). For HCP Consul Central guidance, refer to [Establish cluster peering connections on HCP Consul Central](/hcp/docs/consul/usage/cluster-peering/create-connections). ## Requirements diff --git a/website/content/docs/connect/config-entries/api-gateway.mdx b/website/content/docs/connect/config-entries/api-gateway.mdx index 58c8167bd4..dc5d6f63e2 100644 --- a/website/content/docs/connect/config-entries/api-gateway.mdx +++ b/website/content/docs/connect/config-entries/api-gateway.mdx @@ -1,10 +1,10 @@ --- layout: docs -page_title: API Gateway Configuration Entry Reference +page_title: API Gateway configuration reference description: Learn how to configure a Consul API gateway on VMs. --- -# API gateway configuration entry reference +# API gateway configuration reference This topic provides reference information for the API gateway configuration entry that you can deploy to networks in virtual machine (VM) environments. For reference information about configuring Consul API gateways on Kubernetes, refer to [Gateway Resource Configuration](/consul/docs/connect/gateways/api-gateway/configuration/gateway). @@ -349,7 +349,7 @@ Specifies a list of cipher suites that the listener supports when negotiating co ### `Listeners[].TLS.Certificates[]` -The list of references to file system or inline certificates that the listener uses for TLS termination. +The list of references to [file system](/consul/docs/connect/config-entries/file-system-certificate) or [inline certificates](/consul/docs/connect/config-entries/inline-certificate) that the listener uses for TLS termination. You should create the configuration entry for the certificate separately and then reference the configuration entry in the `Name` field. #### Values @@ -372,7 +372,7 @@ The list of references to certificates that the listener uses for TLS terminatio ### `Listeners[].TLS.Certificates[].Name` -Specifies the name of the file system or inline certificate that the listener uses for TLS termination. +Specifies the name of the [file system certificate](/consul/docs/connect/config-entries/file-system-certificate) or [inline certificate](/consul/docs/connect/config-entries/inline-certificate) that the listener uses for TLS termination. #### Values diff --git a/website/content/docs/connect/config-entries/control-plane-request-limit.mdx b/website/content/docs/connect/config-entries/control-plane-request-limit.mdx index d6e828e670..53404c0d3f 100644 --- a/website/content/docs/connect/config-entries/control-plane-request-limit.mdx +++ b/website/content/docs/connect/config-entries/control-plane-request-limit.mdx @@ -1,10 +1,10 @@ --- layout: docs -page_title: Control Plane Request Limit Configuration Entry Configuration Reference +page_title: Control Plane Request Limit configuration reference description: Learn how to configure the control-plane-request-limit configuration entry, which defines how Consul agents limit read and request traffic rate limits. --- -# Control Plane Request Limit Configuration Entry Configuration Reference +# Control Plane Request Limit configuration reference This topic describes the configuration options for the `control-plane-request-limit` configuration entry. You can only write the `control-plane-request-limit` configuration entry to the `default` partition, but the configuration entry applies to all client requests that target any partition. diff --git a/website/content/docs/connect/config-entries/exported-services.mdx b/website/content/docs/connect/config-entries/exported-services.mdx index 7a268aeb77..ba783b4837 100644 --- a/website/content/docs/connect/config-entries/exported-services.mdx +++ b/website/content/docs/connect/config-entries/exported-services.mdx @@ -1,11 +1,11 @@ --- layout: docs -page_title: Exported Services - Configuration Entry Reference +page_title: Exported Services configuration reference description: >- An exported services configuration entry defines the availability of a cluster's services to cluster peers and local admin partitions. Learn about `""exported-services""` config entry parameters and exporting services to other datacenters. --- -# Exported Services Configuration Entry +# Exported Services configuration reference This topic describes the `exported-services` configuration entry type. The `exported-services` configuration entry enables Consul to export service instances to other clusters from a single file and connect services across clusters. For additional information, refer to [Cluster Peering](/consul/docs/connect/cluster-peering) and [Admin Partitions](/consul/docs/enterprise/admin-partitions). diff --git a/website/content/docs/connect/config-entries/file-system-certificate.mdx b/website/content/docs/connect/config-entries/file-system-certificate.mdx index a2c37eb3a6..d633139a77 100644 --- a/website/content/docs/connect/config-entries/file-system-certificate.mdx +++ b/website/content/docs/connect/config-entries/file-system-certificate.mdx @@ -1,13 +1,15 @@ --- layout: docs -page_title: File System Certificate Configuration Reference +page_title: File system certificate configuration reference description: Learn how to configure a file system certificate bound to an API Gateway on VMs. --- # File system certificate configuration reference -This topic provides reference information for the gateway file system certificate -configuration entry. For information about certificate configuration for Kubernetes environments, refer to [Gateway Resource Configuration](/consul/docs/connect/gateways/api-gateway/configuration/gateway). +This topic provides reference information for the file system certificate +configuration entry. The file system certificate is a more secure alternative to the [inline certificate configuration entry](/consul/docs/connect/config-entries/inline-certificate) when using Consul API Gateway on VMs because it references a local filepath instead of including sensitive information in the configuration entry itself. File system certificates also include a file system watch that implements certificate and key changes without restarting the gateway. + +Consul on Kubernetes deployments that use `consul-k8s` Helm chart v1.5.0 or later use file system certificates without additional configuration. To learn about configuring certificates for Kubernetes environments, refer to [Gateway Resource Configuration](/consul/docs/connect/gateways/api-gateway/configuration/gateway). ## Configuration model @@ -15,7 +17,7 @@ The following list outlines field hierarchy, language-specific data types, and requirements in a `file-system-certificate` configuration entry. Click on a property name to view additional details, including default values. -- [`Kind`](#kind): string | must be `"file-system-certificate"` +- [`Kind`](#kind): string | must be set to `"file-system-certificate"` - [`Name`](#name): string | no default - [`Namespace`](#namespace): string | no default - [`Partition`](#partition): string | no default @@ -27,33 +29,43 @@ to view additional details, including default values. When every field is defined, a `file-system-certificate` configuration entry has the following form: - + -```HCL + + +```hcl Kind = "file-system-certificate" Name = "" - +Namespace = "ns" +Partition = "default" Meta = { - "" = "" + "" = "" } -Certificate = "" -PrivateKey = "" +Certificate = "" +PrivateKey = "" ``` -```JSON + + + + +```json { "Kind": "file-system-certificate", "Name": "", + "Namespace": "ns", + "Partition": "default", "Meta": { - "any key": "any value" - } - "Certificate": "", - "PrivateKey": "" + "key": "value" + }, + "Certificate": "", + "PrivateKey": "" } ``` - + + ## Specification @@ -63,7 +75,7 @@ Specifies the type of configuration entry to implement. #### Values -- Default: none +- Default: None - This field is required. - Data type: string that must equal `"file-system-certificate"` @@ -75,7 +87,7 @@ as applying a configuration entry to a specific cluster. #### Values -- Default: none +- Default: None - This field is required. - Data type: string @@ -103,12 +115,12 @@ Specifies an arbitrary set of key-value pairs to associate with the gateway. #### Values -- Default: none +- Default: None - Data type: map containing one or more keys and string values. ### `Certificate` -Specifies the filepath to a public certificate to use for TLS. This filepath must be accessible to the API gateway proxy at runtime. +Specifies the path to a file that contains a public certificate to use for TLS. This filepath must be accessible to the API gateway proxy at runtime. #### Values @@ -118,10 +130,41 @@ Specifies the filepath to a public certificate to use for TLS. This filepath mus ### `PrivateKey` -Specifies the filepath to a private key to use for TLS. This filepath must be accessible to the API gateway proxy at runtime. +Specifies the path to a file that contains a private key to use for TLS. This filepath must be accessible to the API gateway proxy at runtime. #### Values - Default: none - This field is required. - Data type: string value of the filepath to a private key + +## Examples + +The following example demonstrates a file system certificate configuration. + + + + + +```hcl +Kind = "file-system-certificate" +Name = "tls-certificate" +Certificate = "/opt/consul/tls/api-gateway.crt" +PrivateKey = "/opt/consul/tls/api-gateway.key" +``` + + + + + +```json +{ + "Kind": "file-system-certificate", + "Name": "tls-certificate", + "Certificate": "opt/consul/tls/api-gateway.crt", + "PrivateKey": "/opt/consul/tls/api-gateway.key" +} +``` + + + \ No newline at end of file diff --git a/website/content/docs/connect/config-entries/http-route.mdx b/website/content/docs/connect/config-entries/http-route.mdx index 2240088b1a..6265930b3e 100644 --- a/website/content/docs/connect/config-entries/http-route.mdx +++ b/website/content/docs/connect/config-entries/http-route.mdx @@ -1,6 +1,6 @@ --- layout: docs -page_title: HTTP Route Configuration +page_title: HTTP Route configuration reference description: Learn how to configure an HTTP Route bound to an API Gateway on VMs. --- diff --git a/website/content/docs/connect/config-entries/ingress-gateway.mdx b/website/content/docs/connect/config-entries/ingress-gateway.mdx index 07339ffb52..cd8eaf326f 100644 --- a/website/content/docs/connect/config-entries/ingress-gateway.mdx +++ b/website/content/docs/connect/config-entries/ingress-gateway.mdx @@ -1,11 +1,11 @@ --- layout: docs -page_title: Ingress gateway configuration entry reference +page_title: Ingress gateway configuration reference description: >- The ingress gateway configuration entry kind defines behavior for securing incoming communication between the service mesh and external sources. Learn about `""ingress-gateway""` config entry parameters for exposing TCP and HTTP listeners. --- -# Ingress gateway configuration entry reference +# Ingress gateway configuration reference diff --git a/website/content/docs/connect/config-entries/inline-certificate.mdx b/website/content/docs/connect/config-entries/inline-certificate.mdx index 496650f859..abf68dc9b2 100644 --- a/website/content/docs/connect/config-entries/inline-certificate.mdx +++ b/website/content/docs/connect/config-entries/inline-certificate.mdx @@ -1,13 +1,15 @@ --- layout: docs -page_title: Inline Certificate Configuration Reference +page_title: Inline certificate configuration reference description: Learn how to configure an inline certificate bound to an API Gateway on VMs. --- # Inline certificate configuration reference -This topic provides reference information for the gateway inline certificate -configuration entry. For information about certificate configuration for Kubernetes environments, refer to [Gateway Resource Configuration](/consul/docs/connect/gateways/api-gateway/configuration/gateway). +This topic provides reference information for the inline certificate +configuration entry. The inline certificate secures TLS for the Consul API gateway on VMs. In production environments, we recommend you use the more secure [file system certificate configuration entry](/consul/docs/connect/config-entries/file-system-certificate) instead. + +The inline certificate configuration entry is not used for Consul on Kubernetes deployments. To learn about configuring certificates for Kubernetes environments, refer to [Gateway Resource Configuration](/consul/docs/connect/gateways/api-gateway/configuration/gateway). ## Configuration model @@ -27,9 +29,11 @@ to view additional details, including default values. When every field is defined, an `inline-certificate` configuration entry has the following form: - + -```HCL + + +```hcl Kind = "inline-certificate" Name = "" @@ -41,19 +45,24 @@ Certificate = "" PrivateKey = "" ``` -```JSON + + + + +```json { "Kind": "inline-certificate", "Name": "", "Meta": { "any key": "any value" - } + }, "Certificate": "", "PrivateKey": "" } ``` - + + ## Specification @@ -125,3 +134,34 @@ Specifies the inline private key to use for TLS. - Default: none - This field is required. - Data type: string value of the private key + +## Examples + +The following example demonstrates an inline certificate configuration. + + + + + +```hcl +Kind = "inline-certificate" +Name = "tls-certificate" +Certificate = "" +PrivateKey = "" +``` + + + + + +```json +{ + "Kind": "inline-certificate", + "Name": "tls-certificate", + "Certificate": "", + "PrivateKey": "" +} +``` + + + \ No newline at end of file diff --git a/website/content/docs/connect/config-entries/jwt-provider.mdx b/website/content/docs/connect/config-entries/jwt-provider.mdx index 9ea7ce40ba..d1bedd9355 100644 --- a/website/content/docs/connect/config-entries/jwt-provider.mdx +++ b/website/content/docs/connect/config-entries/jwt-provider.mdx @@ -1,10 +1,10 @@ --- -page_title: JWT provider configuration entry reference +page_title: JWT provider configuration reference description: |- JWT provider configuration entries add JSON Web Token token validation to intentions in the service mesh. Learn how to write `jwt-provider` config entries in HCL or YAML with a specification reference, configuration model, a complete example, and example code by use case. --- -# JWT provider configuration entry reference +# JWT provider configuration reference This page provides reference information for the JWT provider configuration entry, which configures Consul to use a JSON Web Token (JWT) and JSON Web Key Set (JWKS) in order to add JWT validation to proxies in the service mesh. Refer to [Use JWT authorization with service intentions](/consul/docs/connect/intentions/jwt-authorization) for more information. diff --git a/website/content/docs/connect/config-entries/mesh.mdx b/website/content/docs/connect/config-entries/mesh.mdx index 4b5bb7ee2d..b64062e518 100644 --- a/website/content/docs/connect/config-entries/mesh.mdx +++ b/website/content/docs/connect/config-entries/mesh.mdx @@ -264,6 +264,58 @@ spec: Note that the Kubernetes example does not include a `partition` field. Configuration entries are applied on Kubernetes using [custom resource definitions (CRD)](/consul/docs/k8s/crds), which can only be scoped to their own partition. +### Request Normalization + +Enable options under `HTTP.Incoming.RequestNormalization` to apply normalization to all inbound traffic to mesh proxies. + + + +```hcl +Kind = "mesh" +HTTP { + Incoming { + RequestNormalization { + InsecureDisablePathNormalization = false // default false, shown for completeness + MergeSlashes = true + PathWithEscapedSlashesAction = "UNESCAPE_AND_FORWARD" + HeadersWithUnderscoresAction = "REJECT_REQUEST" + } + } +} +``` + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: Mesh +metadata: + name: mesh +spec: + http: + incoming: + requestNormalization: + insecureDisablePathNormalization: false # default false, shown for completeness + mergeSlashes: true + pathWithEscapedSlashesAction: UNESCAPE_AND_FORWARD + headersWithUnderscoresAction: REJECT_REQUEST +``` + +```json +{ + "Kind": "mesh", + "HTTP": { + "Incoming": { + "RequestNormalization": { + "InsecureDisablePathNormalization": false, + "MergeSlashes": true, + "PathWithEscapedSlashesAction": "UNESCAPE_AND_FORWARD", + "HeadersWithUnderscoresAction": "REJECT_REQUEST" + } + } + } +} +``` + + ## Available Fields @@ -342,6 +394,17 @@ Note that the Kubernetes example does not include a `partition` field. Configura description: 'Controls whether `MutualTLSMode=permissive` can be set in the `proxy-defaults` and `service-defaults` configuration entries. ' }, + { + name: 'ValidateClusters', + type: 'bool: false', + description: + `Controls whether the clusters the route table refers to are validated. The default value is false. When set to + false and a route refers to a cluster that does not exist, the route table loads and routing to a non-existent + cluster results in a 404. When set to true and the route is set to a cluster that do not exist, the route table + will not load. For more information, refer to + [HTTP route configuration in the Envoy docs](https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/route/v3/route.proto#envoy-v3-api-field-config-route-v3-routeconfiguration-validate-clusters) + for more details. `, + }, { name: 'TLS', type: 'TLSConfig: ', @@ -441,6 +504,57 @@ Note that the Kubernetes example does not include a `partition` field. Configura for all Envoy proxies. As a result, Consul will not include the \`x-forwarded-client-cert\` header in the next hop. If set to \`false\` (default), the XFCC header is propagated to upstream applications.`, }, + { + name: 'Incoming', + type: 'DirectionalHTTPConfig: ', + description: `HTTP configuration for inbound traffic to mesh proxies.`, + children: [ + { + name: 'RequestNormalization', + type: 'RequestNormalizationConfig: ', + description: `Request normalization configuration for inbound traffic to mesh proxies.`, + children: [ + { + name: 'InsecureDisablePathNormalization', + type: 'bool: false', + description: `Sets the value of the \`normalize_path\` option in the Envoy listener's \`HttpConnectionManager\`. The default value is \`false\`. + When set to \`true\` in Consul, \`normalize_path\` is set to \`false\` for the Envoy proxy. + This parameter disables the normalization of request URL paths according to RFC 3986, + conversion of \`\\\` to \`/\`, and decoding non-reserved %-encoded characters. When using L7 + intentions with path match rules, we recommend enabling path normalization in order + to avoid match rule circumvention with non-normalized path values.`, + }, + { + name: 'MergeSlashes', + type: 'bool: false', + description: `Sets the value of the \`merge_slashes\` option in the Envoy listener's \`HttpConnectionManager\`. The default value is \`false\`. + This option controls the normalization of request URL paths by merging consecutive \`/\` characters. This normalization is not part + of RFC 3986. When using L7 intentions with path match rules, we recommend enabling this setting to avoid match rule circumvention through non-normalized path values, unless legitimate service + traffic depends on allowing for repeat \`/\` characters, or upstream services are configured to + differentiate between single and multiple slashes.`, + }, + { + name: 'PathWithEscapedSlashesAction', + type: 'string: ""', + description: `Sets the value of the \`path_with_escaped_slashes_action\` option in the Envoy listener's + \`HttpConnectionManager\`. The default value of this option is empty, which is + equivalent to \`IMPLEMENTATION_SPECIFIC_DEFAULT\`. This parameter controls the action taken in response to request URL paths with escaped + slashes in the path. When using L7 intentions with path match rules, we recommend enabling this setting to avoid match rule circumvention through non-normalized path values, unless legitimate service + traffic depends on allowing for escaped \`/\` or \`\\\` characters, or upstream services are configured to + differentiate between escaped and unescaped slashes. Refer to the Envoy documentation for more information on available + options.`, + }, + { + name: 'HeadersWithUnderscoresAction', + type: 'string: ""', + description: `Sets the value of the \`headers_with_underscores_action\` option in the Envoy listener's + \`HttpConnectionManager\` under \`common_http_protocol_options\`. The default value of this option is + empty, which is equivalent to \`ALLOW\`. Refer to the Envoy documentation for more information on available options.`, + }, + ], + }, + ], + } ], }, { diff --git a/website/content/docs/connect/config-entries/proxy-defaults.mdx b/website/content/docs/connect/config-entries/proxy-defaults.mdx index 90ffb4eaa4..824ce83599 100644 --- a/website/content/docs/connect/config-entries/proxy-defaults.mdx +++ b/website/content/docs/connect/config-entries/proxy-defaults.mdx @@ -659,7 +659,7 @@ Example use-cases include exposing the `/metrics` endpoint to a monitoring syste - Default: None - Data type: Map containing the following parameters: - [`checks`](#expose-checks) - - [`aths`](#expose-paths) + - [`paths`](#expose-paths) ### `spec.expose{}.checks` diff --git a/website/content/docs/connect/config-entries/registration.mdx b/website/content/docs/connect/config-entries/registration.mdx new file mode 100644 index 0000000000..95740cb084 --- /dev/null +++ b/website/content/docs/connect/config-entries/registration.mdx @@ -0,0 +1,670 @@ +--- +layout: docs +page_title: Registration CRD configuration reference +description: The Registration CRD enables Consul on Kubernetes to register an external service without a terminating gateway. Learn how to configure a Registration CRD in YAML with a specification reference, configuration model, a complete example, and example code. +--- + +# Registration CRD configuration reference + +This topic provides reference information for `Registration` custom resource definitions (CRDs). You can use this CRD to register an external service with Consul on Kubernetes. For more information. Refer to [Register services running on external nodes to Consul on Kubernetes](/consul/docs/k8s/deployment-configurations/external-service) for more information. + +## Configuration model + +The following list outlines field hierarchy, language-specific data types, and requirements in a `Registration` CRD. Click on a property name to view additional details, including default values. + +- [`apiVersion`](#apiversion): string | required | must be set to `consul.hashicorp.com/v1alpha1` +- [`kind`](#kind): string | required | must be set to `Registration` +- [`metadata`](#metadata): map + - [`name`](#metadata-name): string +- [`spec`](#spec): map | required + - [`address`](#spec-address): string + - [`check`](#spec-check): map + - [`checkId`](#spec-check-checkid): string + - [`definition`](#spec-check-definition): map + - [`body`](#spec-check-definition): string + - [`deregisterCriticalServiceAfterDuration`](#spec-check-definition): string + - [`grpc`](#spec-check-definition): string + - [`grpcUseTLS`](#spec-check-definition): boolean | `false` + - [`header`](#spec-check-definition): map of strings + - [`http`](#spec-check-definition): string + - [`intervalDuration`](#spec-check-definition): string + - [`method`](#spec-check-definition): string + - [`osService`](#spec-check-definition): string + - [`tcp`](#spec-check-definition): string + - [`tcpUseTLS`](#spec-check-definition): boolean + - [`timeoutDuration`](#spec-check-definition): string + - [`tlsServerName`](#spec-check-definition): string + - [`tlsSkipVerify`](#spec-check-definition): boolean | `false` + - [`udp`](#spec-check-definition): string + - [`exposedPort`](#spec-check-exposed-port): integer + - [`name`](#spec-check-name): string + - [`namespace`](#spec-check-namespace): string + - [`node`](#spec-check-node): string + - [`notes`](#spec-check-notes): string + - [`output`](#spec-check-output): string + - [`partition`](#spec-check-partition): string + - [`serviceId`](#spec-check-serviceid): string + - [`serviceName`](#spec-check-servicename): string + - [`status`](#spec-check-status): string + - [`type`](#spec-check-type): string + - [`datacenter`](#spec-datacenter): string + - [`id`](#spec-id): string + - [`locality`](#spec-locality): map + - [`region`](#spec-locality): string + - [`zone`](#spec-locality): string + - [`node`](#spec-node): string + - [`nodeMeta`](#spec-nodemeta): map of strings + - [`partition`](#spec-partition): string + - [`service`](#spec-service): map + - [`address`](#spec-service-address): string + - [`enableTagOverride`](#spec-service-enabletagoverride): boolean | `false` + - [`id`](#spec-service-id): string + - [`locality`](#spec-service-locality): map + - [`region`](#spec-service-locality): string + - [`zone`](#spec-service-locality): string + - [`meta`](#spec-service-meta): map of strings + - [`name`](#spec-service-name): string + - [`namespace`](#spec-service-namespace): string + - [`partition`](#spec-service-partition): string + - [`port`](#spec-service-port): integer + - [`socketPath`](#spec-service-socketpath): string + - [`taggedAddresses`](#spec-service-taggedaddresses): map + - [`address`](#spec-service-taggedaddresses): string + - [`port`](#spec-service-port): integer + - [`tags`](#spec-service-tags): map of strings + - [`weights`](#spec-service-weights): map + - [`passing`](#spec-service-weights): integer | `1` + - [`warning`](#spec-service-weights): integer | `1` + - [`skipNodeUpdate`](#spec-skipnodeupdate): boolean | `false` + - [`taggedAddresses`](#spec-taggedaddresses): map of strings + +## Complete configuration + +When every field is defined, a `Registration` CRD has the following form: + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 # required +kind: Registration # required +metadata: + name: +spec: + address: 10.0.0.1 + check: + name: external-health-check + checkId: unique-id + definition: + body: "{\"custom\": \"json\"}" + deregisterCriticalServiceAfterDuration: false + grpc: 127.0.0.1:443 + grpcUseTLS: false + header: + name: + intervalDuration: "5s" + method: "GET" + osService: + tcp: 127.0.0.1:4242 + tcpUseTLS: false + timeoutDuration: 10s + tlsServerName: + tlsSkipVerify: false + udp: 127.0.0.1:80 + exposedPort: 200 + namespace: default + notes: "Human readable description" + output: "Human readable output" + partition: default + serviceId: + serviceName: + status: critical + type: HTTP + datacenter: dc1 + id: + locality: + region: us-east-1 + zone: us-east-1a + node: + nodeMeta: + - key: value + partition: default + service: + address: "10.0.0.1:8300" + enableTagOverride: false + id: + locality: + region: us-east-1 + zone: us-east-1a + meta: + key: value + name: + namespace: default + partition: default + port: 5400 + socketPath: /folder/socket + taggedAddresses: + lan: + address: 127.0.1.0 + port: 80 + tags: + - "v2" + - "primary" + weights: + passing: 1 + warning: 1 + skipNodeUpdate: false + taggedAddresses: + lan: "192.168.10.10" +``` + +## Specification + +This section provides details about the fields you can configure in the `Registration` CRD. + +### `apiVersion` + +Specifies the version of the Consul API for integrating with Kubernetes. The value must be `consul.hashicorp.com/v1alpha1`. + +#### Values + +- Default: None +- This field is required. +- String value that must be set to `consul.hashicorp.com/v1alpha1`. + +### `kind` + +Specifies the type of configuration entry to implement. Must be set to `Registration`. + +#### Values + +- Default: None +- This field is required. +- Data type: String value that must be set to `Registration`. + +### `metadata` + +Map that contains an arbitrary name for the configuration entry and the namespace it applies to. + +#### Values + +- Default: None +- Data type: Map + +### `metadata.name` + +Specifies a name for the configuration entry that is used to identify the sameness group. To ensure consistency, use descriptive names and make sure that the same name is used when creating configuration entries to add each member to the sameness group. + +#### Values + +- Default: None +- This field is required. +- Data type: String + +### `spec` + +Map that contains the details about the `Registration` CRD. The `apiVersion`, `kind`, and `metadata` fields are siblings of the spec field. All other configurations are children. + +#### Values + +- Default: None +- This field is required. +- Data type: Map + +### `spec.address` + +Specifies the IP address where the external service is available. + +#### Values + +- Default: None +- Data type: String + +### `spec.check` + +Specifies details for a health check. Health checks perform several safety functions, such as allowing a web balancer to gracefully remove failing nodes and allowing a database to replace a failed secondary. You can configure health checks to monitor the health of the entire node. + +For more information about configuring health checks for Consul, refer to [health check configuration reference](/consul/docs/services/configuration/checks-configuration-reference). + +#### Values + +- Default: None +- Data type: Map + +### `spec.check.checkId` + +Specifies an ID for the health check. When `name` values conflict, provide a unique identifier to avoid overwriting existing checks. + +#### Values + +- Default: None +- Data type: String + +### `spec.check.definition` + +Specifies additional configuration details for the health check. + +Requires child parameters `deregisterCriticalServiceAfterDuration`, `intervalDuration`, and `timeoutDuration`. + +#### Values + +- Default: None +- Data type: Map that can contain the following parameters: + +| Parameter | Description | Type | Default | +| :------------------------------------------------------------- | :---------- | :---------- | :---------- | +| `spec.check.definition.body` | Specifies JSON attributes to send in HTTP check requests. You must escape the quotation marks around the keys and values for each attribute. | String | None | +| `spec.check.definition.deregisterCriticalServiceAfterDuration` | Specifies how long a service and its associated checks are allowed to be in a `critical` state. Consul deregisters services if they are `critical` for the specified amount of time. This parameter is required to configure `spec.check.definition`. | String | `"30s"` | +| `spec.check.definition.grpc` | Specifies the gRPC endpoint, including port number, to send requests to. Append the endpoint with `:/` and a service identifier to check a specific service. The endpoint must support the [gRPC health checking protocol](https://github.com/grpc/grpc/blob/master/doc/health-checking.md). | String | None | +| `spec.check.definition.grpcUseTLS` | Enables TLS for gRPC checks when set to `true`. | Boolean | `false` | +| `spec.check.definition.header` | Specifies header fields to send in HTTP check requests. | Map of strings | None | +| `spec.check.definition.http` | Specifies an HTTP endpoint to send requests to. | String | None | +| `spec.check.definition.intervalDuration` | Specifies how frequently to run the health check. This parameter is required to configure `spec.check.definition`. | String | `"5s"` | +| `spec.check.definition.method` | Specifies the request method to send during HTTP checks. | String | `GET` | +| `spec.check.definition.osService` | Specifies the name of the name of a service to check during an OSService check. | String | None | +| `spec.check.definition.tcp` | Specifies an IP address or host and port number that the check establishes a TCP connection with. | String | None | +| `spec.check.definition.tcpUseTLS` | Enables TLS for TCP checks when set to `true`. | Boolean | `false` | +| `spec.check.definition.timeoutDuration` | Specifies how long unsuccessful requests take to end with a timeout. This parameter is required to configure `spec.check.definition`. | String | `"10s"` | +| `spec.check.definition.tlsServerName` | Specifies the server name used to verify the hostname on the returned certificates unless `tls_skip_verify` is configured. This value is also included in the client's handshake to support SNI. It is recommended that this field be left unspecified. Refer to [health check configuration reference](/consul/docs/services/configuration/checks-configuration-reference#check-block) for more information. | String | None | +| `spec.check.definition.tlsSkipVerify` | Determines if the check verifies the chain and hostname of the certificate that the server presents. Set to `true` to disable verification. We recommend setting to `false` in production environments. | Boolean | `false` | +| `spec.check.definition.udp` | Specifies an IP address or host and port number for the check to send UDP datagrams to. | String | None | + +### `spec.check.exposedPort` + +Specifies the port the service exposes to health checks. + +#### Values + +- Default: None +- Data type: Integer + +### `spec.check.name` + +Specifies a name for the health check. Defaults to [`spec.service.id`](#spec-service-id). + +#### Values + +- Default: None +- Data type: String + +### `spec.check.namespace` + +Specifies the Consul namespace the health check applies to. Refer to [namespaces](/consul/docs/enterprise/namespace) for more information. + +#### Values + +- Default: None +- Data type: String + +### `spec.check.node` + +Specifies the name of the node the health check applies to. + +#### Values + +- Default: None +- Data type: String + +### `spec.check.notes` + +Provides a human-readable description of the health check. The contents are not visible to Consul. + +#### Values + +- Default: None +- Data type: String + +### `spec.check.output` + +Specifies human readable output in response to a health check. + +#### Values + +- Default: None +- Data type: String + +### `spec.check.partition` + +Specifies the Consul admin partition the health check applies to. Refer to [admin partitions](/consul/docs/enterprise/admin-partitions) for more information. + +#### Values + +- Default: None +- Data type: String + +### `spec.check.serviceId` + +Specifies the ID of the service to perform the health check on. + +#### Values + +- Default: None +- Data type: String + +### `spec.check.serviceName` + +Specifies the name of the service to perform the health check on. + +#### Values + +- Default: None +- Data type: String + +### `spec.check.status` + +Specifies the initial status of the health check. The default value for this field is `critical`, which requires services to pass a health check before making them available for service disocvery. You can specify one the following values: + +- `critical` +- `warning` +- `passing` + +#### Values + +- Default: `critical` +- Data type: String + +### `spec.check.type` + +Specifies the type health check in the form of a Kubernetes probe. + +#### Values + +- Default: None +- Data type: String + +### `spec.datacenter` + +Specifies the datacenter to register the service's node in, which defaults to the agent's datacenter if not provided. + +#### Values + +- Default: None +- Data type: String + +### `spec.id` + +Specifies a unique ID for the node to register. This optional ID must be a 36-character UUID-formatted string. + +#### Values + +- Default: None +- Data type: String + +### `spec.locality` + +Specifies the cloud region and zone where the node is available. + +#### Values + +- Default: None +- Data type: Map that can contain the following parameters: + +| Parameter | Description | Data type | Default | +| :-------- | :---------- | :-------- | :------ | +| `spec.locality.region` | Specifies the region where the node is running. Consul assigns this value to services registered to that agent. When service proxy regions match, Consul is able to prioritize routes between service instances in the same region over instances in other regions. You must specify values that are consistent with how regions are defined in your network, for example `us-west-1` for networks in AWS. | String | None | +| `spec.locality.zone` | Specifies the availability zone where the node is running. Consul assigns this value to services registered to that agent. When service proxy regions match, Consul is able to prioritize routes between service instances in the same region and zone over instances in other regions and zones. When healthy service instances are available in multiple zones within the most-local region, Consul prioritizes instances that also match the downstream proxy's `zone`. You must specify values that are consistent with how zones are defined in your network, for example `us-west-1a` for networks in AWS. | String | None | + +### `spec.node` + +Specifies the name of the node to register. + +#### Values + +- Default: None +- Data type: String + +### `spec.nodeMeta` + +Specifies arbitrary KV metadata pairs for filtering purposes. + +#### Values + +- Default: None +- Data type: Map of strings + +### `spec.partition` + +Specifies the admin partition of the node to register. + +#### Values + +- Default: None +- Data type: String + +### `spec.service` + +Specifies the service to register. The `Service.Service` field is required. If `Service.ID` is not provided, the default is the `Service.Service`. + +You can only specify one service with a given `ID` per node. We recommend using valid DNS labels for service definition names. Refer to [Internet Engineering Task Force's RFC 1123](https://datatracker.ietf.org/doc/html/rfc1123#page-72) for additional information. Service names that conform to standard usage ensures compatibility with external DNSs. Refer to [Services Configuration Reference](/consul/docs/services/configuration/services-configuration-reference#name) for more information. + +#### Values + +- Default: None +- Data type: Map + +### `spec.service.address` + +Specifies a service-specific IP address or hostname. If no value is specified, the IP address of the agent node is used by default. There is no service-side validation of this parameter. + +#### Values + +- Default: None +- Data type: String + +### `spec.service.enableTagOverride` + +Setermines if the anti-entropy feature for the service is enabled. + +Set to `true` to allow external Consul agents to modify tags on the services in the Consul catalog. The local Consul agent ignores updated tags during subsequent sync operations. + +This parameter only applies to the locally-registered service. When multiple nodes register a service with the same name, the `enable_tag_override` configuration and all other service configuration items operate independently. + +Refer to [Modify anti-entropy synchronization](/consul/docs/services/usage/define-services#modify-anti-entropy-synchronization) for additional information. + +#### Values + +- Default: `false` +- Data type: Boolean + +### `spec.service.id` + +Specifies an ID for the service. Services on the same node must have unique IDs. We recommend specifying unique values if the default name conflicts with other services. + +#### Values + +- Default: None +- Data type: String + +### `spec.service.locality` + +Specifies the region and zone in the cloud service provider (CSP) where the service is available. Configure this field to enable Consul to route traffic to the nearest physical service instance. Services inherit the `locality` configuration of the Consul agent they are registered with, but you can explicitly define locality for your service instances if an override is needed. Refer to [Route traffic to local upstreams](/consul/docs/connect/manage-traffic/route-to-local-upstreams) for additional information. + +#### Values + +- Default: None +- Data type: Map that can contain the following parameters: + +| Parameter | Description | Data type | Default | +| :-------- | :---------- | :-------- | :------ | +| `spec.service.locality.region` | Specifies the region where the Consul agent is running. Consul assigns this value to services registered to that agent. When service proxy regions match, Consul is able to prioritize routes between service instances in the same region over instances in other regions. You must specify values that are consistent with how regions are defined in your network, for example `us-west-1` for networks in AWS. | String | None | +| `spec.service.locality.zone` | Specifies the availability zone where the Consul agent is running. Consul assigns this value to services registered to that agent. When service proxy regions match, Consul is able to prioritize routes between service instances in the same region and zone over instances in other regions and zones. When healthy service instances are available in multiple zones within the most-local region, Consul prioritizes instances that also match the downstream proxy's `zone`. You must specify values that are consistent with how zones are defined in your network, for example `us-west-1a` for networks in AWS. | String | None | + +### `spec.service.meta` + +Specifies custom key-value pairs that associate semantic metadata with the service. You can specify up to 64 pairs that meet the following requirements: + +- Keys and values must be strings. +- Keys can only contain ASCII characters (`A` -` Z`, `a`- `z`, `0` - `9`, `_`, and `-`). +- Keys can not have special characters. +- Keys are limited to 128 characters. +- Values are limited to 512 characters. + +#### Values + +- Default: None +- Data type: Map of strings + +### `spec.service.name` + +Specifies a name for the service. We recommend using valid DNS labels for service definition names for compatibility with external DNSs. The value of this parameter is also used as the service ID if the `spec.service.id` parameter is not specified. + +#### Values + +- Default: None +- Data type: String + +### `spec.service.namespace` + +Specifies the Consul namespace to register the service in. Refer to [namespaces](/consul/docs/enterprise/namespaces) for more information. + +#### Values + +- Default: None +- Data type: String + +### `spec.service.partition` + +Specifies the Consul admin partition to register the service in. Refer to [admin partitions](/consul/docs/enterprise/admin-partitions) for more information. + +#### Values + +- Default: None +- Data type: String + +### `spec.service.port` + +Specifies a port number for the service. To improve service discoverability, we recommend specifying the port number, as well as an address in the tagged_addresses parameter. + +#### Values + +- Default: None +- Data type: Integer + +### `spec.service.socketPath` + +Specifies the path to the service socket. Specify this parameter to expose the service to the mesh if the service listens on a Unix Domain socket. + +#### Values + +- Default: None +- Data type: String + +### `spec.service.taggedAddresses` + +Configures additional addresses for a node or service. Remote agents and services can communicate with the service using a tagged address as an alternative to the address specified in [`spec.service.address`](#spec-serviceaddress). You can configure multiple addresses for a node or service. The following tags are supported: + +- `lan`: IPv4 LAN address where the node or service is accessible. +- `lan_ipv4`: IPv4 LAN address where the node or service is accessible. +- `lan_ipv6`: IPv6 LAN address where the node or service is accessible. +- `virtual`: A fixed address for the instances of a given logical service. +- `wan`: IPv4 WAN address where the node or service is accessible when dialed from a remote data center. +- `wan_ipv4`: IPv4 WAN address where the node or service is accessible when dialed from a remote data center. +- `wan_ipv6`: IPv6 WAN address at which the node or service is accessible when being dialed from a remote data center. + +#### Values + +- Default: None +- Data type: Map that contains a supported tag and a child map containing the following fields: + +| Parameter | Description | Type | Default | +| :------------------------------------- | :---------------------------- | :---------- | :---------- | +| `spec.service.taggedAddresses.address` | The address saved in the tag. | String | None | +| `spec.service.taggedAddresses.port` | The port saved in the tag. | String | None | + +### `spec.service.tags` + +Specifies a list of string values that add service-level labels. Tag values are opaque to Consul. We recommend using valid DNS labels for service definition IDs for compatibility with external DNSs. In the following example, the service is tagged as `v2` and `primary`: + +```yaml +spec: + service: + tags: ["v2", "primary"] +``` + +Consul uses tags as an anti-entropy mechanism to maintain the state of the cluster. You can disable the anti-entropy feature for a service using [`spec.service.enable_tag_override`](#spec-service-enabletagoverride), which enables external agents to modify tags on services in the catalog. Refer to [Modify anti-entropy synchronization](/consul/docs/services/usage/define-services#modify-anti-entropy-synchronization) for additional usage information. + +#### Values + +- Default: None +- Data type: Map of strings + +### `spec.service.weights` + +Configures how a service instance is weighted in a DNS SRV request based on the service's health status. Configuring tells DNS clients to direct more traffic to instances with a higher weight. A use case would be adjusting the weight higher for an instance with large capacity. It could also be used to reduce the load on services with checks in `warning` status by favoring passing instances with a higher weight. + +Larger integer values increase the weight state. + +You can specify one or more of the following states and configure an integer value indicating its weight: + +- `passing` +- `warning` + +Services in a `critical` state are excluded from DNS responses. Services with `warning` checks are included in responses by default. + +#### Values + +- Default: None +- Data type: Map containing the following parameters: + +| Parameter | Description | Type | Default | +| :----------------------------- | :---------------------------- | :---------- | :---------- | +| `spec.service.weights.passing` | Higher values increases the likelihood that a request is routed to a service in a `passing` state. | Integer | `1` | +| `spec.service.weights.warning` | Higher values increases the likelihood that a request is routed to a service in a `warning` state. | Integer | `1` | + +### `spec.skipNodeUpdate` + +Specifies whether to skip updating the node's information in the registration. This field is useful in casew where only a health check or service entry on a node needs to be updated or when a register request is intended to update a service entry or health check. In both use cases, node information is not be overwritten, if the node is already registered. Note, if the parameter is enabled for a node that does not exist, it is still be created. + +#### Values + +- Default: `false` +- Data type: Boolean + +### `spec.taggedAddresses` + +Specifies tagged addresses for the node. Remote agents and services can communicate with the service using a tagged address as an alternative to the address specified in [`spec.address`](#spec-serviceaddress). You can configure multiple addresses for a node or service. The following tags are supported: + +- `lan`: IPv4 LAN address where the node or service is accessible. +- `lan_ipv4`: IPv4 LAN address where the node or service is accessible. +- `lan_ipv6`: IPv6 LAN address where the node or service is accessible. +- `virtual`: A fixed address for the instances of a given logical service. +- `wan`: IPv4 WAN address where the node or service is accessible when dialed from a remote data center. +- `wan_ipv4`: IPv4 WAN address where the node or service is accessible when dialed from a remote data center. +- `wan_ipv6`: IPv6 WAN address at which the node or service is accessible when being dialed from a remote data center. + +#### Values + +- Default: None +- Data type: Map of strings + +## Examples + +The following example demonstrates a common `Registration` CRD configuration pattern. In this example, Consul registers a database service available at the IP address `10.96.32.66` that runs outside of the Kubernetes cluster where Consul is deployed. Consul runs an HTTP health check on this service every 10 seconds. + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: Registration +metadata: + name: external-database +spec: + node: node-virtual + check: + node: node-virtual + checkId: db-check + name: db + serviceName: db + serviceId: db-external + notes: "Runs the external health check for the database." + status: "passing" + definition: + http: "10.96.32.66:8081/health" + intervalDuration: "10s" + timeoutDuration: "10s" + deregisterCriticalServiceAfterDuration: "30s" + service: + name: db + id: db-external + address: "10.96.32.66" + port: 8081 + weights: + passing: 1 + warning: 1 + address: 10.96.32.66 +``` \ No newline at end of file diff --git a/website/content/docs/connect/config-entries/sameness-group.mdx b/website/content/docs/connect/config-entries/sameness-group.mdx index 6e3b248bdc..f9fdffcd7a 100644 --- a/website/content/docs/connect/config-entries/sameness-group.mdx +++ b/website/content/docs/connect/config-entries/sameness-group.mdx @@ -1,10 +1,10 @@ --- -page_title: Sameness group configuration entry reference +page_title: Sameness group configuration reference description: |- Sameness groups enable Consul to associate service instances with the same name deployed to the same namespace as identical services. Learn how to configure a `sameness-group` configuration entry to enable failover between partitions and cluster peers in non-federated networks. --- -# Sameness groups configuration entry reference +# Sameness groups configuration reference This page provides reference information for sameness group configuration entries. Sameness groups associate identical admin partitions to facilitate traffic between identical services. When partitions are part of the same Consul datacenter, you can create a sameness group by listing them in the `Members[].Partition` field. When partitions are located on remote clusters, you must establish cluster peering connections between remote partitions in order to add them to a sameness group in the `Members[].Peer` field. @@ -152,6 +152,8 @@ When this field is set to `true`, upstream requests automatically fail over to s When this field is set to `false`, you can use a sameness group for failover by configuring the `Failover` block of a [service resolver configuration entry](/consul/docs/connect/config-entries/service-resolver). +When you [query Consul DNS](/consul/docs/services/discovery/dns-static-lookups) using sameness groups, `DefaultForFailover` must be set to `true`. Otherwise, Consul DNS returns an error. + #### Values - Default: `false` @@ -228,7 +230,7 @@ Specifies the type of configuration entry to implement. Must be set to `Sameness - This field is required. - Data type: String value that must be set to `SamenessGroup`. -## `metadata` +### `metadata` Map that contains an arbitrary name for the configuration entry and the namespace it applies to. diff --git a/website/content/docs/connect/config-entries/service-defaults.mdx b/website/content/docs/connect/config-entries/service-defaults.mdx index 697aa36745..271256bbae 100644 --- a/website/content/docs/connect/config-entries/service-defaults.mdx +++ b/website/content/docs/connect/config-entries/service-defaults.mdx @@ -1,6 +1,6 @@ --- layout: docs -page_title: Service defaults configuration entry reference +page_title: Service defaults configuration reference description: -> Use the service-defaults configuration entry to set default configurations for services, such as upstreams, protocols, and namespaces. Learn how to configure service-defaults. --- diff --git a/website/content/docs/connect/config-entries/service-intentions.mdx b/website/content/docs/connect/config-entries/service-intentions.mdx index ff633dca3d..4440b2a76c 100644 --- a/website/content/docs/connect/config-entries/service-intentions.mdx +++ b/website/content/docs/connect/config-entries/service-intentions.mdx @@ -1,11 +1,11 @@ --- layout: docs -page_title: Service intentions configuration entry reference +page_title: Service intentions configuration reference description: >- Use the service intentions configuration entry to allow or deny traffic to services in the mesh from specific sources. Learn how to configure `service-intention` config entries --- -# Service intentions configuration entry reference +# Service intentions configuration reference This topic provides reference information for the service intentions configuration entry. Intentions are configurations for controlling access between services in the service mesh. A single service intentions configuration entry specifies one destination service and one or more L4 traffic sources, L7 traffic sources, or combination of traffic sources. Refer to [Service mesh intentions overview](/consul/docs/connect/intentions) for additional information. @@ -96,7 +96,9 @@ The following outline shows how to format the service intentions configuration e - [`exact`](#spec-sources-permissions-http-header): string | no default - [`prefix`](#spec-sources-permissions-http-header): string | no default - [`suffix`](#spec-sources-permissions-http-header): string | no default + - [`contains`](#spec-sources-permissions-http-header): string | no default - [`regex`](#spec-sources-permissions-http-header): string | no default + - [`ignoreCase`](#spec-sources-permissions-http-header): boolean | `false` - [`invert`](#spec-sources-permissions-http-header): boolean | `false` - [`description`](#spec-sources-description): string @@ -156,18 +158,31 @@ Sources = [ { Name = "" # string Present = # boolean + Invert = # boolean }, { Name = "" # string Exact = "" # boolean + IgnoreCase = # boolean + Invert = # boolean }, { Name = "" # string Prefix = "" # string + IgnoreCase = # boolean + Invert = # boolean }, { Name = "" # string Suffix = "" # string + IgnoreCase = # boolean + Invert = # boolean + }, + { + Name = "" # string + Contains = "" # string + IgnoreCase = # boolean + Invert = # boolean }, { Name = "" # string @@ -227,12 +242,23 @@ spec: header: - name: present: true + invert: false - name: - exact: false + exact: + ignoreCase: false + invert: false - name: prefix: + ignoreCase: false + invert: false - name: suffix: + ignoreCase: false + invert: false + - name: + contains: + ignoreCase: false + invert: false - name: regex: invert: false @@ -287,19 +313,32 @@ spec: "Header":[ { "Name":"", - "Present":true + "Present":true, + "Invert":false }, { "Name":"", - "Exact":false + "Exact":"", + "IgnoreCase":false,, + "Invert":false }, { "Name":"", - "Prefix":"" + "Prefix":"", + "IgnoreCase":false, + "Invert":false }, { "Name":"", - "Suffix":"" + "Suffix":"", + "IgnoreCase":false, + "Invert":false + }, + { + "Name":"", + "Contains":"", + "IgnoreCase":false, + "Invert":false }, { "Name":"", @@ -923,16 +962,22 @@ Specifies a set of criteria for matching HTTP request headers. The request heade - Default: None - Data type: List of maps -Each member of the `header` list is a map that contains a `name` field and at least one match criterion. The following table describes the parameters that each member of the `header` list may contain: +Each member of the `header` list is a map that contains a `name` field and at least one match criterion. + +~> **Warning**: If it is possible for a header to contain multiple values, we recommend using `contains` or `regex` rather than `exact`, `prefix`, or `suffix`. Envoy internally concatenates multiple header values into a single CSV value prior to applying match rules, which may result in match rules that depend on the beginning or end of a string vulnerable to circumvention. A more robust alternative is using `contains` or, if a stricter value match is required, configuring a regex pattern that is tolerant of comma-separated values. + +The following table describes the parameters that each member of the `header` list may contain: | Parameter | Description | Data type | Required | | --- | --- | --- | --- | | `name` | Specifies the name of the header to match. | string | required | -| `present` | Enables a match if the header configured in the `name` field appears in the request. Consul matches on any value as long as the header key appears in the request. Do not specify `present` if `exact`, `prefix`, `suffix`, or `regex` are configured in the same `header` configuration. | boolean | optional | -| `Exact` | Specifies a value for the header key set in the `Name` field. If the request header value matches the `exact` value, Consul applies the permission. Do not specify `exact` if `present`, `prefix`, `suffix`, or `regex` are configured in the same `header` configuration. | string | optional | -| `prefix` | Specifies a prefix value for the header key set in the `name` field. If the request header value starts with the `prefix` value, Consul applies the permission. Do not specify `prefix` if `present`, `exact`, `suffix`, or `regex` are configured in the same `header` configuration. | string | optional | -| `suffix` | Specifies a suffix value for the header key set in the `name` field. If the request header value ends with the `suffix` value, Consul applies the permission. Do not specify `suffix` if `present`, `exact`, `prefix`, or `regex` are configured in the same `header` configuration. | string | optional | -| `regex` | Specifies a regular expression pattern as the value for the header key set in the `name` field. If the request header value matches the regex, Consul applies the permission. Do not specify `regex` if `present`, `exact`, `prefix`, or `suffix` are configured in the same `header` configuration. The regex syntax is proxy-specific. If using Envoy, refer to the [re2 documentation](https://github.com/google/re2/wiki/Syntax) for details. | string | optional | +| `present` | Enables a match if the header configured in the `name` field appears in the request. Consul matches on any value as long as the header key appears in the request. Do not specify `present` if `exact`, `prefix`, `suffix`, `contains`, or `regex` are configured in the same `header` configuration. | boolean | optional | +| `Exact` | Specifies a value for the header key set in the `Name` field. If the request header value matches the `exact` value, Consul applies the permission. Do not specify `exact` if `present`, `prefix`, `suffix`, `contains`, or `regex` are configured in the same `header` configuration. | string | optional | +| `prefix` | Specifies a prefix value for the header key set in the `name` field. If the request header value starts with the `prefix` value, Consul applies the permission. Do not specify `prefix` if `present`, `exact`, `suffix`, `contains`, or `regex` are configured in the same `header` configuration. | string | optional | +| `suffix` | Specifies a suffix value for the header key set in the `name` field. If the request header value ends with the `suffix` value, Consul applies the permission. Do not specify `suffix` if `present`, `exact`, `prefix`, `contains`, or `regex` are configured in the same `header` configuration. | string | optional | +| `contains` | Specifies a contains value for the header key set in the `name` field. If the request header value includes the `contains` value, Consul applies the permission. Do not specify `contains` if `present`, `exact`, `prefix`, `suffix`, or `regex` are configured in the same `header` configuration. | string | optional | +| `regex` | Specifies a regular expression pattern as the value for the header key set in the `name` field. If the request header value matches the regex, Consul applies the permission. Do not specify `regex` if `present`, `exact`, `prefix`, `suffix`, or `contains` are configured in the same `header` configuration. The regex syntax is proxy-specific. If using Envoy, refer to the [re2 documentation](https://github.com/google/re2/wiki/Syntax) for details. | string | optional | +| `ignoreCase` | Ignores the case of the provided header value when matching with exact, prefix, suffix, or contains. Default is `false`. | boolean | optional | | `invert` | Inverts the matching logic configured in the `header`. Default is `false`. | boolean | optional | ### `spec.sources[].type` diff --git a/website/content/docs/connect/config-entries/service-resolver.mdx b/website/content/docs/connect/config-entries/service-resolver.mdx index 4358e49b1c..bce568514d 100644 --- a/website/content/docs/connect/config-entries/service-resolver.mdx +++ b/website/content/docs/connect/config-entries/service-resolver.mdx @@ -1,11 +1,11 @@ --- layout: docs -page_title: Service Resolver Configuration Entry Reference +page_title: Service Resolver configuration reference description: >- Service resolver configuration entries are L7 traffic management tools for defining sets of service instances that resolve upstream requests and Consul’s behavior when resolving them. Learn how to write `service-resolver` config entries in HCL or YAML with a specification reference, configuration model, a complete example, and example code by use case. --- -# Service resolver configuration entry reference +# Service resolver configuration reference This page provides reference information for service resolver configuration entries. Configure and apply service resolvers to create named subsets of service instances and define their behavior when satisfying upstream requests. diff --git a/website/content/docs/connect/config-entries/service-router.mdx b/website/content/docs/connect/config-entries/service-router.mdx index 814763957c..d384b83be3 100644 --- a/website/content/docs/connect/config-entries/service-router.mdx +++ b/website/content/docs/connect/config-entries/service-router.mdx @@ -1,11 +1,11 @@ --- layout: docs -page_title: Service Router Configuration Entry Reference +page_title: Service Router configuration reference description: >- Service router configuration entries are L7 traffic management tools for redirecting requests for a service to a particular instance or set of instances. Learn how to write `service-router` config entries in HCL or YAML with a specification reference, configuration model, a complete example, and example code by use case. --- -# Service router configuration entry reference +# Service router configuration reference This page provides reference information for service router configuration entries. Service routers use L7 network information to redirect a traffic request for a service to one or more specific service instances. diff --git a/website/content/docs/connect/config-entries/service-splitter.mdx b/website/content/docs/connect/config-entries/service-splitter.mdx index 5d94a4e38a..f7247be924 100644 --- a/website/content/docs/connect/config-entries/service-splitter.mdx +++ b/website/content/docs/connect/config-entries/service-splitter.mdx @@ -1,13 +1,13 @@ --- layout: docs -page_title: Service Splitter Configuration Entry Reference +page_title: Service Splitter configuration reference description: >- Service splitter configuration entries are L7 traffic management tools for redirecting requests for a service to multiple instances. Learn how to write `service-splitter` config entries in HCL or YAML with a specification reference, configuration model, a complete example, and example code by use case. --- -# Service Splitter Configuration Reference +# Service Splitter configuration reference This reference page describes the structure and contents of service splitter configuration entries. Configure and apply service splitters to redirect a percentage of incoming traffic requests for a service to one or more specific service instances. diff --git a/website/content/docs/connect/config-entries/tcp-route.mdx b/website/content/docs/connect/config-entries/tcp-route.mdx index e573a0183a..e7eda8c1cc 100644 --- a/website/content/docs/connect/config-entries/tcp-route.mdx +++ b/website/content/docs/connect/config-entries/tcp-route.mdx @@ -1,10 +1,10 @@ --- layout: docs -page_title: TCP Route Configuration Reference +page_title: TCP Route configuration reference description: Learn how to configure a TCP Route that is bound to an API gateway on VMs. --- -# TCP route configuration Reference +# TCP route configuration reference This topic provides reference information for the gateway TCP routes configuration entry. Refer to [Route Resource Configuration](/consul/docs/connect/gateways/api-gateway/configuration/routes) for information diff --git a/website/content/docs/connect/dataplane/index.mdx b/website/content/docs/connect/dataplane/index.mdx index f2eaa4eb78..30996e3476 100644 --- a/website/content/docs/connect/dataplane/index.mdx +++ b/website/content/docs/connect/dataplane/index.mdx @@ -87,7 +87,7 @@ $ export VERSION=1.0.0 && \ Refer to the following documentation for Consul on ECS workloads: - [Deploy Consul with the Terraform module](/consul/docs/ecs/deploy/terraform) -- [Deploy Consul manually](/consul/ecs/install-manul) +- [Deploy Consul manually](/consul/docs/ecs/deploy/manual) @@ -121,7 +121,7 @@ Consul Dataplane on Kubernetes supports the following features: - Running Consul service mesh in AWS Fargate and GKE Autopilot is supported. - xDS load balancing is supported. - Servers running in Kubernetes and servers external to Kubernetes are both supported. -- HCP Consul is supported. +- HCP Consul Dedicated and HCP Consul Central are supported. - Consul API Gateway Consul Dataplane on ECS support the following features: @@ -130,7 +130,7 @@ Consul Dataplane on ECS support the following features: - Mesh gateways - Running Consul service mesh in AWS Fargate and EC2 - xDS load balancing -- Self-managed and HCP Consul managed servers +- Self-managed Enterprise and HCP Consul Dedicated servers ### Technical Constraints diff --git a/website/content/docs/connect/gateways/api-gateway/configuration/gatewayclassconfig.mdx b/website/content/docs/connect/gateways/api-gateway/configuration/gatewayclassconfig.mdx index 816550a786..612875db4f 100644 --- a/website/content/docs/connect/gateways/api-gateway/configuration/gatewayclassconfig.mdx +++ b/website/content/docs/connect/gateways/api-gateway/configuration/gatewayclassconfig.mdx @@ -155,7 +155,7 @@ You can specify the following strings: * `trace` ### mapPrivilegedContainerPorts -Specifies a value that Consul adds to privileged ports defined in the gateway. Privileged ports are port numbers less than 1024 and some platforms, such as Red Hat OpenShift, explicitly configure Kubernetes to avoid running containers on privileged ports. The total value of the configured port number and the `mapPriviledgedContainerPorts` value must not exceed 65535, which is the highest possible TCP port number allowed. +Specifies a value that Consul adds to privileged ports defined in the gateway. Privileged ports are port numbers less than 1024 and some platforms, such as Red Hat OpenShift, explicitly configure Kubernetes to avoid running containers on privileged ports. The total value of the configured port number and the `mapPrivilegedContainerPorts` value must not exceed 65535, which is the highest possible TCP port number allowed. for gateway containers * Type: Integer * Required: optional diff --git a/website/content/docs/connect/gateways/api-gateway/configuration/gatewaypolicy.mdx b/website/content/docs/connect/gateways/api-gateway/configuration/gatewaypolicy.mdx index dc638fbf72..f74a9ed77c 100644 --- a/website/content/docs/connect/gateways/api-gateway/configuration/gatewaypolicy.mdx +++ b/website/content/docs/connect/gateways/api-gateway/configuration/gatewaypolicy.mdx @@ -1,7 +1,7 @@ --- layout: docs page_title: GatewayPolicy configuration reference -description: Learn about the configuration options for the GatewayPolicy configuration resource. GatewayPolicy resources define API gateway polcies for Consul service mesh on Kubernetes. +description: Learn about the configuration options for the GatewayPolicy configuration resource. GatewayPolicy resources define API gateway policies for Consul service mesh on Kubernetes. --- # GatewayPolicy @@ -15,33 +15,33 @@ The following list outlines field hierarchy, data types, and requirements in a g - [`apiVersion`](#apiversion): string | required | must be set to `consul.hashicorp.com/v1alpha1` - [`kind`](#kind): string | required | must be set to `GatewayPolicy` - [`metadata`](#metadata): map | required - - [`name`](#metadata-name): string | required + - [`name`](#metadata-name): string | required - [`namespace`](#metadata-namespace): string | `default` - [`spec`](#spec): map | required - [`targetRef`](#spec-targetref): map | required - - [`namespace`](#spec-targetref): string | `default` - - [`name`](#spec-targetref): string | required + - [`namespace`](#spec-targetref): string | `default` + - [`name`](#spec-targetref): string | required - [`kind`](#spec-targetref): string | required | must be set to `Gateway` - - [`group`](#spec-targetref): string | required - - [`sectionName`](#spec-targetref): string - - [`override`](#spec-override): map | required - - [`jwt`](#spec-override-jwt): map | required - - [`providers`](#spec-override-providers): list | required - - [`name`](#spec-override-providers): string | required - - [`verifyClaims`](#spec-override-providers): map | required - - [`path`](#spec-override-providers): list of strings | required - - [`value`](#spec-override-providers): string | required - - [`default`](#spec-default): map | required - - [`jwt`](#spec-default-jwt): map | required - - [`providers`](#spec-default-providers): list | required - - [`name`](#spec-default-providers): string | required - - [`verifyClaims`](#spec-default-providers): map | required - - [`path`](#spec-default-providers): list of strings | required - - [`value`](#spec-default-providers): string | required + - [`group`](#spec-targetref): string | required + - [`sectionName`](#spec-targetref): string + - [`override`](#spec-override): map | required + - [`jwt`](#spec-override-jwt): map | required + - [`providers`](#spec-override-providers): list | required + - [`name`](#spec-override-providers): string | required + - [`verifyClaims`](#spec-override-providers): map | required + - [`path`](#spec-override-providers): list of strings | required + - [`value`](#spec-override-providers): string | required + - [`default`](#spec-default): map | required + - [`jwt`](#spec-default-jwt): map | required + - [`providers`](#spec-default-providers): list | required + - [`name`](#spec-default-providers): string | required + - [`verifyClaims`](#spec-default-providers): map | required + - [`path`](#spec-default-providers): list of strings | required + - [`value`](#spec-default-providers): string | required ## Complete configuration -When every field is defined, a gateway policy has the following form: +When every field is defined, a gateway policy has the following form: ```yaml apiVersion: consul.hashicorp.com/v1alpha1 @@ -53,7 +53,7 @@ spec: targetRef: name: gateway kind: Gateway - group: gateway.networking.kuberenetes.io + group: gateway.networking.k8s.io/v1beta1 sectionName: override: jwt: @@ -109,7 +109,7 @@ Map that contains an arbitrary name for the resource and the namespace it applie ### `metadata.name` -Specifies a name for the resource. The name is metadata that you can use to reference the resource when performing Consul operations, such as applying the resource to a specific cluster. +Specifies a name for the resource. The name is metadata that you can use to reference the resource when performing Consul operations, such as applying the resource to a specific cluster. #### Values @@ -117,7 +117,7 @@ Specifies a name for the resource. The name is metadata that you can use to refe - This field is required. - Data type: String -### `metadata.namespace` +### `metadata.namespace` Specifies the namespace that the configuration applies to. Refer to [namespaces](/consul/docs/enterprise/namespaces) for more information. @@ -138,7 +138,7 @@ Map that contains the details about the gateway policy. The `apiVersion`, `kind` ### `targetRef` -Map that contains references to the gateway that the policy applies to. +Map that contains references to the gateway that the policy applies to. #### Values @@ -153,7 +153,7 @@ The following table describes the members of the `targetRef` map: | `namespace` | Specifies the namespace that the target reference is a member of. | String | `default` | | `name` | Specifies the name of the API gateway that the policy attaches to. | String | None | | `kind` | Specifies the type of resource that the policy attaches to. Must be set to `Gateway`. | String | None | -| `group` | Specifies the resource group. Must be set to `gateway.networking.kuberenetes.io`. | String | None | +| `group` | Specifies the resource group. Must be set to `gateway.networking.k8s.io/v1beta1`. | String | None | | `sectionName` | Specifies a part of the gateway that the policy applies to. | String | None | ### `spec.override` @@ -192,7 +192,7 @@ The following table describes the parameters you can specify in a member of the ### `spec.default` -Map that contains default configurations to apply to listeners when the policy is attached to the gateway. All routes attached to the gateway listener inherit the default configurations. You can specify override configurations that have precedence over default configurations. Refer to [`spec.override`](#spec-override) for details. +Map that contains default configurations to apply to listeners when the policy is attached to the gateway. All routes attached to the gateway listener inherit the default configurations. You can specify override configurations that have precedence over default configurations. Refer to [`spec.override`](#spec-override) for details. #### Values @@ -226,7 +226,7 @@ The following table describes the parameters you can specify in a member of the ## Example configuration -In the following example, all requests through the gateway must have the `api.apps.organization.com` audience claim. Additionally, requests through the gateway must have a `user` role by default. +In the following example, all requests through the gateway must have the `api.apps.organization.com` audience claim. Additionally, requests through the gateway must have a `user` role by default. ```yaml apiVersion: consul.hashicorp.com/v1alpha1 @@ -237,7 +237,7 @@ spec: targetRef: name: gateway kind: Gateway - group: gateway.networking.kuberenetes.io + group: gateway.networking.k8s.io/v1beta1 sectionName: to-server override: jwt: @@ -256,4 +256,4 @@ spec: - "roles" - "perm" value: "user" -``` \ No newline at end of file +``` diff --git a/website/content/docs/connect/gateways/api-gateway/configuration/routes.mdx b/website/content/docs/connect/gateways/api-gateway/configuration/routes.mdx index afa333dd2c..6723931290 100644 --- a/website/content/docs/connect/gateways/api-gateway/configuration/routes.mdx +++ b/website/content/docs/connect/gateways/api-gateway/configuration/routes.mdx @@ -50,9 +50,9 @@ The following outline shows how to format the configurations for the `Route` obj - [`replacePrefixMatch`](#rules-filters-urlrewrite-path): string | required - [`type`](#rules-filters-urlrewrite-path): string | required - [`extensionRef`](#rules-filters-extensionref): map - - [`group`](#rultes-filters-extensionref): string | must be set to `consul.hashicorp.com` - - [`kind`](#rultes-filters-extensionref): string | must be set to `RouteAuthFilter` - - [`name`](#rultes-filters-extensionref): string | must be set to `consul.hashicorp.com` + - [`group`](#rules-filters-extensionref): string | must be set to `consul.hashicorp.com` + - [`kind`](#rules-filters-extensionref): string | must be set to `RouteAuthFilter` + - [`name`](#rules-filters-extensionref): string | must be set to `consul.hashicorp.com` - [`matches`](#rules-matches): array of objects | optional - [`path`](#rules-matches-path): list of objects | optional - [`type`](#rules-matches-path): string | required diff --git a/website/content/docs/connect/gateways/api-gateway/define-routes/routes-k8s.mdx b/website/content/docs/connect/gateways/api-gateway/define-routes/routes-k8s.mdx index 3e29ac2d34..13413e82bd 100644 --- a/website/content/docs/connect/gateways/api-gateway/define-routes/routes-k8s.mdx +++ b/website/content/docs/connect/gateways/api-gateway/define-routes/routes-k8s.mdx @@ -1,18 +1,18 @@ --- layout: docs page_title: Define API gateway routes on Kubernetes -description: Learn how to define and attach HTTP and TCP routes to Consul API gateway listeners in Kubernetes-orchestrated networks. +description: Learn how to define and attach HTTP and TCP routes to Consul API gateway listeners in Kubernetes-orchestrated networks. --- # Define API gateway routes on Kubernetes -This topic describes how to configure HTTP and TCP routes and attach them to Consul API gateway listeners in Kubernetes-orchestrated networks. Routes are rule-based configurations that allow external clients to send requests to services in the mesh. For information +This topic describes how to configure HTTP and TCP routes and attach them to Consul API gateway listeners in Kubernetes-orchestrated networks. Routes are rule-based configurations that allow external clients to send requests to services in the mesh. For information ## Overview The following steps describe the general workflow for defining and deploying routes: -1. Define a route configuration that specifies the protocol type, name of the gateway to attach to, and rules for routing requests. +1. Define a route configuration that specifies the protocol type, name of the gateway to attach to, and rules for routing requests. 1. Deploy the configuration to create the routes and attach them to the gateway. Routes and the gateways they are attached to are eventually-consistent objects. They provide feedback about their current state through a series of status conditions. As a result, you must manually check the route status to determine if the route successfully bound to the gateway. @@ -23,7 +23,7 @@ Verify that your environment meets the requirements specified in [Technical spec ### OpenShift -If your Kubernetes-orchestrated network runs on OpenShift, verify that OpenShift is enabled for your Consul installation. Refer to [OpenShift requirements](/consul/docs/connect/gateways/api-gateway/tech-specs#openshift-requirements) for additional information. +If your Kubernetes-orchestrated network runs on OpenShift, verify that OpenShift is enabled for your Consul installation. Refer to [OpenShift requirements](/consul/docs/connect/gateways/api-gateway/tech-specs#openshift-requirements) for additional information. ## Define routes @@ -31,12 +31,12 @@ Define route configurations and bind them to listeners configured on the gateway 1. Create a configuration file and specify the following fields: - - `apiVersion`: Specifies the Kuberenetes API gateway version. This must be set to `gateway.networking.k8s.io/v1beta1` + - `apiVersion`: Specifies the Kubernetes API gateway version. This must be set to `gateway.networking.k8s.io/v1beta1` - `kind`: Set to `HTTPRoute` or `TCPRoute`. - `metadata.name`: Specify a name for the route. The name is metadata that you can use to reference the configuration when performing Consul operations. - - `spec.parentRefs.name`: Specifies a list of API gateways that the route binds to. + - `spec.parentRefs.name`: Specifies a list of API gateways that the route binds to. - `spec. rules`: Specifies a list of routing rules for constructing a routing table that maps listeners to services. - + Refer to the [`Routes` configuration reference](/consul/docs/connect/gateways/api-gateway/configuration/routes) for details about configuring route rules. 1. Configure any additional fields necessary for your use case, such as the namespace or admin partition. diff --git a/website/content/docs/connect/gateways/api-gateway/index.mdx b/website/content/docs/connect/gateways/api-gateway/index.mdx index 5b29311bbc..c6202d898b 100644 --- a/website/content/docs/connect/gateways/api-gateway/index.mdx +++ b/website/content/docs/connect/gateways/api-gateway/index.mdx @@ -26,7 +26,8 @@ You can deploy API gateways to networks that implement a variety of computing en The following steps describe the general workflow for deploying a Consul API gateways: 1. For Kubernetes-orchestrated services, install Consul on your cluster. For Kubernetes-orchestrated services on OpenShift, you must also enable the `openShift.enabled` parameter. Refer to [Install Consul on Kubernetes](/consul/docs/connect/gateways/api-gateway/install-k8s) for additional information. -1. Define and deploy the API gateway configurations to create the API gateway artifacts. For VM-hosted services, create configuration entries for the gateway service, listeners configurations, and references to TLS certificates. For Kubernetes-orchestrated, configurations also include `GatewayClassConfig`s and `parametersRef`s. +1. Define and deploy the API gateway configurations to create the API gateway artifacts. For VM-hosted services, create configuration entries for the gateway service, listeners configurations, and TLS certificates. For Kubernetes-orchestrated services, configurations also include `GatewayClassConfig` and `parametersRef`. All Consul API Gateways created in Kubernetes with the `consul-k8s` Helm chart v1.5.0 or later use file system certificates when TLS is enabled. + 1. Define and deploy routes between the gateway listeners and services in the mesh. Gateway configurations are modular, so you can define and attach routes and inline certificates to multiple gateways. diff --git a/website/content/docs/connect/gateways/api-gateway/secure-traffic/encrypt-vms.mdx b/website/content/docs/connect/gateways/api-gateway/secure-traffic/encrypt-vms.mdx index 3520dfbc2e..fd1cdfe86c 100644 --- a/website/content/docs/connect/gateways/api-gateway/secure-traffic/encrypt-vms.mdx +++ b/website/content/docs/connect/gateways/api-gateway/secure-traffic/encrypt-vms.mdx @@ -1,7 +1,7 @@ --- layout: docs page_title: Encrypt API gateway traffic on virtual machines -description: Learn how to define inline certificate config entries and deploy them to Consul. Inline certificate configuration entries enable you to attach TLS certificates and keys to gateway listeners so that traffic between external clients and gateway listeners is encrypted. +description: Learn how to define inline certificate config entries and deploy them to Consul. Inline certificate and file system certificate configuration entries enable you to attach TLS certificates and keys to gateway listeners so that traffic between external clients and gateway listeners is encrypted. --- # Encrypt API gateway traffic on virtual machines @@ -10,8 +10,9 @@ This topic describes how to make TLS certificates available to API gateways so t ## Requirements -- Consul 1.15 or later -- You must have a certificate and key from your CA +- Consul v1.15 or later is required to use the Consul API gateway on VMs + - Consul v1.19 or later is required to use the [file system certificate configuration entry](/consul/docs/connect/config-entries/file-system-certificate) +- You must have a certificate and key from your CA - A Consul cluster with service mesh enabled. Refer to [`connect`](/consul/docs/agent/config/config-files#connect) - Network connectivity between the machine deploying the API gateway and a Consul cluster agent or server @@ -30,14 +31,20 @@ with Consul API gateway configurations. ## Define TLS certificates -1. Create an [`inline-certificate` configuration entry](/consul/docs/connect/gateways/api-gateway/configuration/inline-certificate) and specify the following fields: - - `Kind`: Specifies the type of configuration entry. This must be set to `inline-certificate`. +1. Create a [file system certificate](/consul/docs/connect/config-entries/file-system-certificate) or [inline certificate](/consul/docs/connect/config-entries/inline-certificate) and specify the following fields: + - `Kind`: Specifies the type of configuration entry. This must be set to `file-system-certificate` or `inline-certificate`. - `Name`: Specify the name in the [API gateway listener configuration](/consul/docs/connect/gateways/api-gateway/configuration/api-gateway#listeners) to bind the certificate to that listener. - - `Certificate`: Specifies the inline public certificate to use for TLS as plain text. - - `PrivateKey`: Specifies the inline private key to use for TLS as plain text. -1. Configure any additional fields necessary for your use case, such as the namespace or admin partition. Refer to the [`inline-certificate` configuration entry](/consul/docs/connect/gateways/api-gateway/configuration/inline-certificate) reference for additional information. + - `Certificate`: Specifies the filepath to the certificate on the local system or the inline public certificate as plain text. + - `PrivateKey`: Specifies the filepath to private key on the local system or the inline private key to as plain text. +1. Configure any additional fields necessary for your use case, such as the namespace or admin partition. Refer to the [file system certificate configuration reference](/consul/docs/connect/config-entries/file-system-certificate) or [inline certificate configuration reference](/consul/docs/connect/config-entries/inline-certificate) for more information. 1. Save the configuration. +### Examples + + + + + The following example defines a certificate named `my-certificate`. API gateway configurations that specify `inline-certificate` in the `Certificate.Kind` field and `my-certificate` in the `Certificate.Name` field are able to use the certificate. ```hcl @@ -57,6 +64,22 @@ PrivateKey = < + + + +The following example defines a certificate named `my-certificate`. API gateway configurations that specify `file-system-certificate` in the `Certificate.Kind` field and `my-certificate` in the `Certificate.Name` field are able to use the certificate. + +```hcl +Kind = "file-system-certificate" +Name = "my-certificate" +Certificate = "/opt/consul/tls/api-gateway.crt" +PrivateKey = "/opt/consul/tls/api-gateway.key" +``` + + + + ## Deploy the configuration to Consul Run the `consul config write` command to enable listeners to use the certificate. The following example writes a configuration called `my-certificate.hcl`: diff --git a/website/content/docs/connect/gateways/api-gateway/secure-traffic/verify-jwts-k8s.mdx b/website/content/docs/connect/gateways/api-gateway/secure-traffic/verify-jwts-k8s.mdx index 97c6eb30cb..6bd8f28ccd 100644 --- a/website/content/docs/connect/gateways/api-gateway/secure-traffic/verify-jwts-k8s.mdx +++ b/website/content/docs/connect/gateways/api-gateway/secure-traffic/verify-jwts-k8s.mdx @@ -14,16 +14,17 @@ This topic describes how to use JSON web tokens (JWT) to verify requests to API You can configure API gateways to use JWTs to verify incoming requests so that you can stop unverified traffic at the gateway. You can configure JWT verification at different levels: -- Listener defaults: Define basic defaults that apply to all routes attached to a listener. +- Listener defaults: Define basic defaults in a GatewayPolicy resource to apply them to all routes attached to a listener. - HTTP route-specific settings: You can define JWT authentication settings for specific HTTP routes. Route-specific JWT settings override default listener configurations. -- Listener overrides: Define override settings that take precedence over default and route-specific configurations. This enables you to set enforceable policies for listeners. +- Listener overrides: Define override settings in a GatewayPolicy resource that take precedence over default and route-specific configurations. Use override settings to set enforceable policies for listeners. Complete the following steps to use JWTs to verify requests: -1. Define a policy that specifies default and override settings for API gateway listeners and attach it to the gateway. -1. Define an HTTP route auth filter that specifies route-specific JWT verification settings. -1. Attach the auth filter to the HTTP route values file. +1. Define a JWTProvider that specifies the JWT provider and claims used to verify requests to the gateway. +1. Define a GatewayPolicy that specifies default and override settings for API gateway listeners and attach it to the gateway. +1. Define a RouteAuthFilter that specifies route-specific JWT verification settings. +1. Reference the RouteAuthFilter from the HTTPRoute. 1. Apply the configurations. @@ -33,42 +34,193 @@ Complete the following steps to use JWTs to verify requests: - Consul on Kubernetes CLI or Helm chart v1.3.0+ - JWT details, such as claims and provider -## Define override and default settings -Create a `GatewayPolicy` values file and configure the following fields to define default and override settings for JWT verification. Refer to [`GatewayPolicy` configuration reference](/consul/docs/connect/gateways/api-gateway/configuration/gatewaypolicy) for details. +## Define a JWTProvider + +Create a `JWTProvider` CRD that defines the JWT provider to verify claims against. + +In the following example, the JWTProvider CRD contains a local JWKS. In production environments, use a production-grade JWKs endpoint instead. + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: JWTProvider +metadata: + name: local +spec: + issuer: local + jsonWebKeySet: + local: + jwks: "" +``` + + + +For more information about the fields you can configure in this CRD, refer to [`JWTProvider` configuration reference](/consul/docs/connect/config-entries/jwtprovider). + +## Define a GatewayPolicy + +Create a `GatewayPolicy` CRD that defines default and override settings for JWT verification. - `kind`: Must be set to `GatewayPolicy` - `metadata.name`: Specifies a name for the policy. - `spec.targetRef.name`: Specifies the name of the API gateway to attach the policy to. - `spec.targetRef.kind`: Specifies the kind of resource to attach to the policy to. Must be set to `Gateway`. -- `spec.targetRef.group`: Specifies the resource group. Unless you have created a custom group, this should be set to `gateway.networking.kuberenetes.io`. +- `spec.targetRef.group`: Specifies the resource group. Unless you have created a custom group, this should be set to `gateway.networking.k8s.io/v1beta1`. - `spec.targetRef.sectionName`: Specifies a part of the gateway that the policy applies to. - `spec.targetRef.override.jwt.providers`: Specifies a list of providers and claims used to verify requests to the gateway. The override settings take precedence over the default and route-specific JWT verification settings. - `spec.targetRef.default.jwt.providers`: Specifies a list of default providers and claims used to verify requests to the gateway. -## Define an HTTP route auth filter +The following examples configure a Gateway and the GatewayPolicy being attached to it so that every request coming through the listener must meet these conditions: -Create an `RouteAuthFilter` values file and configure the following fields. Refer to [`RouteAuthFilter` configuration reference](/consul/docs/connect/gateways/api-gateway/configuration/routeauthfilter) for details. +- The request must be signed by the `local` provider +- The request must have a claim of `role` with a value of `user` unless the HTTPRoute attached to the listener overrides it + + + + + + +```yaml +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: Gateway +metadata: + name: api-gateway +spec: + gatewayClassName: consul + listeners: + - protocol: HTTP + port: 30002 + name: listener-one +``` + + + + + + + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: GatewayPolicy +metadata: + name: gw-policy +spec: + targetRef: + name: api-gateway + sectionName: listener-one + group: gateway.networking.k8s.io/v1beta1 + kind: Gateway + override: + jwt: + providers: + - name: "local" + default: + jwt: + providers: + - name: "local" + verifyClaims: + - path: + - role + value: user +``` + + + + + + +For more information about the fields you can configure, refer to [`GatewayPolicy` configuration reference](/consul/docs/connect/gateways/api-gateway/configuration/gatewaypolicy). + +## Define a RouteAuthFilter + +Create an `RouteAuthFilter` CRD that defines overrides for the default JWT verification configured in the GatewayPolicy. - `kind`: Must be set to `RouteAuthFilter` - `metadata.name`: Specifies a name for the filter. - `metadata.namespace`: Specifies the Consul namespace the filter applies to. - `spec.jwt.providers`: Specifies a list of providers and claims used to verify requests to the gateway. The override settings take precedence over the default and route-specific JWT verification settings. +In the following example, the RouteAuthFilter overrides default settings set in the GatewayPolicy so that every request coming through the listener must meet these conditions: + +- The request must be signed by the `local` provider +- The request must have a `role` claim +- The value of the claim must be `admin` + + + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: RouteAuthFilter +metadata: + name: auth-filter +spec: + jwt: + providers: + - name: local + verifyClaims: + - path: + - role + value: admin +``` + + + +For more information about the fields you can configure, refer to [`RouteAuthFilter` configuration reference](/consul/docs/connect/gateways/api-gateway/configuration/routeauthfilter). + ## Attach the auth filter to your HTTP routes -In the `filters` field of your HTTP route configuration, add the following fields. Refer to the [`extensionRef` configuration reference](/consul/docs/connect/gateways/api-gateway/configuration/routes#rules-filters-extensionref) for details: +In the `filters` field of your HTTPRoute configuration, define the filter behavior that results from JWT verification. - `type: extensionRef`: Declare list of extension references. -- `extensionRef.group`: Specifies the resource group. Unless you have created a custom group, this should be set to `gateway.networking.kuberenetes.io`. +- `extensionRef.group`: Specifies the resource group. Unless you have created a custom group, this should be set to `gateway.networking.k8s.io/v1beta1`. - `extensionRef.kind`: Specifies the type of extension reference to attach to the route. Must be `RouteAuthFilter` - `extensionRef.name`: Specifies the name of the auth filter. -## Apply the configurations +The following example configures an HTTPRoute so that every request to `api-gateway-fqdn:3002/admin` must meet these conditions: -Run the `kubectl apply` command and specify the values files to apply the configurations. The following example applies the values files stored in the `jwt-routes` directory: +- The request be signed by the `local` provider. +- The request must have a `role` claim. +- The value of the claim must be `admin`. -```shell-session -$ kubectl apply -f jwt-routes +Every other request must be signed by the `local` provider and have a claim of `role` with a value of `user`, as defined in the GatewayPolicy. + + + +```yaml +apiVersion: gateway.networking.k8s.io/v1beta1 +kind: HTTPRoute +metadata: + name: http-route +spec: + parentRefs: + - name: api-gateway + rules: + - matches: + - path: + type: PathPrefix + value: /admin + filters: + - type: ExtensionRef + extensionRef: + group: consul.hashicorp.com + kind: RouteAuthFilter + name: auth-filter + backendRefs: + - kind: Service + name: admin + port: 8080 + - matches: + - path: + type: PathPrefix + value: / + backendRefs: + - kind: Service + name: user-service + port: 8081 ``` + diff --git a/website/content/docs/connect/gateways/api-gateway/secure-traffic/verify-jwts-vms.mdx b/website/content/docs/connect/gateways/api-gateway/secure-traffic/verify-jwts-vms.mdx index f58d92621a..fda579669f 100644 --- a/website/content/docs/connect/gateways/api-gateway/secure-traffic/verify-jwts-vms.mdx +++ b/website/content/docs/connect/gateways/api-gateway/secure-traffic/verify-jwts-vms.mdx @@ -20,6 +20,7 @@ You can configure API gateways to use JWTs to verify incoming requests so that y Complete the following steps to use JWTs to verify requests: +1. Define a JWTProvider that specifies the JWT provider and claims used to verify requests to the gateway. 1. Configure default and override settings for listeners in the API gateway configuration entry. 1. Define route-specific JWT verification settings as filters in the HTTP route configuration entries. 1. Write the configuration entries to Consul to begin verifying requests using JWTs. @@ -29,17 +30,155 @@ Complete the following steps to use JWTs to verify requests: - Consul 1.17 or later - JWT details, such as claims and provider +## Define a JWTProvider + +Create a JWTProvider config entry that defines the JWT provider to verify claims against. +In the following example, the JWTProvider CRD contains a local JWKS. In production environments, use a production-grade JWKs endpoint instead. + + + +```hcl +Kind = "jwt-provider" +Name = "local" + +Issuer = "local" + +JSONWebKeySet = { + Local = { + JWKS="" + } +} +``` + + + +For more information about the fields you can configure in this CRD, refer to [`JWTProvider` configuration reference](/consul/docs/connect/config-entries/jwtprovider). + ## Configure default and override settings -Define default and override settings for JWT verification in the [API gateway configuration entry](/consul/docs/connect/gateways/api-gateway/configuration/api-gateway). +Define default and override settings for JWT verification in the [API gateway configuration entry](/consul/docs/connect/gateways/api-gateway/configuration/api-gateway). -1. Add a `default.JWT` block to the listener that you want to apply JWT verification to. Consul applies these configurations to routes attached to the listener. Refer to the [`Listeners.default.JWT`](/consul/docs/connect/config-entries/api-gateway#listeners-default-jwt) configuration reference for details. -1. Add an `override.JWT` block to the listener that you want to apply JWT verification policies to. Consul applies these configurations to all routes attached to the listener, regardless of the `default` or route-specific settings. Refer to the [`Listeners.override.JWT`](/consul/docs/connect/config-entries/api-gateway#listeners-override-jwt) configuration reference for details. -1. Apply the settings in the API gateway configuration entry. You can use the [`/config` API endpoint](/consul/api-docs/config#apply-configuration) or the [`consul config write` command](/consul/commands/config/write). +1. Add a `default.JWT` block to the listener that you want to apply JWT verification to. Consul applies these configurations to routes attached to the listener. Refer to the [`Listeners.default.JWT`](/consul/docs/connect/config-entries/api-gateway#listeners-default-jwt) configuration reference for details. +1. Add an `override.JWT` block to the listener that you want to apply JWT verification policies to. Consul applies these configurations to all routes attached to the listener, regardless of the `default` or route-specific settings. Refer to the [`Listeners.override.JWT`](/consul/docs/connect/config-entries/api-gateway#listeners-override-jwt) configuration reference for details. +1. Apply the settings in the API gateway configuration entry. You can use the [`/config` API endpoint](/consul/api-docs/config#apply-configuration) or the [`consul config write` command](/consul/commands/config/write). + +The following examples configure a Gateway so that every request coming through the listener must meet these conditions: +- The request must be signed by the `local` provider +- The request must have a claim of `role` with a value of `user` unless the HTTPRoute attached to the listener overrides it + + + +```hcl +Kind = "api-gateway" +Name = "api-gateway" +Listeners = [ + { + Name = "listener-one" + Port = 9001 + Protocol = "http" + Override = { + JWT = { + Providers = [ + { + Name = "local" + } + ] + } + } + default = { + JWT = { + Providers = [ + { + Name = "local" + VerifyClaims = [ + { + Path = ["role"] + Value = "pet" + } + ] + } + ] + } + } + } +] +``` + + ## Configure verification for specific HTTP routes -Define filters to enable route-specific JWT verification settings in the [HTTP route configuration entry](/consul/docs/connect/config-entries/http-route). +Define filters to enable route-specific JWT verification settings in the [HTTP route configuration entry](/consul/docs/connect/config-entries/http-route). 1. Add a `JWT` configuration to the `rules.filter` block. Route-specific configurations that overlap the [default settings ](/consul/docs/connect/config-entries/api-gateway#listeners-default-jwt) in the API gateway configuration entry take precedence. Configurations defined in the [listener override settings](/consul/docs/connect/config-entries/api-gateway#listeners-override-jwt) take the highest precedence. -1. Apply the settings in the API gateway configuration entry. You can use the [`/config` API endpoint](/consul/api-docs/config#apply-configuration) or the [`consul config write` command](/consul/commands/config/write). +1. Apply the settings in the API gateway configuration entry. You can use the [`/config` API endpoint](/consul/api-docs/config#apply-configuration) or the [`consul config write` command](/consul/commands/config/write). + +The following example configures an HTTPRoute so that every request to `api-gateway-fqdn:3002/admin` must meet these conditions: +- The request be signed by the `local` provider. +- The request must have a `role` claim. +- The value of the claim must be `admin`. + +Every other request must be signed by the `local` provider and have a claim of `role` with a value of `user`, as defined in the Gateway listener. + + + +```hcl +Kind = "http-route" +Name = "api-gateway-route" +Parents = [ + { + SectionName = "listener-one" + Name = "api-gateway" + Kind = "api-gateway" + }, +] +Rules = [ + { + Matches = [ + { + Path = { + Match = "prefix" + Value = "/admin" + } + } + ] + Filters = { + JWT = { + Providers = [ + { + Name = "local" + VerifyClaims = [ + { + Path = ["role"] + Value = "admin" + } + ] + } + ] + } + } + Services = [ + { + Name = "admin-service" + } + ] + }, + { + Matches = [ + { + Path = { + Match = "prefix" + Value = "/" + } + } + ] + Services = [ + { + Name = "user-service" + } + ] + }, +] +``` + + diff --git a/website/content/docs/connect/gateways/api-gateway/tech-specs.mdx b/website/content/docs/connect/gateways/api-gateway/tech-specs.mdx index 98f5e28c0c..9a79f75ca1 100644 --- a/website/content/docs/connect/gateways/api-gateway/tech-specs.mdx +++ b/website/content/docs/connect/gateways/api-gateway/tech-specs.mdx @@ -40,7 +40,7 @@ Refer to the following topics for additional information: ### Security context constraints -OpenShift requires a security context constraint (SCC) configuration, which restricts pods to specific groups. You can create a custom SCC or use one of the default constraints. Refer to the [OpenShift documentation](https://docs.openshift.com/container-platform/4.13/authentication/managing-security-context-constraints.html) for additional information. +OpenShift requires a security context constraint (SCC) configuration, which restricts pods to specific groups. You can create a custom SCC or use one of the default constraints. Refer to the [OpenShift documentation](https://docs.openshift.com/container-platform/4.13/authentication/managing-security-context-constraints.html) for additional information. By default, the SCC is set to `restricted-v2` for the `managedGatewayClass` that Consul automatically creates. The `restricted-v2` SCC is one of OpenShifts default SCCs, but you can specify a different SCC in the `openshiftSCCName` parameter: @@ -52,12 +52,12 @@ connectInject: ``` ### Privileged container ports - + Containers cannot use privileged ports when OpenShift is enabled. Privileged ports are 1 through 1024, and serving applications from that range is a security risk. To allow gateway listeners to use privileged port numbers, specify an integer value in the `mapPrivilegedContainerPorts` field of your Consul values configuration. Consul adds the value to listener port numbers that are set to a number in the privileged container range. Consul maps the configured port number to the total port number so that traffic sent to the configured port number is correctly forwarded to the service. -For example, if a gateway listener is configured to port `80` and the `mapPrivilegedContainerPorts` field is configured to `2000`, then the actual port number on the underlying container is `2080`. +For example, if a gateway listener is configured to port `80` and the `mapPrivilegedContainerPorts` field is configured to `2000`, then the actual port number on the underlying container is `2080`. You can set the `mapPrivilegedContainerPorts` parameter in the following map in your Consul values file: @@ -74,29 +74,29 @@ Refer to the [release notes](/consul/docs/release-notes) for your version of Con ## Supported Kubernetes gateway specification features -Consul API gateways for Kuberentes support a subset of the Kubernetes Gateway API specification. For a complete list of features, including the list of gateway and route statuses and an explanation on how they -are used, refer to the [documentation in our GitHub repo](https://github.com/hashicorp/consul-api-gateway/blob/main/dev/docs/supported-features.md): +Consul API gateways for Kubernetes support a subset of the Kubernetes Gateway API specification. For a complete list of features, including the list of gateway and route statuses and an explanation on how they +are used, refer to the [documentation in our GitHub repo](https://github.com/hashicorp/consul-api-gateway/blob/main/dev/docs/supported-features.md): ### `GatewayClass` -The `GatewayClass` resource describes a class of gateway configurations to use a template for creating `Gateway` resources. You can also specify custom API gateway configurations in a `GatewayClassConfig` CRD and attach them to resource to the `GatewayClass` using the `parametersRef` field. +The `GatewayClass` resource describes a class of gateway configurations to use a template for creating `Gateway` resources. You can also specify custom API gateway configurations in a `GatewayClassConfig` CRD and attach them to resource to the `GatewayClass` using the `parametersRef` field. You must specify the `"hashicorp.com/consul-api-gateway-controller"` controller so that Consul can manage gateways generated by the `GatewayClass`. Refer to the [Kubernetes `GatewayClass` documentation](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GatewayClass) for additional information. -### `Gateway` +### `Gateway` -The `Gateway` resource is the core API gateway component. Gateways have one or more listeners that can route `HTTP`, `HTTPS`, or `TCP` traffic. You can define header-based hostname matching for listeners, but SNI is not supported. +The `Gateway` resource is the core API gateway component. Gateways have one or more listeners that can route `HTTP`, `HTTPS`, or `TCP` traffic. You can define header-based hostname matching for listeners, but SNI is not supported. -You can apply filters to add, remove, and set header values on incoming requests. Gateways support the `terminate` TLS mode and `core/v1/Secret` TLS certificates. Extended option support includes TLS version and cipher constraints. Refer to the [Kubernetes `Gateway` documentation](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.Gateway) for additional information. +You can apply filters to add, remove, and set header values on incoming requests. Gateways support the `terminate` TLS mode and `core/v1/Secret` TLS certificates. Extended option support includes TLS version and cipher constraints. Refer to [Kubernetes `Gateway` resource configuration reference](/consul/docs/connect/gateways/api-gateway/configuration/gateway) for more information. ### `HTTPRoute` -`HTTPRoute` configurations determine HTTP paths between listeners defined on the gateway and services in the mesh. You can specify weights to load balance traffic, as well as define rules for matching request paths, headers, queries, and methods to ensure that traffic is routed appropriately. You can apply filters to add, remove, and set header values on requests sent through th route. +`HTTPRoute` configurations determine HTTP paths between listeners defined on the gateway and services in the mesh. You can specify weights to load balance traffic, as well as define rules for matching request paths, headers, queries, and methods to ensure that traffic is routed appropriately. You can apply filters to add, remove, and set header values on requests sent through th route. Routes support the following backend types: -- `core/v1/Service` backend types when the route maps to service registered with Consul. -- `api-gateway.consul.hashicorp.com/v1alpha1/MeshService`. +- `core/v1/Service` backend types when the route maps to service registered with Consul. +- `api-gateway.consul.hashicorp.com/v1alpha1/MeshService`. Refer to [Kubernetes `HTTPRoute` documentation](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute) for additional information. @@ -104,8 +104,8 @@ Refer to [Kubernetes `HTTPRoute` documentation](https://gateway-api.sigs.k8s.io/ `TCPRoute` configurations determine TCP paths between listeners defined on the gateway and services in the mesh. Routes support the following backend types: -- `core/v1/Service` backend types when the route maps to service registered with Consul. -- `api-gateway.consul.hashicorp.com/v1alpha1/MeshService`. +- `core/v1/Service` backend types when the route maps to service registered with Consul. +- `api-gateway.consul.hashicorp.com/v1alpha1/MeshService`. Refer to [Kubernetes `TCPRoute` documentation](https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute) for additional information. @@ -121,10 +121,10 @@ Refer to the [Kubernetes `ReferenceGrant` documentation](https://gateway-api.sig ## Consul server deployments -- Consul Enterprise and the free community edition are both supported. +- Consul Enterprise and the community edition are both supported. - Supported Consul Server deployment types: - Self-Managed - - HCP Consul + - HCP Consul Dedicated ### Consul feature support @@ -152,6 +152,3 @@ The following resources are allocated for each component of the API gateway. - **CPU**: None. Either the namespace or cluster default is allocated, depending on the Kubernetes cluster configuration. - **Memory**: None. Either the namespace or cluster default is allocated, depending on the Kubernetes cluster configuration. - - - diff --git a/website/content/docs/connect/intentions/index.mdx b/website/content/docs/connect/intentions/index.mdx index c9bbbabf2a..8d6364638a 100644 --- a/website/content/docs/connect/intentions/index.mdx +++ b/website/content/docs/connect/intentions/index.mdx @@ -36,7 +36,11 @@ application](/consul/docs/connect/native) enforces intentions on inbound connect L4 intentions mediate the ability to establish new connections. Modifying an intention does not have an effect on existing connections. As a result, changing a connection from `allow` to `deny` does not sever the connection. -L7 intentions mediate the ability to issue new requests. When an intention is modified, requests received after the modification use the latest intention rules to enforce access. Changing a connection from `allow` to `deny` does not sever the connection, but doing so blocks new requests from being processed. +L7 intentions mediate the ability to issue new requests. When an intention is modified, requests received after the modification use the latest intention rules to enforce access. Changing a connection from `allow` to `deny` does not sever the connection, but doing so blocks new requests from being processed. + +When using L7 intentions, we recommend that you review and update the [Mesh request normalization configuration](/consul/docs/connect/security#request-normalization-and-configured) to avoid unintended match rule circumvention. More details are available in the [Mesh configuration entry reference](/consul/docs/connect/config-entries/mesh#request-normalization). + +When you use L7 intentions with header matching and it is possible for a header to contain multiple values, we recommend using `contains` or `regex` instead of `exact`, `prefix`, or `suffix`. For more information, refer to the [service intentions configuration entry reference](/consul/docs/connect/config-entries/service-intentions#spec-sources-permissions-http-header). ### Caching diff --git a/website/content/docs/connect/manage-traffic/failover/sameness.mdx b/website/content/docs/connect/manage-traffic/failover/sameness.mdx index 75a7e769f9..ac8c8745fe 100644 --- a/website/content/docs/connect/manage-traffic/failover/sameness.mdx +++ b/website/content/docs/connect/manage-traffic/failover/sameness.mdx @@ -6,13 +6,13 @@ description: You can configure sameness groups so that when a service instance f # Failover with sameness groups -This page describes how to use sameness groups to automatically redirect service traffic to healthy instances in failover scenarios. Sameness groups are a user-defined set of Consul admin partitions with identical registered services. These admin paritions typically belong to Consul datacenters in different cloud regions, which enables sameness groups to participate in several service failover configuration strategies. +This page describes how to use sameness groups to automatically redirect service traffic to healthy instances in failover scenarios. Sameness groups are a user-defined set of Consul admin partitions with identical registered services. These admin partitions typically belong to Consul datacenters in different cloud regions, which enables sameness groups to participate in several service failover configuration strategies. To create a sameness group and configure each Consul datacenter to allow traffic from other members of the group, refer to [create sameness groups](/consul/docs/connect/cluster-peering/usage/create-sameness-groups). ## Failover strategies -You can edit a sameness group configuration entry so that all services failover to healthy instances on other members of a sameness group by default. You can also reference the sameness group in other configuration entries to enact other failover strategies for your network. +You can edit a sameness group configuration entry so that all services failover to healthy instances on other members of a sameness group by default. You can also reference the sameness group in other configuration entries to enact other failover strategies for your network. You can establish a failover strategy by configuring sameness group behavior in the following locations: @@ -33,9 +33,9 @@ In the following example configuration entry, datacenter `dc1` has two partition ```hcl -Kind = "sameness-group" +Kind = "sameness-group" Name = "example-sg" -Partition = "partition-1" +Partition = "partition-1" DefaultForFailover = true Members = [ {Partition = "partition-1"}, @@ -50,8 +50,8 @@ Members = [ ``` { - "Kind": "sameness-group", - "Name": "example-sg", + "Kind": "sameness-group", + "Name": "example-sg", "Partition": "partition-1", "DefaultForFailover": true, "Members": [ @@ -74,12 +74,12 @@ Members = [ ```yaml apiVersion: consul.hashicorp.com/v1alpha1 -kind: SamenessGroup +kind: SamenessGroup metadata: name: example-sg spec: defaultForFailover: true - members: + members: - partition: partition-1 - partition: partition-2 - peer: dc2-partition-1 @@ -132,8 +132,8 @@ Failover { ``` { - "Kind": "service-resolver", - "Name": "db", + "Kind": "service-resolver", + "Name": "db", "DefaultSubset": "v1", "Subsets": { "v1": { @@ -142,7 +142,7 @@ Failover { "v2": { "Filter": "Service.Meta.version == v2" } - }, + }, "Failover": { "v1": { "SamenessGroup": "product-group" diff --git a/website/content/docs/connect/manage-traffic/route-to-local-upstreams.mdx b/website/content/docs/connect/manage-traffic/route-to-local-upstreams.mdx index b2e891734c..4d5be2e5b5 100644 --- a/website/content/docs/connect/manage-traffic/route-to-local-upstreams.mdx +++ b/website/content/docs/connect/manage-traffic/route-to-local-upstreams.mdx @@ -32,17 +32,17 @@ For networks deployed to virtual machines, complete the following steps to route If you deployed Consul to a Kubernetes or ECS environment using `consul-k8s` or `consul-ecs`, service instance locality information is inherited from the host machine. As a result, you do not need to specify the regions and zones on containerized platforms unless you are implementing a custom deployment. -On Kubernetes, Consul automatically populates geographic information about service instances using the `topology.kubernetes.io/region` and `topology.kubernetes.io/zone` labels from the Kubernetes nodes. On AWS ECS, Consul uses the `AWS_REGION` environment variable and `AvailabilityZone` attribute of the ECS task meta. +On Kubernetes, Consul automatically populates geographic information about service instances using the `topology.kubernetes.io/region` and `topology.kubernetes.io/zone` labels from the Kubernetes nodes. On AWS ECS, Consul uses the `AWS_REGION` environment variable and `AvailabilityZone` attribute of the ECS task meta. ### Requirements -You should only enable locality-aware routing when each set of external upstream instances within the same zone and region have enough capacity to handle requests from downstream service instances in their respective zones. Locality-aware routing is an advanced feature that may adversely impact service capacity if used incorrectly. When enabled, Consul routes all traffic to the nearest set of service instances and only fails over when no healthy instances are available in the nearest set. +You should only enable locality-aware routing when each set of external upstream instances within the same zone and region have enough capacity to handle requests from downstream service instances in their respective zones. Locality-aware routing is an advanced feature that may adversely impact service capacity if used incorrectly. When enabled, Consul routes all traffic to the nearest set of service instances and only fails over when no healthy instances are available in the nearest set. ## Specify the locality of your Consul agents The `locality` configuration on a Consul client applies to all services registered to the client. -1. Configure the `locality` block in your Consul client agent configuration files. The `locality` block is a map containing the `region` and `zone` parameters. +1. Configure the `locality` block in your Consul client agent configuration files. The `locality` block is a map containing the `region` and `zone` parameters. The parameters should match the values for regions and zones defined in your network. Refer to [`locality`](/consul/docs/agent/config/config-files#locality) in the agent configuration reference for additional information. @@ -59,7 +59,7 @@ locality = { ## Specify the localities of your service instances (optional) -This step is optional in most scenarios. Refer to [Workflow](#workflow) for additional information. +This step is optional in most scenarios. Refer to [Workflow](#workflow) for additional information. 1. Configure the `locality` block in your service definition for both downstream (client) and upstream services. The `locality` block is a map containing the `region` and `zone` parameters. When you start a proxy for the service, Consul passes the locality to the proxy so that it can route traffic accordingly. @@ -70,7 +70,7 @@ Register or re-register the service to apply the configuration. Refer to [Regist In the following example, the `web` service is available in the `us-west-1` region and `us-west-1a` zone on AWS: -```hcl +```hcl service { id = "web" locality = { @@ -172,7 +172,7 @@ Apply the configuration by either running the [`consul config write` CLI command - + ```hcl Kind = "service-resolver" Name = "api" @@ -182,10 +182,10 @@ Apply the configuration by either running the [`consul config write` CLI command ``` - + - + ```json { "kind": "service-resolver", @@ -199,7 +199,7 @@ Apply the configuration by either running the [`consul config write` CLI command - + ```yaml apiversion: consul.hashicorp.com/v1alpha1 kind: ServiceResolver @@ -209,7 +209,7 @@ Apply the configuration by either running the [`consul config write` CLI command prioritizeByLocality: mode: failover ``` - + @@ -218,32 +218,32 @@ Apply the configuration by either running the [`consul config write` CLI command - + ```shell-session $ consul config write api-resolver.hcl ``` - + - + - + ```shell-session $ kubectl apply -f api-resolver.yaml ``` - + - + ```shell-session $ curl --request PUT --data @api-resolver.hcl http://127.0.0.1:8500/v1/config ``` - + ### Configure locality on Kubernetes test clusters explicitly -You can explicitly configure locality for each Kubernetes node so that you can test locality-aware routing with a local Kubernetes cluster or in an environment where `topology.kubernetes.io` labels are not set. +You can explicitly configure locality for each Kubernetes node so that you can test locality-aware routing with a local Kubernetes cluster or in an environment where `topology.kubernetes.io` labels are not set. Run the `kubectl label node` command and specify the locality as arguments. The following example specifies the `us-east-1` region and `us-east-1a` zone for the node: @@ -261,7 +261,7 @@ Consul configures Envoy's built-in [`overprovisioning_factor`](https://www.envoy To verify that locality-aware routing and failover configurations, you can inspect Envoy's xDS configuration dump for a downstream proxy. Refer to the [consul-k8s CLI docs](https://developer.hashicorp.com/consul/docs/k8s/k8s-cli#proxy-read) for details on how to obtain the xDS configuration dump on Kubernetes. For other workloads, use the Envoy [admin interface](https://www.envoyproxy.io/docs/envoy/latest/operations/admin) and ensure that you [include EDS](https://www.envoyproxy.io/docs/envoy/latest/operations/admin#get--config_dump?include_eds). -Inspect the [priority](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/priority#arch-overview-load-balancing-priority-levels) on each set of endpoints under the upstream `ClusterLoadAssignment` in the `EndpointsConfigDump`. Alternatively, the same priorities should be visibile within the output of the [`/clusters?format=json`](https://www.envoyproxy.io/docs/envoy/latest/operations/admin#get--clusters?format=json) admin endpoint. +Inspect the [priority](https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/load_balancing/priority#arch-overview-load-balancing-priority-levels) on each set of endpoints under the upstream `ClusterLoadAssignment` in the `EndpointsConfigDump`. Alternatively, the same priorities should be visible within the output of the [`/clusters?format=json`](https://www.envoyproxy.io/docs/envoy/latest/operations/admin#get--clusters?format=json) admin endpoint. ```json { diff --git a/website/content/docs/connect/observability/service.mdx b/website/content/docs/connect/observability/service.mdx new file mode 100644 index 0000000000..55b4580aff --- /dev/null +++ b/website/content/docs/connect/observability/service.mdx @@ -0,0 +1,212 @@ +--- +layout: docs +page_title: Monitoring service-to-service communication with Envoy +description: >- + Learn to monitor the appropriate metrics when using Envoy proxy. +--- + +# Monitoring service-to-service communication with Envoy + +When running a service mesh with Envoy as the proxy, there are a wide array of possible metrics produced from traffic flowing through the data plane. This document covers a set of scenarios and key baseline metrics and potential alerts that will help you maintain the overall health and resilience of the mesh for HTTP services. In addition, it provides examples of using these metrics in specific ways to generate a Grafana dashboard using a Prometheus backend to better understand how the metrics behave. + +When collecting metrics, it is important to establish a baseline. This baseline ensures your Consul deployment is healthy, and serves as a reference point when troubleshooting abnormal Cluster behavior. Once you have established a baseline for your metrics, use them and the following recommendations to configure reasonable alerts for your Consul agent. + + + + The following examples assume that the operator adds the cluster name (i.e. datacenter) using the label “cluster” and the node name (i.e. machine or pod) using the label “node” to all scrape targets. + + + +## General scenarios + +### Is Envoy's configuration growing stale? + +When Envoy connects to the Consul control plane over xDS, it will rapidly converge to the current configuration that the control plane expects it to have. If the xDS stream terminates and does not reconnect for an extended period, then the xDS configuration currently in the Envoy instances will “fail static” and slowly grow out of date. + +##### Metric + +`envoy_control_plane_connected_state` + +#### Alerting + +If the value for a given node/pod/machine was 0 for an extended period of time. + +#### Example dashboard (table) + +``` +group(last_over_time(envoy_control_plane_connected_state{cluster="$cluster"}[1m] ) == 0) by (node) +``` + +## Inbound traffic scenarios + +### Is this service being sent requests? + +Within a mesh, a request travels from one service to another. You may choose to measure many relevant metrics from the calling-side, the serving-side, or both. + +It is useful to track the perceived request rate of requests from the calling-side as that would include all requests, even those that fail to arrive at the serving-side due to any failures. + +Any measurement of the request rate is also generally useful for capacity planning purposes as increased traffic typically correlates with a need for a scale-up event in the near future. + +##### Metric + +`envoy_cluster_upstream_rq_total` + +#### Alerting + +If the value has a significant change, check if services are properly interacting with each other and if you need to increase your Consul agent resource requirements. + +#### Example dashboard (plot; rate) + +``` +sum(irate(envoy_cluster_upstream_rq_total{consul_destination_datacenter="$cluster", +consul_destination_service="$service"}[1m])) by (cluster, local_cluster) +``` + +### Are requests sent to this service mostly successful? + +A service mesh is about communication between services, so it is important to track the perceived success rate of requests witnessed by the calling services. + +##### Metric + +`envoy_cluster_upstream_rq_xx` + +#### Alerting + +If the value crosses a user defined baseline. + +#### Example dashboard (plot; %) + +``` +sum(irate(envoy_cluster_upstream_rq_xx{envoy_response_code_class!="5",consul_destination_datacenter="$cluster",consul_destination_service="$service"}[1m])) by (cluster, local_cluster) / sum(irate(envoy_cluster_upstream_rq_xx{consul_destination_datacenter="$cluster",consul_destination_service="$service"}[1m])) by (cluster, local_cluster) +``` + +### Are requests sent to this service handled in a timely manner? + +If you undersize your infrastructure from a resource perspective, then you may expect a decline in response speed over time. You can track this by plotting the 95th percentile of the latency as experienced by the clients. + +##### Metric + +`envoy_cluster_upstream_rq_time_bucket` + +#### Alerting + +If the value crosses a user defined baseline. + +#### Example dashboard (plot; value) + +``` +histogram_quantile(0.95, sum(rate(envoy_cluster_upstream_rq_time_bucket{consul_destination_datacenter="$cluster",consul_destination_service="$service",local_cluster!=""}[1m])) by (le, cluster, local_cluster)) +``` + +### Is this service responding to requests that it receives? + +Unlike the perceived request rate, which is measured from the calling side, this is the real request rate measured on the serving-side. This is a serving-side parallel metric that can help clarify underlying causes of problems in the calling-side equivalent metric. Ideally this metric should roughly track the calling side values in a 1-1 manner. + +##### Metric + +`envoy_http_downstream_rq_total` + +#### Alerting + +If the value crosses a user defined baseline. + +#### Example dashboard (plot; rate) + +``` +sum(irate(envoy_http_downstream_rq_total{cluster="$cluster",local_cluster="$service",envoy_http_conn_manager_prefix="public_listener"}[1m])) +``` + +### Are responses from this service mostly successful? + +Unlike the perceived success rate of requests, which is measured from the calling side, this is the real success rate of requests measured on the serving-side. This is a serving-side parallel metric that can help clarify underlying causes of problems in the calling-side equivalent metric. Ideally this metric should roughly track the calling side values in a 1-1 manner. + +##### Metrics + +`envoy_http_downstream_rq_total` + +`envoy_http_downstream_rq_xx` + +#### Alerting + +If the value crosses a user defined baseline. + +#### Example dashboard (plot; %) + +##### Total + +``` +sum(increase(envoy_http_downstream_rq_total{cluster="$cluster",local_cluster="$service",envoy_http_conn_manager_prefix="public_listener"}[1m])) +``` + +##### BY STATUS CODE: + +``` +sum(increase(envoy_http_downstream_rq_xx{cluster="$cluster",local_cluster="$service",envoy_http_conn_manager_prefix="public_listener"}[1m])) by (envoy_response_code_class) +``` + +## Outbound traffic scenarios + +### Is this service sending traffic to its upstreams? + +Similar to the real request rate for requests arriving at a service, it may be helpful to view the perceived request rate departing from a service through its upstreams. + +##### Metric + +`envoy_cluster_upstream_rq_total` + +#### Alerting + +If the value crosses a user defined success threshold. + +#### Example dashboard (plot; rate) + +``` +sum(irate(envoy_cluster_upstream_rq_total{cluster="$cluster", +local_cluster="$service", +consul_destination_target!=""}[1m])) by (consul_destination_target) +``` + +### Are requests from this service to its upstreams mostly successful? + +Similar to the real success rate of requests arriving at a service, it is also important to track the perceived success rate of requests departing from a service through its upstreams. + +##### Metric + +`envoy_cluster_upstream_rq_xx` + +#### Alerting + +If the value crosses a user defined success threshold. + +#### Example dashboard (plot; value) + +``` +sum(irate(envoy_cluster_upstream_rq_xx{envoy_response_code_class!="5", +cluster="$cluster",local_cluster="$service", +consul_destination_target!=""}[1m])) by (consul_destination_target) / sum(irate(envoy_cluster_upstream_rq_xx{cluster="$cluster",local_cluster="$service",consul_destination_target!=""}[1m])) by (consul_destination_target) +``` + +### Are requests from this service to its upstreams handled in a timely manner? + +Similar to the latency of requests departing for a service, it is useful to track the 95th percentile of the latency of requests departing from a service through its upstreams. + +##### Metric + +`envoy_cluster_upstream_rq_time_bucket` + +#### Alerting + +If the value crosses a user defined success threshold. + +#### Example dashboard (plot; value) + +``` +histogram_quantile(0.95, sum(rate(envoy_cluster_upstream_rq_time_bucket{cluster="$cluster", +local_cluster="$service",consul_target!=""}[1m])) by (le, consul_destination_target)) +``` + +## Next steps + +In this guide, you learned recommendations for monitoring your Envoy metrics, and why monitoring these metrics is important for your Consul deployment. + +To learn about monitoring Consul components, visit our [Monitoring Consul components](/well-architected-framework/reliability/reliability-consul-monitoring-consul-components) documentation. diff --git a/website/content/docs/connect/proxies/deploy-service-mesh-proxies.mdx b/website/content/docs/connect/proxies/deploy-service-mesh-proxies.mdx index 630da4a131..0bb3f7df03 100644 --- a/website/content/docs/connect/proxies/deploy-service-mesh-proxies.mdx +++ b/website/content/docs/connect/proxies/deploy-service-mesh-proxies.mdx @@ -1,13 +1,13 @@ --- layout: docs page_title: Deploy service mesh proxies -description: >- +description: >- Envoy and other proxies in Consul service mesh enable service-to-service communication across your network. Learn how to deploy service mesh proxies in this topic. --- # Deploy service mesh proxies services -This topic describes how to create, register, and start service mesh proxies in Consul. Refer to [Service mesh proxies overview](/consul/docs/connect/proxies) for additional information about how proxies enable Consul functionalities. +This topic describes how to create, register, and start service mesh proxies in Consul. Refer to [Service mesh proxies overview](/consul/docs/connect/proxies) for additional information about how proxies enable Consul functionalities. For information about deploying proxies as sidecars for service instances, refer to [Deploy sidecar proxy services](/consul/docs/connect/proxies/deploy-sidecar-services). @@ -15,7 +15,7 @@ For information about deploying proxies as sidecars for service instances, refer Complete the following steps to deploy a service mesh proxy: -1. It is not required, but you can create a proxy defaults configuration entry that contains global passthrough settings for all Envoy proxies. +1. It is not required, but you can create a proxy defaults configuration entry that contains global passthrough settings for all Envoy proxies. 1. Create a service definition file and specify the proxy configurations in the `proxy` block. 1. Register the service using the API or CLI. 1. Start the proxy service. Proxies appear in the list of services registered to Consul, but they must be started before they begin to route traffic in your service mesh. @@ -31,29 +31,29 @@ If you want to define global passthrough settings for all Envoy proxies, create 1. Create a proxy defaults configuration entry and specify the following parameters: - `Kind`: Must be set to `proxy-defaults` - `Name`: Must be set to `global` -1. Configure any additional settings you want to apply to all proxies. Refer to [Proxy defaults configuration entry reference](/consul/docs/connect/config-entries/proxy-defaults) for details about all settings available in the configuraiton entry. +1. Configure any additional settings you want to apply to all proxies. Refer to [Proxy defaults configuration entry reference](/consul/docs/connect/config-entries/proxy-defaults) for details about all settings available in the configuration entry. 1. Apply the configuration by either calling the [`/config` HTTP API endpoint](/consul/api-docs/config) or running the [`consul config write` CLI command](/consul/commands/config/write). The following example writes a proxy defaults configuration entry from a local HCL file using the CLI: ```shell-session $ consul config write proxy-defaults.hcl ``` -## Define service mesh proxy +## Define service mesh proxy Create a service definition file and configure the following fields to define a service mesh proxy: 1. Set the `kind` field to `connect-proxy`. Refer to the [services configuration reference](/consul/docs/services/configuration/services-configuration-reference#kind) for information about other kinds of proxies you can declare. 1. Specify a name for the proxy service in the `name` field. Consul applies the configurations to any proxies you bootstrap with the same name. -1. In the `proxy.destination_service_name` field, specify the name of the service that the proxy represents. -1. Configure any additional proxy behaviors that you want to implement in the `proxy` block. Refer to the [Service mesh proxy configuration reference](/consul/docs/connect/proxies/proxy-config-reference) for information about all parameters. +1. In the `proxy.destination_service_name` field, specify the name of the service that the proxy represents. +1. Configure any additional proxy behaviors that you want to implement in the `proxy` block. Refer to the [Service mesh proxy configuration reference](/consul/docs/connect/proxies/proxy-config-reference) for information about all parameters. 1. Specify a port number where other services registered with Consul can discover and connect to the proxies service in the `port` field. To ensure that services only allow external connections established through the service mesh protocol, you should configure all services to only accept connections on a loopback address. Refer to the [Service mesh proxy configuration reference](/consul/docs/connect/proxies/proxy-config-reference) for example configurations. -## Register the service +## Register the service Provide the service definition to the Consul agent to register your proxy service. You can use the same methods for registering proxy services as you do for registering application services: - + - Place the service definition in a Consul agent's configuration directory and start, restart, or reload the agent. Use this method when implementing changes to an existing proxy service. - Use the `consul services register` command to register the proxy service with a running Consul agent. - Call the `/agent/service/register` HTTP API endpoint to register the proxy service with a running Consul agent. @@ -68,7 +68,7 @@ $ consul services register proxy.hcl ## Start the proxy -Envoy requires a bootstrap configuration file before it can start. Use the [`consul connect envoy` command](/consul/commands/connect/envoy) to create the Envoy bootstrap configuration and start the proxy service. Specify the ID of the proxy you want to start with the `-proxy-id` option. +Envoy requires a bootstrap configuration file before it can start. Use the [`consul connect envoy` command](/consul/commands/connect/envoy) to create the Envoy bootstrap configuration and start the proxy service. Specify the ID of the proxy you want to start with the `-proxy-id` option. The following example command starts an Envoy proxy for the `web-proxy` service: @@ -76,4 +76,4 @@ The following example command starts an Envoy proxy for the `web-proxy` service: $ consul connect envoy -proxy-id=web-proxy ``` -For details about operating an Envoy proxy in Consul, refer to the [Envoy proxy reference](/consul/docs/connect/proxies/envoy). +For details about operating an Envoy proxy in Consul, refer to the [Envoy proxy reference](/consul/docs/connect/proxies/envoy). diff --git a/website/content/docs/connect/proxies/deploy-sidecar-services.mdx b/website/content/docs/connect/proxies/deploy-sidecar-services.mdx index ad533b5946..c42a5b2c7f 100644 --- a/website/content/docs/connect/proxies/deploy-sidecar-services.mdx +++ b/website/content/docs/connect/proxies/deploy-sidecar-services.mdx @@ -11,15 +11,15 @@ This topic describes how to create, register, and start sidecar proxy services i ## Overview -Sidecar proxies run on the same node as the single service instance that they handle traffic for. -They may be on the same VM or running as a separate container in the same network namespace. +Sidecar proxies run on the same node as the single service instance that they handle traffic for. +They may be on the same VM or running as a separate container in the same network namespace. You can attach a sidecar proxy to a service you want to deploy to your mesh: -1. It is not required, but you can create a proxy defaults configuration entry that contains global passthrough settings for all Envoy proxies. +1. It is not required, but you can create a proxy defaults configuration entry that contains global passthrough settings for all Envoy proxies. 1. Create the service definition and include the `connect` block. The `connect` block contains the sidecar proxy configurations that allow the service to interact with other services in the mesh. 1. Register the service using either the API or CLI. -1. Start the sidecar proxy service. +1. Start the sidecar proxy service. ## Requirements @@ -32,21 +32,21 @@ If you want to define global passthrough settings for all Envoy proxies, create 1. Create a proxy defaults configuration entry and specify the following parameters: - `Kind`: Must be set to `proxy-defaults` - `Name`: Must be set to `global` -1. Configure any additional settings you want to apply to all proxies. Refer to [Proxy defaults configuration entry reference](/consul/docs/connect/config-entries/proxy-defaults) for details about all settings available in the configuraiton entry. +1. Configure any additional settings you want to apply to all proxies. Refer to [Proxy defaults configuration entry reference](/consul/docs/connect/config-entries/proxy-defaults) for details about all settings available in the configuration entry. 1. Apply the configuration by either calling the [`/config` API endpoint](/consul/api-docs/config) or running the [`consul config write` CLI command](/consul/commands/config/write). The following example writes a proxy defaults configuration entry from a local HCL file using the CLI: ```shell-session $ consul config write proxy-defaults.hcl ``` -## Define service mesh proxy +## Define service mesh proxy -Create a service definition and configure the following fields: +Create a service definition and configure the following fields: -1. `name`: Specify a name for the service you want to attach a sidecar proxy to in the `name` field. This field is required for all services you want to register in Consul. +1. `name`: Specify a name for the service you want to attach a sidecar proxy to in the `name` field. This field is required for all services you want to register in Consul. 1. `port`: Specify a port number where other services registered with Consul can discover and connect to the service in the `port` field. This field is required for all services you want to register in Consul. 1. `connect`: Set the `connect` field to `{ sidecar_service: {} }`. The `{ sidecar_service: {} }` value is a macro that applies a set of default configurations that enable you to quickly implement a sidecar. Refer to [Sidecar service defaults](#sidecar-service-defaults) for additional information. -1. Configure any additional options for your service. Refer to [Services configuration reference](/consul/docs/services/configuration/services-configuration-reference) for details. +1. Configure any additional options for your service. Refer to [Services configuration reference](/consul/docs/services/configuration/services-configuration-reference) for details. In the following example, a service named `web` is configured with a sidecar proxy: @@ -60,7 +60,7 @@ service = { port = 8080 connect = { sidecar_service = {} } } -``` +``` @@ -89,7 +89,7 @@ When Consul processes the service definition, it generates the following configu ```hcl -services = [ +services = [ { name = "web" port = 8080 @@ -114,7 +114,7 @@ services = [ } ] -``` +``` @@ -156,12 +156,12 @@ services = [ - + -## Register the service +## Register the service Provide the service definition to the Consul agent to register your proxy service. You can use the same methods for registering proxy services as you do for registering application services: - + - Place the service definition in a Consul agent's configuration directory and start, restart, or reload the agent. Use this method when implementing changes to an existing proxy service. - Use the `consul services register` command to register the proxy service with a running Consul agent. - Call the `/agent/service/register` HTTP API endpoint to register the proxy service with a running Consul agent. @@ -176,7 +176,7 @@ $ consul services register proxy.hcl ## Start the proxy -Envoy requires a bootstrap configuration file before it can start. Use the [`consul connect envoy` command](/consul/commands/connect/envoy) to create the Envoy bootstrap configuration and start the proxy service. Specify the name of the service with the attached proxy with the `-sidecar-for` option. +Envoy requires a bootstrap configuration file before it can start. Use the [`consul connect envoy` command](/consul/commands/connect/envoy) to create the Envoy bootstrap configuration and start the proxy service. Specify the name of the service with the attached proxy with the `-sidecar-for` option. The following example command starts an Envoy sidecar proxy for the `web` service: @@ -184,11 +184,11 @@ The following example command starts an Envoy sidecar proxy for the `web` servic $ consul connect envoy -sidecar-for=web ``` -For details about operating an Envoy proxy in Consul, refer to [](/consul/docs/connect/proxies/envoy) +For details about operating an Envoy proxy in Consul, refer to [](/consul/docs/connect/proxies/envoy) ## Configuration reference -The `sidecar_service` block is a service definition that can contain most regular service definition fields. Refer to [Limitations](#limitations) for information about unsupported service definition fields for sidecar proxies. +The `sidecar_service` block is a service definition that can contain most regular service definition fields. Refer to [Limitations](#limitations) for information about unsupported service definition fields for sidecar proxies. Consul treats sidecar proxy service definitions as a root-level service definition. All fields are optional in nested definitions, which default to opinionated settings that are intended to reduce burden of setting up a sidecar proxy. @@ -224,7 +224,7 @@ proxy. In the following example, the `sidecar_service` macro sets baselines configurations for the proxy, but the [proxy upstreams](/consul/docs/connect/proxies/proxy-config-reference#upstream-configuration-reference) and [built-in proxy -configuration](/consul/docs/connect/proxies/built-in) fields contain custom values: +configuration](/consul/docs/connect/proxies/built-in) fields contain custom values: ```json { @@ -281,4 +281,4 @@ service's ID, which enables the following behavior. - When reloading the configuration files, if a service definition changes its ID, then a new service instance and a new sidecar instance are registered. The old instance and proxy are removed because they are no longer found in - the configuration files. \ No newline at end of file + the configuration files. diff --git a/website/content/docs/connect/proxies/envoy-extensions/index.mdx b/website/content/docs/connect/proxies/envoy-extensions/index.mdx index 79f1dbb1f3..8fd19feed3 100644 --- a/website/content/docs/connect/proxies/envoy-extensions/index.mdx +++ b/website/content/docs/connect/proxies/envoy-extensions/index.mdx @@ -21,6 +21,7 @@ Instead of modifying Consul code, you can configure your services to use Envoy e Envoy extensions enable additional service mesh functionality in Consul by changing how the sidecar proxies behave. Extensions dynamically modify the configuration of Envoy proxies based on Consul configuration entries, enabling a wider set of use cases for the service mesh traffic that passes through an Envoy proxy. Consul supports the following extensions: - External authorization +- Fault injection - Lua - Lambda - OpenTelemetry Access Logging @@ -31,6 +32,10 @@ Envoy extensions enable additional service mesh functionality in Consul by chang The `ext-authz` extension lets you configure external authorization filters for Envoy proxy so that you can route requests to external authorization systems. Refer to the [external authorization documentation](/consul/docs/connect/proxies/envoy-extensions/usage/ext-authz) for more information. +### Fault injection + +The `fault-injection` extension lets you alter responses from an upstream service so that users can test the resilience of their system to different unexpected issues. Refer to the [fault injection documentation](/consul/docs/connect/manage-traffic/fault-injection) for more information. + ### Lambda The `lambda` Envoy extension enables services to make requests to AWS Lambda functions through the mesh as if they are a normal part of the Consul catalog. Refer to the [Lambda extension documentation](/consul/docs/connect/proxies/envoy-extensions/usage/lambda) for more information. diff --git a/website/content/docs/connect/proxies/envoy.mdx b/website/content/docs/connect/proxies/envoy.mdx index 479485af9c..8180787efb 100644 --- a/website/content/docs/connect/proxies/envoy.mdx +++ b/website/content/docs/connect/proxies/envoy.mdx @@ -58,9 +58,11 @@ apply to both Consul Enterprise and Consul community edition (CE). | Consul Version | Compatible Envoy Versions | | -------------- | -------------------------------------- | -| 1.18.x CE | 1.28.2, 1.27.4, 1.26.8, 1.25.11 | -| 1.17.x | 1.27.4, 1.26.8, 1.25.11, 1.24.12 | -| 1.16.x | 1.26.8, 1.25.11, 1.24.12, 1.23.12 | +| 1.20.x CE | 1.31.x, 1.30.x, 1.29.x, 1.28.x | +| 1.19.x CE | 1.29.x, 1.28.x, 1.27.x, 1.26.x | +| 1.18.x CE | 1.28.x, 1.27.x, 1.26.x, 1.25.x | +| 1.17.x | 1.27.x, 1.26.x, 1.25.x, 1.24.x | +| 1.16.x | 1.26.x, 1.25.x, 1.24.x, 1.23.x | #### Enterprise Long Term Support releases @@ -71,8 +73,8 @@ until the LTS release reaches its end of maintenance. | Consul Version | Compatible Envoy Versions | | -------------- | -----------------------------------------------------------------------------------| -| 1.18.x Ent | 1.28.2, 1.27.4, 1.26.8, 1.25.11 | -| 1.15.x Ent | 1.28.2, 1.27.4, 1.26.8, 1.25.11, 1.24.12, 1.23.12, 1.22.11 | +| 1.18.x Ent | 1.29.x, 1.28.x, 1.27.x, 1.26.x, 1.25.x | +| 1.15.x Ent | 1.29.x, 1.28.x, 1.27.x, 1.26.x, 1.25.x, 1.24.x, 1.23.x, 1.22.x | ### Envoy and Consul Dataplane @@ -104,6 +106,7 @@ apply to both Consul Enterprise and Consul community edition (CE). | Consul Version | Default `consul-dataplane` Version | Other compatible `consul-dataplane` Versions | | -------------- | -------------------------------------|----------------------------------------------| +| 1.19.x CE | 1.5.x (Envoy 1.29.x) | 1.4.x (Envoy 1.28.x) | | 1.18.x CE | 1.4.x (Envoy 1.28.x) | 1.3.x (Envoy 1.27.x) | | 1.17.x | 1.3.x (Envoy 1.27.x) | 1.4.x (Envoy 1.28.x), 1.2.x (Envoy 1.26.x) | | 1.16.x | 1.2.x (Envoy 1.26.x) | 1.3.x (Envoy 1.27.x), 1.1.x (Envoy 1.25.x) | @@ -117,6 +120,7 @@ until the LTS release reaches its end of maintenance. | Consul Version | Default `consul-dataplane` Version | Other compatible `consul-dataplane` Versions | | -------------- | -------------------------------------|----------------------------------------------| +| 1.19.x Ent | 1.5.x (Envoy 1.29.x) | 1.4.x (Envoy 1.28.x) | | 1.18.x Ent | 1.4.x (Envoy 1.28.x) | 1.3.x (Envoy 1.27.x) | | 1.15.x Ent | 1.1.x (Envoy 1.26.x) | 1.4.x (Envoy 1.28.x) - 1.0.x (Envoy 1.24.x) | @@ -129,6 +133,7 @@ of Consul dataplane use a maintained version of Envoy. | `consul-dataplane` Version Range | Associated Consul Enterprise LTS version | Contained Envoy Binary Version | | -------------------------------- | ---------------------------------------- | ------------------------------ | +| 1.5.0 - 1.5.latest | 1.18.x Ent | Envoy 1.29.x | | 1.4.0 - 1.4.latest | 1.18.x Ent | Envoy 1.28.x | | 1.1.9 - 1.1.latest | 1.15.x Ent | Envoy 1.26.x | | 1.1.0 - 1.1.8 | 1.15.x Ent | Envoy 1.25.x | diff --git a/website/content/docs/connect/security.mdx b/website/content/docs/connect/security.mdx index 1889f57406..a65d4fabcd 100644 --- a/website/content/docs/connect/security.mdx +++ b/website/content/docs/connect/security.mdx @@ -32,10 +32,38 @@ Consul should be configured with a default deny intention policy. This forces all service-to-service communication to be explicitly allowed via an allow [intention](/consul/docs/connect/intentions). +One advantage of using a default deny policy in combination with specific "allow" rules +is that a failure of intentions due to misconfiguration always results in +_denied_ traffic, rather than unwanted _allowed_ traffic. + In the absence of `default_intention_policy` Consul will fall back to the ACL default policy when determining whether to allow or deny communications without an explicit intention. +### Request Normalization Configured for L7 Intentions + +Atypical traffic patterns may interfere with the enforcement of L7 intentions. For +example, if a service makes request to a non-normalized URI path and Consul is not +configured to force path normalization, it becomes possible to circumvent path match rules. While a +default deny policy can limit the impact of this issue, we still recommend +that you review your current request normalization configuration. Normalization is critical to avoid unwanted +traffic, especially when using unrecommended security options such as a default allow intentions policy. + +Consul adopts a default normalization mode that adheres to [RFC 3986]( +https://tools.ietf.org/html/rfc3986#section-6), but additional options to enable stricter +normalization are available in the cluster-wide [Mesh configuration entry]( +/consul/docs/connect/config-entries/mesh). We recommend reviewing these options and +enabling the strictest set that does not interfere with application traffic. + +We also recommend that you review L7 intention header match rules for potential +issues with multiple header values. Refer to the [service intentions +configuration entry reference](/consul/docs/connect/config-entries/service-intentions#spec-sources-permissions-http-header) +for more information. + +You do not need to enable request normalization if you are not using L7 intentions. +However, normalization may also benefit the use of other service mesh features that +rely on L7 attribute matching, such as [service routers](/consul/docs/connect/manage-traffic#routing). + ### ACLs Enabled with Default Deny Consul must be configured to use ACLs with a default deny policy. This forces @@ -51,6 +79,10 @@ this. **If ACLs are not enabled**, deny intentions will still be enforced, but a may edit intentions. This renders the security of the created intentions effectively useless. +The advantage of a default deny policy in combination with specific "allow" rules +is that at worst, a failure of intentions due to misconfiguration will result in +_denied_ traffic, rather than unwanted _allowed_ traffic. + ### TCP and UDP Encryption Enabled TCP and UDP encryption must be enabled to prevent plaintext communication diff --git a/website/content/docs/dynamic-app-config/kv/index.mdx b/website/content/docs/dynamic-app-config/kv/index.mdx index 982c772938..677037321d 100644 --- a/website/content/docs/dynamic-app-config/kv/index.mdx +++ b/website/content/docs/dynamic-app-config/kv/index.mdx @@ -111,7 +111,7 @@ increment to the `LockIndex` and the session value is updated to reflect the session holding the lock. Review the session documentation for more information on the [integration](/consul/docs/dynamic-app-config/sessions#k-v-integration). -Review the following tutorials to learn how to use Consul sessions for [application leader election](/consul/tutorials/developer-configuration/application-leader-elections) and +Review the following tutorials to learn how to use Consul sessions for [application leader election](/consul/docs/dynamic-app-config/sessions/application-leader-election) and to [build distributed semaphores](/consul/tutorials/developer-configuration/distributed-semaphore). ### Vault diff --git a/website/content/docs/dynamic-app-config/sessions/application-leader-election.mdx b/website/content/docs/dynamic-app-config/sessions/application-leader-election.mdx new file mode 100644 index 0000000000..5b14bcdc9e --- /dev/null +++ b/website/content/docs/dynamic-app-config/sessions/application-leader-election.mdx @@ -0,0 +1,396 @@ +--- +layout: docs +page_title: Application leader election +description: >- + Learn how to perform client-side leader elections using sessions and Consul key/value (KV) store. +--- + +# Application leader election + +This topic describes the process for building client-side leader elections for service instances using Consul's [session mechanism for building distributed locks](/consul/docs/dynamic-app-config/sessions) and the [Consul key/value store](/consul/docs/dynamic-app-config/kv), which is Consul's key/value datastore. + +This topic is not related to Consul's leader election. For more information about the Raft leader election used internally by Consul, refer to +[consensus protocol](/consul/docs/architecture/consensus) documentation. + +## Background + +Some distributed applications, like HDFS or ActiveMQ, require setting up one instance as a leader to ensure application data is current and stable. + +Consul's support for [sessions](/consul/docs/dynamic-app-config/sessions) and [watches](/consul/docs/dynamic-app-config/watches) allows you to build a client-side leader election process where clients use a lock on a key in the KV datastore to ensure mutual exclusion and to gracefully handle failures. + +All service instances that are participating should coordinate on a key format. We recommend the following pattern: + +```plaintext +service//leader +``` + +## Requirements + +- A running Consul server +- A path in the Consul KV datastore to acquire locks and to store information about the leader. The instructions on this page use the following key: `service/leader`. +- If ACLs are enabled, a token with the following permissions: + - `session:write` permissions over the service session name + - `key:write` permissions over the key + - The `curl` command + +Expose the token using the `CONSUL_HTTP_TOKEN` environment variable. + +## Client-side leader election procedure + +The workflow for building a client-side leader election process has the following steps: + +- For each client trying to acquire the lock: + 1. [Create a session](#create-a-new-session) associated with the client node. + 1. [Acquire the lock](#acquire-the-lock) on the designated key in the KV store using the `acquire` parameter. + 1. [Watch the KV key](#watch-the-kv-key-for-locks) to verify if the lock was released. If no lock is present, try to acquire a lock. + +- For the client that acquires the lock: + 1. Periodically, [renew the session](#renew-a-session) to avoid expiration. + 1. Optionally, [release the lock](#release-a-lock). + +- For other services: + 1. [Watch the KV key](#watch-the-kv-key-for-locks) to verify there is at least one process holding the lock. + 1. Use the values written under the KV path to identify the leader and update configurations accordingly. + +## Create a new session + +Create a configuration for the session. +The minimum viable configuration requires that you specify the session name. The following example demonstrates this configuration. + + + +```json +{ + "Name": "session_name" +} +``` + + + +Create a session using the [`/session` Consul HTTP API](/consul/api-docs/session) endpoint. In the following example, the node's `hostname` is the session name. + +```shell-session +$ curl --silent \ + --header "X-Consul-Token: $CONSUL_HTTP_TOKEN" \ + --data '{"Name": "'`hostname`'"}' \ + --request PUT \ + http://127.0.0.1:8500/v1/session/create | jq +``` + + +The command returns a JSON object containing the ID of the newly created session. + +```json +{ + "ID": "d21d60ad-c2d2-b32a-7432-ca4048a4a7d6" +} +``` + +### Verify session + +Use the `/v1/session/list` endpoint to retrieve existing sessions. + +```shell-session +$ curl --silent \ + --header "X-Consul-Token: $CONSUL_HTTP_TOKEN" \ + --request GET \ + http://127.0.0.1:8500/v1/session/list | jq +``` + +The command returns a JSON array containing all available sessions in the system. + + + +```json +[ + { + "ID": "d21d60ad-c2d2-b32a-7432-ca4048a4a7d6", + "Name": "hashicups-db-0", + "Node": "hashicups-db-0", + "LockDelay": 15000000000, + "Behavior": "release", + "TTL": "", + "NodeChecks": [ + "serfHealth" + ], + "ServiceChecks": null, + "CreateIndex": 11956, + "ModifyIndex": 11956 + } +] +``` + + + +You can verify from the output that the session is associated with the `hashicups-db-0` node, which is the client agent where the API request was made. + +With the exception of the `Name`, all parameters are set to their default values. The session is created without a `TTL` value, which means that it never expires and requires you to delete it explicitly. + +Depending on your needs you can create sessions specifying more parameters such as: + +- `TTL` - If provided, the session is invalidated and deleted if it is not renewed before the TTL expires. +- `ServiceChecks` - Specifies a list of service checks to monitor. The session is invalidated if the checks return a critical state. + +By setting these extra parameters, you can create a client-side leader election workflow that automatically releases the lock after a specified amount of time since the last renew, or that automatically releases locks when the service holding them fails. + +For a full list of parameters available refer to the [`/session/create` endpoint documentation](/consul/api-docs/session#create-session). + +## Acquire the lock + +Create the data object to associate to the lock request. + +The data of the request should be a JSON object representing the local instance. This value is opaque to Consul, but it should contain whatever information clients require to communicate with your application. For example, it could be a JSON object that contains the node's name and the application's port. + + + +```json +{ + "Node": "node-name", + "Port": "8080" +} +``` + + + + + + +Acquire a lock for a given key using the PUT method on a [KV entry](/consul/api-docs/kv) with the +`?acquire=` query parameter. + +```shell-session +$ curl --silent \ + --header "X-Consul-Token: $CONSUL_HTTP_TOKEN" \ + --data '{"Node": "'`hostname`'"}' \ + --request PUT \ + http://localhost:8500/v1/kv/service/leader?acquire=d21d60ad-c2d2-b32a-7432-ca4048a4a7d6 | jq +``` + +This request returns either `true` or `false`. If `true`, the lock was acquired and +the local service instance is now the leader. If `false`, a different node acquired +the lock. + + + + + +```shell-session +$ consul kv put -acquire -session=d21d60ad-c2d2-b32a-7432-ca4048a4a7d6 /service/leader '{"Node": "'`hostname`'"}' +``` + +In case of success, the command exits with exit code `0` and outputs the following message. + + + +```plaintext +Success! Lock acquired on: service/leader +``` + + + +If the lock was already acquired by another node, the command exits with exit code `1` and outputs the following message. + + + +```plaintext +Error! Did not acquire lock +``` + + + + + + +This example used the node's `hostname` as the key data. This data can be used by the other services to create configuration files. + +Be aware that this locking system has no enforcement mechanism that requires clients to acquire a lock before they perform an operation. Any client can read, write, and delete a key without owning the corresponding lock. + +## Watch the KV key for locks + +Existing locks need to be monitored by all nodes involved in the client-side leader elections, as well as by the other nodes that need to know the identity of the leader. + + - Lock holders need to monitor the lock because the session might get invalidated by an operator. + - Other services that want to acquire the lock need to monitor it to check if the lock is released so they can try acquire the lock. + - Other nodes need to monitor the lock to see if the value of the key changed and update their configuration accordingly. + + + + +Monitor the lock using the GET method on a [KV entry](/consul/api-docs/kv) with the blocking query enabled. + +First, verify the latest index for the current value. + +```shell-session +$ curl --silent \ + --header "X-Consul-Token: $CONSUL_HTTP_TOKEN" \ + --request GET \ + http://127.0.0.1:8500/v1/kv/service/leader?index=1 | jq +``` + +The command outputs the key data, including the `ModifyIndex` for the object. + + + +```json +[ + { + "LockIndex": 0, + "Key": "service/leader", + "Flags": 0, + "Value": "eyJOb2RlIjogImhhc2hpY3Vwcy1kYi0wIn0=", + "Session": "d21d60ad-c2d2-b32a-7432-ca4048a4a7d6", + "CreateIndex": 12399, + "ModifyIndex": 13061 + } +] +``` + + + +Using the value of the `ModifyIndex`, run a [blocking query](/consul/api-docs/features/blocking) against the lock. + +```shell-session +$ curl --silent \ + --header "X-Consul-Token: $CONSUL_HTTP_TOKEN" \ + --request GET \ + http://127.0.0.1:8500/v1/kv/service/leader?index=13061 | jq +``` +The command hangs until a change is made on the KV path and after that the path data prints on the console. + + + +```json +[ + { + "LockIndex": 0, + "Key": "service/leader", + "Flags": 0, + "Value": "eyJOb2RlIjogImhhc2hpY3Vwcy1kYi0xIn0=", + "Session": "d21d60ad-c2d2-b32a-7432-ca4048a4a7d6", + "CreateIndex": 12399, + "ModifyIndex": 13329 + } +] +``` + + + +For automation purposes, add logic to the blocking query mechanism to trigger a command every time a change is returned. +A better approach is to use the CLI command `consul watch`. + + + + +Monitor the lock using the [`consul watch`](/consul/commands/watch) command. + +```shell-session +$ consul watch -type=key -key=service/leader cat | jq +``` + +In this example, the command output prints to the shell. However, it is possible to pass more complex option to the command as well as a script that contains more complex logic to react to the lock data change. + +An example output for the command is: + + + +```json +{ + "Key": "service/leader", + "CreateIndex": 12399, + "ModifyIndex": 13061, + "LockIndex": 0, + "Flags": 0, + "Value": "eyJOb2RlIjogImhhc2hpY3Vwcy1kYi0wIn0=", + "Session": "d21d60ad-c2d2-b32a-7432-ca4048a4a7d6" +} +``` + + + +The `consul watch` command polls the KV path for changes and runs the specified command on the output when a change is made. + + + + +From the output, notice that once the lock is acquired, the `Session` parameter contains the ID of the session that holds the lock. + +## Renew a session + +If a session is created with a `TTL` value set, you need to renew the session before the TTL expires. + +Use the [`/v1/session/renew`](/consul/api-docs/session#renew-session) endpoint to renew existing sessions. + +```shell-session +$ curl --silent \ + --header "X-Consul-Token: $CONSUL_HTTP_TOKEN" \ + --request PUT \ + http://127.0.0.1:8500/v1/session/renew/f027470f-2759-6b53-542d-066ae4185e67 | jq +``` + +If the command succeeds, the session information in JSON format is printed. + + + +```json +[ + { + "ID": "f027470f-2759-6b53-542d-066ae4185e67", + "Name": "test", + "Node": "consul-server-0", + "LockDelay": 15000000000, + "Behavior": "release", + "TTL": "30s", + "NodeChecks": [ + "serfHealth" + ], + "ServiceChecks": null, + "CreateIndex": 11842, + "ModifyIndex": 11842 + } +] +``` + + + +## Release a lock + +A lock associated with a session with no `TTL` value set might never be released, even when the service holding it fails. + +In such cases, you need to manually release the lock. + + + + +```shell-session +$ curl --silent \ + --header "X-Consul-Token: $CONSUL_HTTP_TOKEN" \ + --data '{"Node": "'`hostname`'"}' \ + --request PUT \ + http://localhost:8500/v1/kv/service/leader?release=d21d60ad-c2d2-b32a-7432-ca4048a4a7d6 | jq +``` + +The command prints `true` on success. + + + + +```shell-session +$ consul kv put -release -session=d21d60ad-c2d2-b32a-7432-ca4048a4a7d6 service/leader '{"Node": "'`hostname`'"}' +``` + +On success, the command outputs a success message. + + + +```plaintext +Success! Lock released on: service/leader +``` + + + + + + +After a lock is released, the key data do not show a value for `Session` in the results. +Other clients can use this as a way to coordinate their lock requests. + diff --git a/website/content/docs/dynamic-app-config/sessions.mdx b/website/content/docs/dynamic-app-config/sessions/index.mdx similarity index 97% rename from website/content/docs/dynamic-app-config/sessions.mdx rename to website/content/docs/dynamic-app-config/sessions/index.mdx index eb0c4cf495..3eb26f558f 100644 --- a/website/content/docs/dynamic-app-config/sessions.mdx +++ b/website/content/docs/dynamic-app-config/sessions/index.mdx @@ -134,9 +134,9 @@ the goal of Consul to protect against misbehaving clients. ## Leader Election -The primitives provided by sessions and the locking mechanisms of the KV -store can be used to build client-side leader election algorithms. -These are covered in more detail in the [Leader Election guide](/consul/tutorials/developer-configuration/application-leader-elections). +You can use the primitives provided by sessions and the locking mechanisms of the KV +store to build client-side leader election algorithms. +These are covered in more detail in the [Leader Election guide](/consul/docs/dynamic-app-config/sessions/application-leader-election). ## Prepared Query Integration diff --git a/website/content/docs/ecs/deploy/bind-addresses.mdx b/website/content/docs/ecs/deploy/bind-addresses.mdx index 0eea916687..acee9209ea 100644 --- a/website/content/docs/ecs/deploy/bind-addresses.mdx +++ b/website/content/docs/ecs/deploy/bind-addresses.mdx @@ -17,8 +17,8 @@ Binding workloads to the loopback address ensures that your application only rec Consul service mesh must be deployed to ECS before you can bind a network address. For more information, refer to the following topics: -- [Deploy Consul to ECS using the Terraform module](/consul/docs/ecs/deploy/install-terraform) -- [Deploy Consul to ECS manually](/consul/docs/ecs/deploy/install-manual) +- [Deploy Consul to ECS using the Terraform module](/consul/docs/ecs/deploy/terraform) +- [Deploy Consul to ECS manually](/consul/docs/ecs/deploy/manual) ## Change the listening address diff --git a/website/content/docs/ecs/deploy/configure-routes.mdx b/website/content/docs/ecs/deploy/configure-routes.mdx index 11014f99a3..ed9b996104 100644 --- a/website/content/docs/ecs/deploy/configure-routes.mdx +++ b/website/content/docs/ecs/deploy/configure-routes.mdx @@ -20,8 +20,8 @@ To enable tasks to call through the service mesh, complete the following steps: Consul service mesh must be deployed to ECS before you can bind a network address. For more information, refer to the following topics: -- [Deploy Consul to ECS using the Terraform module](/consul/docs/ecs/deploy/install-terraform) -- [Deploy Consul to ECS manually](/consul/docs/ecs/deploy/install-manual) +- [Deploy Consul to ECS using the Terraform module](/consul/docs/ecs/deploy/terraform) +- [Deploy Consul to ECS manually](/consul/docs/ecs/deploy/manual) ## Configure the sidecar proxy @@ -76,4 +76,4 @@ module "web" { ] ... } -``` \ No newline at end of file +``` diff --git a/website/content/docs/ecs/deploy/manual.mdx b/website/content/docs/ecs/deploy/manual.mdx index 52b0bda05b..9b54aa0579 100644 --- a/website/content/docs/ecs/deploy/manual.mdx +++ b/website/content/docs/ecs/deploy/manual.mdx @@ -18,24 +18,24 @@ You should have some familiarity with AWS ECS. Refer to [What is Amazon Elastic You must meet the following requirements and prerequisites to enable security features in Consul service mesh: - Enable [TLS encryption](https://developer.hashicorp.com/consul/docs/security/encryption#rpc-encryption-with-tls) on your Consul servers so that they can communicate security with Consul dataplane containers over gRPC. -- Enable [access control lists (ACLs)](/consul/docs/security/acl) on your Consul servers. ALCs provide authentication and authorization for access to Consul servers on the mesh. +- Enable [access control lists (ACLs)](/consul/docs/security/acl) on your Consul servers. ACLs provide authentication and authorization for access to Consul servers on the mesh. - You should be familiar with specifying sensitive data on ECS. Refer to [Passing sensitive data to a container](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the AWS documentation for additional information. You should be familiar with configuring Consul's secure features, including how to create ACL tokens and policies. Refer to the following resources: - [Create a service token](/consul/docs/security/acl/tokens/create/create-a-service-token) - [Day 1: Security tutorial](https://developer.hashicorp.com/consul/tutorials/security) for additional information. -Consul requires a unique IAM role for each ECS task family. Task IAM roles cannot be shared by different task families because the task family is unique to each Consul service. +Consul requires a unique IAM role for each ECS task family. Task IAM roles cannot be shared by different task families because the task family is unique to each Consul service. ## Configure ECS task definition file -Create a JSON file for the task definition. The task definition is the ECS blueprint for your software services on AWS. Refer to the [ECS task definitions in the AWS documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definitions.html) for additional information. +Create a JSON file for the task definition. The task definition is the ECS blueprint for your software services on AWS. Refer to the [ECS task definitions in the AWS documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definitions.html) for additional information. In addition to your application container, add configurations to your task definition that creates the following Consul containers: - Dataplane container - Control-plane container -- ECS controller container +- ECS controller container ## Top-level fields @@ -46,7 +46,7 @@ The following table describes the top-level fields you must include in the task | `family` | The task family name. This is used as the Consul service name by default. | string | | `networkMode` | Must be `awsvpc`, which is the only network mode supported by Consul on ECS. | string | | `volumes` | Volumes on the host for sharing configuration between containers for initial task setup. You must define a `consul_data` and `consul_binary` bind mount. Bind mounts can be mounted into one or more containers in order to share files among containers. For Consul on ECS, certain binaries and configuration are shared among containers during task startup. | list | -| `containerDefinitions` | Defines the application container that runs in the task. Refer to [Define your application container](#define-your-application-container). | list | +| `containerDefinitions` | Defines the application container that runs in the task. Refer to [Define your application container](#define-your-application-container). | list | The following example shows the top-level fields: @@ -81,7 +81,7 @@ The following example shows the top-level fields: The `tags` list must include the following tags if you are using the ECS controller in a [secure configuration](/consul/docs/ecs/deploy/manual#secure-configuration-requirements). Without these tags, the ACL controller is unable to provision a service token for the task. -| Tag | Description | Type | Default | +| Tag | Description | Type | Default | | --- | --- | --- | --- | | `consul.hashicorp.com/mesh` | Enables the ECS controller. Set to `false` to disable the ECS controller. | String | `true` | | `consul.hashicorp.com/service-name` | Specifies the name of the Consul service associated with this task. Required if the service name is different than the task `family`. | String | None | @@ -96,7 +96,7 @@ Specify your application container configurations in the `containerDefinitions` | --- | --- | --- | | `name` | The name of your application container. | string | | `image` | The container image used to run your application. | string | -| `essential` | Must be `true` to ensure the health of your application container affects the health status of the task. | boolean | +| `essential` | Must be `true` to ensure the health of your application container affects the health status of the task. | boolean | | `dependsOn` | Specifies container dependencies that ensure your application container starts after service mesh setup is complete. Refer to [Application container dependency configuration](#application-container-dependency-configuration) for details. | list | Refer to the [ECS Task Definition](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html) documentation for a complete reference. @@ -120,7 +120,7 @@ The `dependsOn` list must include the following maps: } ``` -## Configure the dataplane container +## Configure the dataplane container The dataplane container runs Envoy proxy for Consul service mesh. Specify the fields described in the following table to declare a dataplane container: @@ -174,7 +174,7 @@ Specify the fields described in the following table to declare the control-plane | --- | --- | --- | | `name` | Specifies the name of the container. This must be `control-plane`. | string | | `image` | Specifies the `consul-ecs` image. Specify the following public AWS registry to avoid rate limits: `public.ecr.aws/hashicorp/consul-ecs` | string | -| `mountPoints` | Specifies a shared volume to store the Envoy bootstrap configuration file that the dataplane container can access and consume. The keys and values in this configuration must be defined as described in [Control-plane shared volume configuration](#control-plane-shared-volume-configuration). | list | +| `mountPoints` | Specifies a shared volume to store the Envoy bootstrap configuration file that the dataplane container can access and consume. The keys and values in this configuration must be defined as described in [Control-plane shared volume configuration](#control-plane-shared-volume-configuration). | list | | `command` | Set to `["control-plane"]` so that the container runs the `control-plane` command. | list | | `environment` | Specifies the `CONSUL_ECS_CONFIG_JSON` environment variable, which configures the container to connect to the Consul servers. Refer to [Control-plane to Consul servers configuration](#control-plane-to-Consul-servers-configuration) for details. | list | @@ -199,7 +199,7 @@ The `mountPoints` configuration defines a volume and path where the control-plan ### Control-plane to Consul servers configuration -Provide Consul server connection settings to the mesh task module so that the module can configure the control-plane and ECS controller containers to connect to the servers. +Provide Consul server connection settings to the mesh task module so that the module can configure the control-plane and ECS controller containers to connect to the servers. 1. In your `variables.tf` file, define variables for the host URL and TLS settings for gRPC and HTTP traffic. Refer to the [mesh task module reference](https://registry.terraform.io/modules/hashicorp/consul-ecs/aws/latest/submodules/mesh-task?tab=inputs) for information about the variables you can define. In the following example, the Consul server address is defined in the `consul_server_hosts` variable: @@ -210,7 +210,7 @@ Provide Consul server connection settings to the mesh task module so that the mo } ``` -1. Add an `environment` block to the control-plane and ECS controller containers definition +1. Add an `environment` block to the control-plane and ECS controller containers definition. 1. Set the `environment.name` field to the `CONSUL_ECS_CONFIG_JSON` environment variable and the value to `local.encoded_config`. ```hcl @@ -223,7 +223,7 @@ Provide Consul server connection settings to the mesh task module so that the mo ``` When you apply the configuration, the mesh task module interpolates the server configuration variables, builds a `config.tf` file, and injects the settings into the appropriate containers. -For additional information about the `config.tf` file, refer to the [JSON schema reference documentation](/consul/docs/ecs/reference/config-json-schema). +For additional information about the `config.tf` file, refer to the [JSON schema reference documentation](/consul/docs/ecs/reference/config-json-schema). ## Register the task definition configuration @@ -245,8 +245,8 @@ Verify that you have completed the prerequisites described in [Secure configurat On the Consul server, create a policy that grants the following access for the controller: -- `acl:write` -- `operator:write` +- `acl:write` +- `operator:write` - `node:write` - `service:write` @@ -260,7 +260,7 @@ The policy allows Consul to generate a token linked to the policy. Refer to [Cre ### Configure the auth method for service tokens -Run the `consul acl auth-method create` command on a Consul server to create an instance of the auth method for service tokens. +Run the `consul acl auth-method create` command on a Consul server to create an instance of the auth method for service tokens. The following example command configures the auth method to associate a service identity to each token created during login to this auth method instance. @@ -303,9 +303,9 @@ You must specify the following configuration in the `-config` flag: Refer to the [auth method configuration parameters documentation](/consul/docs/security/acl/auth-methods/aws-iam#config-parameters) for additional information. -### Create the binding rule +### Create the binding rule -Run the `consul acl binding-rule create` command on a Consul server to create a binding rule. The rule associates a service identity with each token created on successful login to this instance of the auth method. +Run the `consul acl binding-rule create` command on a Consul server to create a binding rule. The rule associates a service identity with each token created on successful login to this instance of the auth method. In the following example, Consul takes the service identity name from the `consul.hashicorp.com.service-name` tag specified for authenticating IAM role identity. @@ -336,7 +336,7 @@ You can reference stored secrets using their ARN. The examples show ARNs for sec You can configure Consul servers connected to your ECS workloads to capture a log of authenticated events. Refer to [Audit Logging](/consul/docs/enterprise/audit-logging) for details. ## Next steps - + After deploying the Consul service mesh infrastructure, you must still define routes between service instances as well as configure the bind address for your applications so that they only receive traffic through the mesh. Refer to the following topics: - [Configure routes between ECS tasks](/consul/docs/ecs/deploy/configure-routes) diff --git a/website/content/docs/ecs/deploy/migrate-existing-tasks.mdx b/website/content/docs/ecs/deploy/migrate-existing-tasks.mdx index 77ab4ded7f..cb405e518a 100644 --- a/website/content/docs/ecs/deploy/migrate-existing-tasks.mdx +++ b/website/content/docs/ecs/deploy/migrate-existing-tasks.mdx @@ -5,13 +5,13 @@ description: >- You can migrate tasks in existing Amazon Web Services ECS deployments to a service mesh deployed with Terraform. Learn how to convert a task specified as an ECS task definition into a `mesh-task` Terraform module. --- -# Migrate existing tasks to Consul on ECS with Terraform +# Migrate existing tasks to Consul on ECS with Terraform -To migrate existing tasks to Consul, rewrite the existing Terraform code for your tasks so that the container definitions include the [`mesh-task` Terraform module](https://registry.terraform.io/modules/hashicorp/consul-ecs/aws/latest/submodules/mesh-task). +To migrate existing tasks to Consul, rewrite the existing Terraform code for your tasks so that the container definitions include the [`mesh-task` Terraform module](https://registry.terraform.io/modules/hashicorp/consul-ecs/aws/latest/submodules/mesh-task). Your tasks must already be defined in Terraform using the `ecs_task_definition` resource so that they can then be converted to use the `mesh-task` module. -## Example +## Example The following example shows an existing task definition configured in Terraform: @@ -93,7 +93,7 @@ module "my_task" { Note the following differences: - The `execution_role_arn` and `task_role_arn` fields are removed. The `mesh-task` module creates the task and execution roles by default. If you need to use existing IAM roles, set the `task_role` and `execution_role` fields to pass in existing roles. -- The `port` field specifes the port that your application listens on. If your application has no listening port, set `outbound_only = true` and remove the `port` field. +- The `port` field specifies the port that your application listens on. If your application has no listening port, set `outbound_only = true` and remove the `port` field. - The `jsonencode()` function is removed from the `container_definitions` field. -The `mesh-task` module creates a new version of your task definition with the necessary dataplane containers so you can delete your existing `aws_ecs_task_definition` resource. \ No newline at end of file +The `mesh-task` module creates a new version of your task definition with the necessary dataplane containers so you can delete your existing `aws_ecs_task_definition` resource. diff --git a/website/content/docs/ecs/deploy/terraform.mdx b/website/content/docs/ecs/deploy/terraform.mdx index c091ff4596..5d5574a967 100644 --- a/website/content/docs/ecs/deploy/terraform.mdx +++ b/website/content/docs/ecs/deploy/terraform.mdx @@ -11,17 +11,17 @@ This topic describes how to create a Terraform configuration that deploys Consul ## Overview -Create a Terraform configuration file that includes the ECS task definition and Terraform modules that build the Consul service mesh components. The task definition is the ECS blueprint for your software services on AWS. Refer to the [ECS task definitions documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definitions.html) for additional information. +Create a Terraform configuration file that includes the ECS task definition and Terraform modules that build the Consul service mesh components. The task definition is the ECS blueprint for your software services on AWS. Refer to the [ECS task definitions documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definitions.html) for additional information. You can add the following modules and resources to your Terraform configuration: -- `mesh-task` module: Adds the Consul ECS control-plane and Consul dataplane containers to the task definition along with your application container. Envoy runs as a subprocess within the Consul dataplane container. +- `mesh-task` module: Adds the Consul ECS control-plane and Consul dataplane containers to the task definition along with your application container. Envoy runs as a subprocess within the Consul dataplane container. - `aws_ecs_service` resource: Adds an ECS service to run and maintain your task instance. - `gateway-task` module: Adds mesh gateway containers to the cluster. Mesh gateways enable service-to-service communication across different types of network areas. -To enable Consul security features for your production workloads, you must also deploy the `controller` module, which provisions ACL tokens for service mesh tasks. +To enable Consul security features for your production workloads, you must also deploy the `controller` module, which provisions ACL tokens for service mesh tasks. -After defining your Terraform configuration, use `terraform apply` to deploy Consul to your ECS cluster. +After defining your Terraform configuration, use `terraform apply` to deploy Consul to your ECS cluster. ## Requirements @@ -34,10 +34,10 @@ After defining your Terraform configuration, use `terraform apply` to deploy Con You must meet the following requirements and prerequisites to enable security features in Consul service mesh: - Enable [TLS encryption](/consul/docs/security/encryption#rpc-encryption-with-tls) on your Consul servers so that they can communicate securely with Consul containers over gRPC. -- Enable [access control lists (ACLs)](/consul/docs/security/acl) on your Consul servers. ALCs provide authentication and authorization for access to Consul servers on the mesh. +- Enable [access control lists (ACLs)](/consul/docs/security/acl) on your Consul servers. ACLs provide authentication and authorization for access to Consul servers on the mesh. - You should be familiar with specifying sensitive data on ECS. Refer to [Passing sensitive data to a container](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/specifying-sensitive-data.html) in the AWS documentation for additional information. -Additionally, Consul requires a unique IAM role for each ECS task family. Task IAM roles cannot be shared by different task families because the task family is unique to each Consul service. +Additionally, Consul requires a unique IAM role for each ECS task family. Task IAM roles cannot be shared by different task families because the task family is unique to each Consul service. You should be familiar with configuring Consul's secure features, including how to create ACL tokens and policies. Refer to the following resources for additional information: @@ -53,7 +53,7 @@ Create a Terraform configuration file and add your ECS task definition. The task Add a `module` block to your Terraform configuration and specify the following fields: - `source`: Specifies the location of the `mesh-task` module. This field must be set to `hashicorp/consul-ecs/aws//modules/mesh-task`. The `mesh-task` module automatically adds the Consul service mesh infrastructure when you apply the Terraform configuration. -- `version`: Specifies the version of the `mesh-task` module to use. +- `version`: Specifies the version of the `mesh-task` module to use. - `family`: Specifies the [ECS task definition family](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#family). Consul also uses the `family` value as the Consul service name by default. - `container_definitions`: Specifies a list of [container definitions](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#container_definitions) for the task definition. This field is where you include your application containers. @@ -99,7 +99,7 @@ The following fields are required. Refer to the [module reference documentation] ## Configure Consul server settings -Provide Consul server connection settings to the mesh task module so that the module can configure the control-plane and ECS controller containers to connect to the servers. +Provide Consul server connection settings to the mesh task module so that the module can configure the control-plane and ECS controller containers to connect to the servers. 1. In your `variables.tf` file, define variables for the host URL and the TLS settings for gRPC and HTTP traffic. Refer to the [mesh task module reference](https://registry.terraform.io/modules/hashicorp/consul-ecs/aws/latest/submodules/gateway-task?tab=inputs) for information about the variables you can define. In the following example, the Consul server address is defined in the `consul_server_hosts` variable: @@ -121,13 +121,13 @@ Provide Consul server connection settings to the mesh task module so that the mo ] ``` - When you apply the configuration, the mesh task module interpolates the server configuration variables, builds a `config.tf` file, and injects the settings into the appropriate containers. For additional information about the `config.tf` file, refer to the [JSON schema reference documentation](/consul/docs/ecs/reference/consul-server-json). + When you apply the configuration, the mesh task module interpolates the server configuration variables, builds a `config.tf` file, and injects the settings into the appropriate containers. For additional information about the `config.tf` file, refer to the [JSON schema reference documentation](/consul/docs/ecs/reference/consul-server-json). ## Configure an ECS service to run your task instances To start a task using the task definition, add the `aws_ecs_service` resource to your configuration to create an ECS service. [ECS services](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/ecs_services.html) are one of the most common ways to start tasks using a task definition. -Reference the `mesh-task` module's `task_definition_arn` output value in your `aws_ecs_service` resource. The following example adds an ECS service for a task definition referenced in as `module.my_task.task_defintion_arn`: +Reference the `mesh-task` module's `task_definition_arn` output value in your `aws_ecs_service` resource. The following example adds an ECS service for a task definition referenced in as `module.my_task.task_definition_arn`: @@ -154,7 +154,7 @@ If you are deploying a test instance of your ECS application, you can apply your If you intend to leverage multi-datacenter Consul features, such as WAN federation and cluster peering, then you must add the `gateway-task` module for each Consul datacenter in your network. Refer to [Configure the gateway task module](#configure-the-gateway-task-module) for instructions. -## Configure the gateway task module +## Configure the gateway task module The `gateway-task` module deploys a mesh gateway, which enables service-to-service communication across network areas. Mesh gateways detect the server name indication (SNI) header from the service mesh session and route the connection to the appropriate destination. @@ -163,12 +163,12 @@ Refer to the following documentation for additional information: - [WAN Federation via Mesh Gateways](/consul/docs/connect/gateways/mesh-gateway/wan-federation-via-mesh-gateways) - [Service-to-service Traffic Across Datacenters](/consul/docs/connect/gateways/mesh-gateway/service-to-service-traffic-wan-datacenters) -To use mesh gateways, TLS must be enabled in your cluster. Refer to the [requirements section](#requirements) for additional information. +To use mesh gateways, TLS must be enabled in your cluster. Refer to the [requirements section](#requirements) for additional information. 1. Add a `module` block to your Terraform configuration file and specify a label. The label is a unique identifier for the gateway. 1. Add a `source` to the `module` and specify the location of the `gateway-task`. The value must be `hashicorp/consul-ecs/aws//modules/gateway-task`. 1. Specify the following required inputs: - - `ecs_cluster_arn`: The ARN of the ECS cluster for the gateway. + - `ecs_cluster_arn`: The ARN of the ECS cluster for the gateway. - `family`: Specifies a name for multiple versions of the task. Refer to the [AWS documentation](https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#family) for details. - `kind`: Set to `mesh-gateway` - `subnets`: Specifies a list of subnet IDs where the gateway task should be deployed. @@ -200,9 +200,9 @@ Refer to [gateway-task module in the Terraform registry](https://registry.terraf Refer to the [gateway task configuration examples](#gateway-task-configuration-examples) for additional example configurations. -## Configure the ECS controller +## Configure the ECS controller -Deploy the ECS controller container to its own ECS task in the cluster. Refer to [ECS controller container](/consul/docs/ecs/reference/architecture#ecs-controller) for details about the container. +Deploy the ECS controller container to its own ECS task in the cluster. Refer to [ECS controller container](/consul/docs/ecs/reference/architecture#ecs-controller) for details about the container. Verify that you have completed the prerequisites described in [Secure configuration requirements](#secure-configuration-requirements) and complete the following steps to configure the controller container. @@ -211,16 +211,16 @@ Verify that you have completed the prerequisites described in [Secure configurat 1. On the Consul server, create a policy that grants the following access for the controller: - `acl:write` - - `operator:write` + - `operator:write` - `node:write` - `service:write` - + The policy allows Consul to generate a token linked to the policy. Refer to [Create a service token](/consul/docs/security/acl/tokens/create/create-a-service-token) for instructions. 1. Create a token and link it to the ACL controller policy. Refer to the [ACL tokens documentation](/consul/docs/security/acl/tokens) for instructions. ### Configure an AWS secrets manager secret -Add the `aws_secretsmanager_secret` resource to your Terraform configuration and specify values for retrieving the CA and TLS certificates. The resource enables services to communicate over TLS and present ACL tokens. The ECS controller also uses the secret manager to retrieve the value of the bootstrap token. +Add the `aws_secretsmanager_secret` resource to your Terraform configuration and specify values for retrieving the CA and TLS certificates. The resource enables services to communicate over TLS and present ACL tokens. The ECS controller also uses the secret manager to retrieve the value of the bootstrap token. In the following example, Terraform creates the CA certificates for gRPC and HTTPS in the secrets manager. Consul retrieves the CA certificate PEM file from the secret manager so that the mesh task can use TLS for HTTP and gRPC traffic: @@ -375,7 +375,7 @@ Terraform reads all files in the current directory that have a `.tf` file extens Refer to the [Terraform documentation](/terraform/docs) for more information and Terraform best practices. ## Next steps - + After deploying the Consul service mesh infrastructure, you must still define routes between service instances as well as configure the bind address for your applications so that they only receive traffic through the mesh. Refer to the following topics: - [Configure routes between ECS tasks](/consul/docs/ecs/deploy/configure-routes) diff --git a/website/content/docs/ecs/index.mdx b/website/content/docs/ecs/index.mdx index 7238765af2..992d7eb3df 100644 --- a/website/content/docs/ecs/index.mdx +++ b/website/content/docs/ecs/index.mdx @@ -30,7 +30,7 @@ Refer to the following documentation and tutorials for additional guidance. ### Tutorials -- [Integrate your AWS ECS services into Consul service mesh](/consul/tutorials/cloud-integrations/consul-ecs): Shows how to use Terraform to run Consul service mesh applications on ECS with self-managed Consul or HCP-managed Consul. +- [Integrate your AWS ECS services into Consul service mesh](/consul/tutorials/cloud-integrations/consul-ecs): Shows how to use Terraform to run Consul service mesh applications on ECS with self-managed Enterprise or HCP Consul Dedicated. You can also refer to the following example configurations: diff --git a/website/content/docs/ecs/reference/consul-server-json.mdx b/website/content/docs/ecs/reference/consul-server-json.mdx index 30e41c207f..87401bbb2d 100644 --- a/website/content/docs/ecs/reference/consul-server-json.mdx +++ b/website/content/docs/ecs/reference/consul-server-json.mdx @@ -6,25 +6,25 @@ description: Learn about the fields available in the JSON scheme for configuring # Consul server configuration JSON schema reference -This topic provides reference information about the JSON schema used to build the `config.tf` file. Refer to [Configure Consul server settings](/consul/docs/ecs/deploy/terraform#configure-consul-server-settings) for information about how Consul on ECS uses the JSON schema. +This topic provides reference information about the JSON schema used to build the `config.tf` file. Refer to [Configure Consul server settings](/consul/docs/ecs/deploy/terraform#configure-consul-server-settings) for information about how Consul on ECS uses the JSON schema. ## Configuration model The following list describes the attributes, data types, and default values, if any, in the `config.tf` file. Click on a value to learn more about the attribute. -- [`consulServers`](#consulservers): map +- [`consulServers`](#consulservers): map - [`hosts`](#consulservers-hosts): string - [`skipServerWatch`](#consulservers-hosts): boolean | `false` - - [`defaults`](#consulservers-defaults): map - - [`caCertFile`](#consulservers-defaults): string - - [`tlsServerName`](#consulservers-defaults): string + - [`defaults`](#consulservers-defaults): map + - [`caCertFile`](#consulservers-defaults): string + - [`tlsServerName`](#consulservers-defaults): string - [`tls`](#consulservers-defaults): boolean | `false` - - [`grpc`](#consulservers-grpc): map - - [`port`](#consulservers-grpc): number - - [`caCertFile`](#consulservers-grpc): string - - [`tlsServerName`](#consulservers-grpc): string + - [`grpc`](#consulservers-grpc): map + - [`port`](#consulservers-grpc): number + - [`caCertFile`](#consulservers-grpc): string + - [`tlsServerName`](#consulservers-grpc): string - [`tls`](#consulservers-grpc): boolean | `false` - - [`http`](#consulservers-http): map + - [`http`](#consulservers-http): map - [`https`](#consulservers-http): boolean | `false` - [`port`](#consulservers-http): number - [`caCertFile`](#consulservers-http): string @@ -33,11 +33,11 @@ The following list describes the attributes, data types, and default values, if ## Specification -This section provides details about the attribes in the `config.tf` file. +This section provides details about the attributes in the `config.tf` file. ### `consulServers` -Parent-level attribute containing all of the server configurations. All other configuraitons in the file are children of the `consulServers` attribute. +Parent-level attribute containing all of the server configurations. All other configurations in the file are children of the `consulServers` attribute. #### Values @@ -47,7 +47,7 @@ Parent-level attribute containing all of the server configurations. All other co ### `consulServers.hosts` -Map that contains the `skipServerWatch` configuration for Consul server hosts. +Map that contains the `skipServerWatch` configuration for Consul server hosts. #### Values @@ -56,7 +56,7 @@ Map that contains the `skipServerWatch` configuration for Consul server hosts. ### `consulServers.hosts.skipServerWatch` -Boolean that disables watches on the Consul server. Set to `true` if the Consul server is already behind a load balancer. +Boolean that disables watches on the Consul server. Set to `true` if the Consul server is already behind a load balancer. #### Values @@ -65,7 +65,7 @@ Boolean that disables watches on the Consul server. Set to `true` if the Consul ### `consulServers.defaults` -Map of default server configurations. Defaults apply to gRPC and HTTP traffic. +Map of default server configurations. Defaults apply to gRPC and HTTP traffic. #### Values @@ -83,7 +83,7 @@ The following table describes the attributes available in the `defaults` configu ### `consulServers.grpc` -Map of server configuration for gRPC traffic that override attributes defined in `consulServers.defaults`. +Map of server configuration for gRPC traffic that override attributes defined in `consulServers.defaults`. #### Values @@ -101,7 +101,7 @@ The following table describes the attributes available in the `grpc` configurati ### `consulServers.http` -Map of server configuration for HTTP traffic that override attributes defined in `consulServers.defaults`. +Map of server configuration for HTTP traffic that override attributes defined in `consulServers.defaults`. #### Values diff --git a/website/content/docs/ecs/tech-specs.mdx b/website/content/docs/ecs/tech-specs.mdx index b9ff7af349..d5fa5b399f 100644 --- a/website/content/docs/ecs/tech-specs.mdx +++ b/website/content/docs/ecs/tech-specs.mdx @@ -18,7 +18,7 @@ Consul on ECS supports the following environments, runtimes, and capabilities: - **Launch Types:** Fargate and EC2 - **Network Modes:** `awsvpc` - **Subnets:** Private and public subnets. Tasks must have network access to Amazon ECR or other public container registries to pull images. -- **Consul servers:** You can use your own Consul servers running on virtual machines or [use HCP Consul to host the servers for you](/hcp/docs/consul/hcp-managed). +- **Consul servers:** You can use your own Consul servers running on virtual machines or [use HCP Consul Dedicated to host the servers for you](/hcp/docs/consul/dedicated). - **ECS controller:** The ECS controller assists with reconciling state back to Consul and facilitates Consul security features. - **Admin partitions:** Enable ACLs and configure the ECS controller to use admin partitions. You must deploy one controller for each admin partition. - **Namespaces:** Enable ACLs and configure the ECS controller to use namespaces. diff --git a/website/content/docs/enterprise/fips.mdx b/website/content/docs/enterprise/fips.mdx index 0aca1ad207..dc4d2ed3bc 100644 --- a/website/content/docs/enterprise/fips.mdx +++ b/website/content/docs/enterprise/fips.mdx @@ -2,7 +2,7 @@ layout: docs page_title: FIPS 140-2 description: >- - A version of Consul compliant with FIPS 140-2 is available to Enterprise users. Learn about where to find FIPS-compliant versions of Consul, as well as usage restrictions and technical details. + A version of Consul compliant with FIPS 140-2 is available to Enterprise users. Learn about where to find FIPS-compliant versions of Consul, its usage restrictions, technical details, and Leidos validation. --- # FIPS 140-2 @@ -17,9 +17,9 @@ To use this feature, you must have an [active or trial license for Consul Enterp ## Using FIPS 140-2 Consul Enterprise -FIPS 140-2 builds of Consul Enterprise behave in the same way as non-FIPS builds. There are no restrictions on Consul algorithms and ensuring that Consul remains in a FIPS-compliant mode of operation is your responsibility. To maintain FIPS-compliant operation, you must [ensure that TLS is enabled](/consul/tutorials/security/tls-encryption-secure) so that communication is encrypted. Consul products surface some helpful warnings where settings are insecure. +FIPS 140-2 builds of Consul Enterprise behave in the same way as non-FIPS builds. There are no restrictions on Consul algorithms and ensuring that Consul remains in a FIPS-compliant mode of operation is your responsibility. To maintain FIPS-compliant operation, you must [ensure that TLS is enabled](/consul/tutorials/archive/tls-encryption-secure) so that communication is encrypted. Consul products surface some helpful warnings where settings are insecure. -Encryption is disabled in Consul Enterprise by default. As a result, Consul may transmit sensitive control plane information. You must ensure that gossip encryption and mTLS is enabled for all agents when running Consul with FIPS-compliant settings. In addition, be aware that TLSv1.3 does not work with FIPS 140-2, as HKDF is not a certified primitive. +Encryption is disabled in Consul Enterprise by default. As a result, Consul may transmit sensitive control plane information. You must ensure that gossip encryption and mTLS is enabled for all agents when running Consul with FIPS-compliant settings. In addition, be aware that TLS v1.3 does not work with FIPS 140-2, as HKDF is not a certified primitive. HashiCorp is not a NIST-certified testing laboratory and can only provide general guidance about using Consul Enterprise in a FIPS-compliant manner. We recommend consulting an approved auditor for further information. @@ -45,6 +45,7 @@ When using Consul Enterprise with FIPS 140-2, be aware of the following operatio We do not support in-place migrations from non-FIPS builds of Consul to FIPS builds of Consul, regardless of version. A fresh cluster installation is required to support FIPS 140-2. You cannot upgrade directly to a FIPS-compliant build. #### TLS restrictions + Consul Enterprise's FIPS modifications include restrictions to supported TLS cipher suites and key information. Only the following cipher suites are allowed: - `TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256` @@ -125,7 +126,21 @@ Similarly, on a FIPS Windows binary, run `go tool nm` on the binary to get a sym On both Linux and Windows non-FIPS builds, the search output yields no results. -### Compliance validation +## Leidos validation -A Lab, authorized by the U.S. Government to certify FIPS 140-2 compliance, is in the process of verifying that Consul Enterprise and its related packages are compliant with the requirements of FIPS 140-2 Level 1. +In 2024, Leidos certified the integration of FIPS 140-2 cryptographic module [BoringCrypto Cert. #4407](https://csrc.nist.gov/Projects/Cryptographic-Module-Validation-Program/Certificate/4407) for the following Consul releases: +- Consul Enterprise builds: + - [`consul_1.16.0+ent.fips1402`](https://releases.hashicorp.com/consul/1.16.0+ent.fips1402/) + - [`consul_1.16.1+ent.fips1402`](https://releases.hashicorp.com/consul/1.16.1+ent.fips1402/) +- Consul Dataplane builds: + - [`consul-dataplane_1.2.0+fips1402`](https://releases.hashicorp.com/consul-dataplane/1.2.0+fips1402/) + - [`consul-dataplane_1.2.1+fips1402`](https://releases.hashicorp.com/consul-dataplane/1.2.1+fips1402/) +- Consul K8s builds: + - [`consul-k8s_1.2.0+fips1402`](https://releases.hashicorp.com/consul-k8s/1.2.0+fips1402/) + - [`consul-k8s_1.2.1+fips1402`](https://releases.hashicorp.com/consul-k8s/1.2.1+fips1402/) +- Consul K8s Control Plane builds: + - [`consul-k8s-control-plane_1.2.0+fips1402`](https://releases.hashicorp.com/consul-k8s-control-plane/1.2.0+fips1402/) + - [`consul-k8s-control-plane_1.2.1+fips1402`](https://releases.hashicorp.com/consul-k8s-control-plane/1.2.2+fips1402/) + +For more information about verified platform architectures and confirmed feature support, [review the Leidos certification letter](https://www.datocms-assets.com/2885/1715791547-boringcrypto_compliance_letter_signed.pdf). \ No newline at end of file diff --git a/website/content/docs/enterprise/index.mdx b/website/content/docs/enterprise/index.mdx index 6427926b17..b77df69692 100644 --- a/website/content/docs/enterprise/index.mdx +++ b/website/content/docs/enterprise/index.mdx @@ -65,17 +65,17 @@ The following features are [available in several forms of Consul Enterprise](#co ## Access Consul Enterprise The method of accessing Consul Enterprise and its features depends on the whether using -HashiCorp Cloud Platform or self-managed Consul. +HashiCorp Cloud Platform or self-managed Consul Enterprise. -### HCP Consul +### HCP Consul Dedicated No action is required to access Consul Enterprise in a [HashiCorp Cloud Platform](https://cloud.hashicorp.com/products/consul) installation. -You can try out HCP Consul for free. Refer to the -[HCP Consul product page](https://cloud.hashicorp.com/products/consul) for more details. +You can try out HCP Consul Dedicated for free. Refer to the +[HCP Consul Dedicated product page](https://cloud.hashicorp.com/products/consul) for more details. -### Self-Managed Consul +### Self-managed Consul Enterprise To access Consul Enterprise in a self-managed installation, [apply a purchased license](/consul/docs/enterprise/license/overview) diff --git a/website/content/docs/enterprise/license/faq.mdx b/website/content/docs/enterprise/license/faq.mdx index d8a46ee209..4278f7ec5e 100644 --- a/website/content/docs/enterprise/license/faq.mdx +++ b/website/content/docs/enterprise/license/faq.mdx @@ -120,9 +120,9 @@ Contact your organization's [HashiCorp account team](https://support.hashicorp.c The license files are not locked to a specific cluster or cluster node. The above changes apply to all nodes in a cluster. -## Q: Will this impact HCP Consul? +## Q: Will this impact HCP Consul Dedicated? -This will not impact HCP Consul. +This will not impact HCP Consul Dedicated. ## Q: Does this need to happen every time a node restarts, or is this a one-time check? diff --git a/website/content/docs/install/glossary.mdx b/website/content/docs/install/glossary.mdx index f11ae21e7b..c8bee74d6d 100644 --- a/website/content/docs/install/glossary.mdx +++ b/website/content/docs/install/glossary.mdx @@ -51,7 +51,7 @@ and our implementation is described [here](/consul/docs/architecture/consensus). ## Gossip -Consul is built on top of [Serf](https://www.serf.io/) which provides a full +Consul is built on top of [Serf](https://github.com/hashicorp/serf/) which provides a full [gossip protocol](https://en.wikipedia.org/wiki/Gossip_protocol) that is used for multiple purposes. Serf provides membership, failure detection, and event broadcast. Our use of these is described more in the [gossip documentation](/consul/docs/architecture/gossip). It is enough to know @@ -78,171 +78,171 @@ This section collects brief definitions of some of the terms used in the discuss ## Access Control List (ACL) -An Access Control List (ACL) is a list of user permissions for a file, folder, or -other object. It defines what users and groups can access the object and what +An Access Control List (ACL) is a list of user permissions for a file, folder, or +other object. It defines what users and groups can access the object and what operations they can perform. Consul uses Access Control Lists (ACLs) to secure the UI, API, CLI, service communications, and agent communications. Visit [Consul ACL Documentation and Guides](/consul/docs/security/acl) -## API Gateway -An Application Programming Interface (API) is a common software interface that -allows two applications to communicate. Most modern applications are built using -APIs. An API Gateway is a single point of entry into these modern applications +## API Gateway +An Application Programming Interface (API) is a common software interface that +allows two applications to communicate. Most modern applications are built using +APIs. An API Gateway is a single point of entry into these modern applications built using APIs. -## Application Security -Application Security is the process of making applications secure by detecting -and fixing any threats or information leaks. This can be done during or after -the app development lifecycle; although, it is easier for app teams and security -teams to incorporate security into an app even before the development process +## Application Security +Application Security is the process of making applications secure by detecting +and fixing any threats or information leaks. This can be done during or after +the app development lifecycle; although, it is easier for app teams and security +teams to incorporate security into an app even before the development process begins. -## Application Services -Application Services are a group of services, such as application performance -monitoring, load balancing, service discovery, service proxy, security, -autoscaling, etc. needed to deploy, run, and improve applications. +## Application Services +Application Services are a group of services, such as application performance +monitoring, load balancing, service discovery, service proxy, security, +autoscaling, etc. needed to deploy, run, and improve applications. -## Authentication and Authorization (AuthN and AuthZ) +## Authentication and Authorization (AuthN and AuthZ) Authentication (AuthN) deals with establishing user identity while Authorization (AuthZ) allows or denies access to the user based on user identity. -## Auto Scaling Groups -An Auto Scaling Group is an AWS specific term that represents a collection of -Amazon EC2 instances that are treated as a logical grouping for the purposes of -automatic scaling and management. -Learn more about Auto Scaling Groups +## Auto Scaling Groups +An Auto Scaling Group is an AWS specific term that represents a collection of +Amazon EC2 instances that are treated as a logical grouping for the purposes of +automatic scaling and management. +Learn more about Auto Scaling Groups [here](https://docs.aws.amazon.com/autoscaling/ec2/userguide/AutoScalingGroup.html). -## Autoscaling -Autoscaling is the process of automatically scaling computational resources based -on network traffic requirements. Autoscaling can be done either horizontally or -vertically. Horizontal scaling is done by adding more machines into the pool of -resources whereas vertical scaling means increasing the capacity of an existing +## Autoscaling +Autoscaling is the process of automatically scaling computational resources based +on network traffic requirements. Autoscaling can be done either horizontally or +vertically. Horizontal scaling is done by adding more machines into the pool of +resources whereas vertical scaling means increasing the capacity of an existing machine. -## Blue-Green Deployments -Blue-Green Deployment is a deployment method designed to reduce downtime by -running two identical production environments labeled Blue and Green. Blue is -the active while Green is the idle environment. +## Blue-Green Deployments +Blue-Green Deployment is a deployment method designed to reduce downtime by +running two identical production environments labeled Blue and Green. Blue is +the active while Green is the idle environment. -## Canary Deployments -Canary deployment is the pattern used for rolling out releases to a subset of -users or servers. The goal is deploy the updates to a subset of users, test it, -and then roll out the changes to everyone. +## Canary Deployments +Canary deployment is the pattern used for rolling out releases to a subset of +users or servers. The goal is deploy the updates to a subset of users, test it, +and then roll out the changes to everyone. -## Client-side Load Balancing -Client-side load balancing is a load balancing approach that relies on clients' -decision to call the right servers. As the name indicates, this approach is part -of the client application. Servers can still have their own load balancer +## Client-side Load Balancing +Client-side load balancing is a load balancing approach that relies on clients' +decision to call the right servers. As the name indicates, this approach is part +of the client application. Servers can still have their own load balancer alongside the client-side load balancer. -## Cloud Native Computing Foundation -The [Cloud Native Computing Foundation (CNCF)](https://github.com/cncf/foundation) -is a Linux Foundation project that was founded in 2015 to help advance +## Cloud Native Computing Foundation +The [Cloud Native Computing Foundation (CNCF)](https://github.com/cncf/foundation) +is a Linux Foundation project that was founded in 2015 to help advance container technology and align the tech industry around its evolution. -HashiCorp joined Cloud Native Computing Foundation to further HashiCorp -product integrations with CNCF projects and to work more closely with the -broader cloud-native community of cloud engineers. Read more +HashiCorp joined Cloud Native Computing Foundation to further HashiCorp +product integrations with CNCF projects and to work more closely with the +broader cloud-native community of cloud engineers. Read more [here](https://www.hashicorp.com/blog/hashicorp-joins-the-cncf/). -## Custom Resource Definition (CRD) -Custom resources are the extensions of the Kubernetes API. A Custom Resource -Definition (CRD) file allows users to define their own custom resources and +## Custom Resource Definition (CRD) +Custom resources are the extensions of the Kubernetes API. A Custom Resource +Definition (CRD) file allows users to define their own custom resources and allows the API server to handle the lifecycle. -## Egress Traffic -Egress traffic is network traffic that begins inside a network and proceeds +## Egress Traffic +Egress traffic is network traffic that begins inside a network and proceeds through its routers to a destination outside the network. -## Elastic Provisioning -Elastic Provisioning is the ability to provision computing resources +## Elastic Provisioning +Elastic Provisioning is the ability to provision computing resources dynamically to meet user demand. -## Envoy Proxy -[Envoy Proxy](https://www.envoyproxy.io/) is a modern, high performance, -small footprint edge and service proxy. Originally written and deployed at +## Envoy Proxy +[Envoy Proxy](https://www.envoyproxy.io/) is a modern, high performance, +small footprint edge and service proxy. Originally written and deployed at [Lyft](https://eng.lyft.com/announcing-envoy-c-l7-proxy-and-communication-bus-92520b6c8191), - Envoy Proxy is now an official project at [Cloud Native Computing Foundation - (CNCF)](https://www.cncf.io/cncf-envoy-project-journey/) + Envoy Proxy is now an official project at [Cloud Native Computing Foundation + (CNCF)](https://www.cncf.io/cncf-envoy-project-journey/) -## Forward Proxy -A forward proxy is used to forward outgoing requests from inside the network -to the Internet, usually through a firewall. The objective is to provide a level +## Forward Proxy +A forward proxy is used to forward outgoing requests from inside the network +to the Internet, usually through a firewall. The objective is to provide a level of security and to reduce network traffic. -## Hybrid Cloud Architecture -A hybrid cloud architecture is an IT architectural approach that mixes -on-premises, private cloud, and public cloud services. A hybrid cloud -environment incorporates workload portability, orchestration, and management +## Hybrid Cloud Architecture +A hybrid cloud architecture is an IT architectural approach that mixes +on-premises, private cloud, and public cloud services. A hybrid cloud +environment incorporates workload portability, orchestration, and management across the environments. -A private cloud, traditionally on-premises, is referred to an infrastructure +A private cloud, traditionally on-premises, is referred to an infrastructure environment managed by the user themselves. -A public cloud, traditionally off-premises, is referred to an infrastructure +A public cloud, traditionally off-premises, is referred to an infrastructure service provided by a third party. -## Identity-based authorization -Identity-based authorization is a security approach to restrict or allow access +## Identity-based authorization +Identity-based authorization is a security approach to restrict or allow access based on the authenticated identity of an individual. -## Infrastructure as a Service -Infrastructure as a Service, often referred to as IaaS, is a cloud computing -approach where the computing resources are delivered online via APIs. These +## Infrastructure as a Service +Infrastructure as a Service, often referred to as IaaS, is a cloud computing +approach where the computing resources are delivered online via APIs. These APIs communicate with underlying infrastructure like physical computing resources, - location, data partitioning, scaling, security, backup, etc. + location, data partitioning, scaling, security, backup, etc. -IaaS is one of the four types of cloud services along with SaaS +IaaS is one of the four types of cloud services along with SaaS (Software as a Service), PaaS (Platform as a Service), and Serverless. -## Infrastructure as Code -Infrastructure as Code (IaC) is the process of developers and operations teams' -ability of provisioning and managing computing resources automatically through +## Infrastructure as Code +Infrastructure as Code (IaC) is the process of developers and operations teams' +ability of provisioning and managing computing resources automatically through software, instead of using configuration tools. -## Ingress Controller -In Kubernetes, "ingress" is an object that allows access Kubernetes services -from outside the Kubernetes cluster. An ingress controller is responsible for -ingress, generally with a load balancer or an edge router that can help with +## Ingress Controller +In Kubernetes, "ingress" is an object that allows access Kubernetes services +from outside the Kubernetes cluster. An ingress controller is responsible for +ingress, generally with a load balancer or an edge router that can help with traffic management. -## Ingress Gateway -An Ingress Gateway is an edge of the mesh load balancer that provides secure and -reliable access from external networks to Kubernetes clusters. +## Ingress Gateway +An Ingress Gateway is an edge of the mesh load balancer that provides secure and +reliable access from external networks to Kubernetes clusters. -## Ingress Traffic -Ingress Traffic is the network traffic that originates outside the network and +## Ingress Traffic +Ingress Traffic is the network traffic that originates outside the network and has a destination inside the network. -## Key-Value Store -A Key-Value Store (or a KV Store) also referred to as a Key-Value Database is -a data model where each key is associated with one and only one value in +## Key-Value Store +A Key-Value Store (or a KV Store) also referred to as a Key-Value Database is +a data model where each key is associated with one and only one value in a collection. -## L4 - L7 Services -L4-L7 Services are a set of functions such as load balancing, web application -firewalls, service discovery, and monitoring for network layers within the +## L4 - L7 Services +L4-L7 Services are a set of functions such as load balancing, web application +firewalls, service discovery, and monitoring for network layers within the Open Systems Interconnection (OSI) model. -## Layer 7 Observability -Layer 7 Observability is a feature of Consul Service Mesh that enables a -unified workflow for metric collection, distributed tracking, and logging. -It also allows centralized configuration and management for a distributed -data plane. +## Layer 7 Observability +Layer 7 Observability is a feature of Consul Service Mesh that enables a +unified workflow for metric collection, distributed tracking, and logging. +It also allows centralized configuration and management for a distributed +data plane. -## Load Balancer -A load balancer is a network appliance that acts as a [reverse proxy](#reverse-proxy) +## Load Balancer +A load balancer is a network appliance that acts as a [reverse proxy](#reverse-proxy) and distributes network and application traffic across the servers. -## Load Balancing -Load Balancing is the process of distributing network and application traffic -across multiple servers. +## Load Balancing +Load Balancing is the process of distributing network and application traffic +across multiple servers. -## Load Balancing Algorithms -Load balancers follow an algorithm to determine how to route the traffic across +## Load Balancing Algorithms +Load balancers follow an algorithm to determine how to route the traffic across the server farm. Some of the commonly used algorithms are: 1. Round Robin 2. Least Connections @@ -251,127 +251,127 @@ the server farm. Some of the commonly used algorithms are: 5. Least Response Time Method 6. Least Bandwidth Method -## Multi-cloud -A multi-cloud environment generally uses two or more cloud computing services -from different vendors in a single architecture. This refers to the distribution -of compute resources, storage, and networking aspects across cloud environments. -A multi-cloud environment could be either all private cloud or all public cloud -or a combination of both. +## Multi-cloud +A multi-cloud environment generally uses two or more cloud computing services +from different vendors in a single architecture. This refers to the distribution +of compute resources, storage, and networking aspects across cloud environments. +A multi-cloud environment could be either all private cloud or all public cloud +or a combination of both. -## Multi-cloud Networking -Multi-cloud Networking provides network configuration and management across +## Multi-cloud Networking +Multi-cloud Networking provides network configuration and management across multiple cloud providers via APIs. -## Mutual Transport Layer Security (mTLS) -Mutual Transport Layer Security, also known as mTLS, is an authentication -mechanism that ensures network traffic security in both directions between -a client and server. +## Mutual Transport Layer Security (mTLS) +Mutual Transport Layer Security, also known as mTLS, is an authentication +mechanism that ensures network traffic security in both directions between +a client and server. -## Network Middleware Automation -The process of publishing service changes to network middleware such as -load balancers and firewalls and automating network tasks is called Network +## Network Middleware Automation +The process of publishing service changes to network middleware such as +load balancers and firewalls and automating network tasks is called Network Middleware Automation. -## Network security -Network security is the process of protecting data and network. It consists -of a set of policies and practices that are designed to prevent and monitor -unauthorized access, misuse, modification, or denial of a computer network +## Network security +Network security is the process of protecting data and network. It consists +of a set of policies and practices that are designed to prevent and monitor +unauthorized access, misuse, modification, or denial of a computer network and network-accessible resources. -## Network traffic management -Network Traffic Management is the process of ensuring optimal network operation -by using a set of network monitoring tools. Network traffic management also -focuses on traffic management techniques such as bandwidth monitoring, deep +## Network traffic management +Network Traffic Management is the process of ensuring optimal network operation +by using a set of network monitoring tools. Network traffic management also +focuses on traffic management techniques such as bandwidth monitoring, deep packet inspection, and application based routing. -## Network Visualization -Network Visualization is the process of visually displaying networks and -connected entities in a "boxes and lines" kind of a diagram. +## Network Visualization +Network Visualization is the process of visually displaying networks and +connected entities in a "boxes and lines" kind of a diagram. -In the context of microservices architecture, visualization can provide a clear -picture of how services are connected to each other, the service-to-service +In the context of microservices architecture, visualization can provide a clear +picture of how services are connected to each other, the service-to-service communication, and resource utilization of each service. -## Observability -Observability is the process of logging, monitoring, and alerting on the +## Observability +Observability is the process of logging, monitoring, and alerting on the events of a deployment or an instance. -## Elastic Scaling -Elastic Scaling is the ability to automatically add or remove compute or +## Elastic Scaling +Elastic Scaling is the ability to automatically add or remove compute or networking resources based on the changes in application traffic patterns. -## Platform as a Service -Platform-as-a-Service (PaaS) is a category of cloud computing that allows -users to develop, run, and manage applications without the complexity of -building and maintaining the infrastructure typically associated with -developing and launching the application. +## Platform as a Service +Platform-as-a-Service (PaaS) is a category of cloud computing that allows +users to develop, run, and manage applications without the complexity of +building and maintaining the infrastructure typically associated with +developing and launching the application. -## Reverse Proxy -A reverse proxy handles requests coming from outside, to the internal -network. Reverse Proxy provides a level of security that prevents the -external clients from having direct access to data on the corporate servers. -The reverse proxy is usually placed between the web server and the external -traffic. +## Reverse Proxy +A reverse proxy handles requests coming from outside, to the internal +network. Reverse Proxy provides a level of security that prevents the +external clients from having direct access to data on the corporate servers. +The reverse proxy is usually placed between the web server and the external +traffic. -## Role-based Access Controls -The act of restricting or provisioning access +## Role-based Access Controls +The act of restricting or provisioning access to a user based on their specific role in the organization. -## Server side load balancing -A Server-side Load Balancer sits between the client and the server farm, -accepts incoming traffic, and distributes the traffic across multiple backend +## Server side load balancing +A Server-side Load Balancer sits between the client and the server farm, +accepts incoming traffic, and distributes the traffic across multiple backend servers using various load balancing methods. -## Service configuration -A service configuration includes the name, description, and the specific -function of a service. In a microservices application architecture setting, +## Service configuration +A service configuration includes the name, description, and the specific +function of a service. In a microservices application architecture setting, a service configuration file includes a service definition. -## Service Catalog -A service catalog is an organized and curated collection of services that +## Service Catalog +A service catalog is an organized and curated collection of services that are available for developers to bind to their applications. -## Service Discovery -Service Discovery is the process of detecting services and devices on a -network. In a microservices context, service discovery is how applications +## Service Discovery +Service Discovery is the process of detecting services and devices on a +network. In a microservices context, service discovery is how applications and microservices locate each other on a network. -## Service Mesh -Service Mesh is the infrastructure layer that facilitates service-to-service -communication between microservices, often using a sidecar proxy. This -network of microservices make up microservice applications and the +## Service Mesh +Service Mesh is the infrastructure layer that facilitates service-to-service +communication between microservices, often using a sidecar proxy. This +network of microservices make up microservice applications and the interactions between them. -## Service Networking -Service networking brings several entities together to deliver a particular -service. Service Networking acts as the brain of an organization's +## Service Networking +Service networking brings several entities together to deliver a particular +service. Service Networking acts as the brain of an organization's networking and monitoring operations. -## Service Proxy -A service proxy is the client-side proxy for a microservice application. -It allows applications to send and receive messages over a proxy server. +## Service Proxy +A service proxy is the client-side proxy for a microservice application. +It allows applications to send and receive messages over a proxy server. -## Service Registration -Service registration is the process of letting clients (of the service) -and routers know about the available instances of the service. +## Service Registration +Service registration is the process of letting clients (of the service) +and routers know about the available instances of the service. Service instances are registered with a service registry on startup and deregistered at shutdown. -## Service Registry -Service Registry is a database of service instances and information on +## Service Registry +Service Registry is a database of service instances and information on how to send requests to these service instances. -## Microservice Segmentation -Microservice segmentation, sometimes visual, of microservices is the -segmentation in a microservices application architecture that enables +## Microservice Segmentation +Microservice segmentation, sometimes visual, of microservices is the +segmentation in a microservices application architecture that enables administrators to view their functions and interactions. -## Service-to-service communication -Service-to-service communication, sometimes referred to as -inter-service communication, is the ability of a microservice -application instance to communicate with another to collaborate and +## Service-to-service communication +Service-to-service communication, sometimes referred to as +inter-service communication, is the ability of a microservice +application instance to communicate with another to collaborate and handle client requests. -## Software as a Service -Software as a Service is a licensing and delivery approach to software -delivery where the software is hosted by a provider and licensed -to users on a subscription basis. +## Software as a Service +Software as a Service is a licensing and delivery approach to software +delivery where the software is hosted by a provider and licensed +to users on a subscription basis. diff --git a/website/content/docs/install/ports.mdx b/website/content/docs/install/ports.mdx index 787c2a29b5..dea835f51b 100644 --- a/website/content/docs/install/ports.mdx +++ b/website/content/docs/install/ports.mdx @@ -1,7 +1,7 @@ --- layout: docs page_title: Consul ports reference -description: Find information about the ports that Consul requires for its networking functions, including required ports for HCP Consul. Required ports differ for Consul servers and clients. +description: Find information about the ports that Consul requires for its networking functions, including required ports for HCP Consul Dedicated. Required ports differ for Consul servers and clients. --- # Consul ports reference @@ -16,13 +16,13 @@ The exact ports that Consul requires depend on your network's specific configura There are slight differences between the port requirements for Consul servers and clients. When a Consul server has services, proxies, or gateways registered to it, then it acts as both a server and client. -HashiCorp-managed servers deployed using [HCP Consul](/hcp/docs/consul) have distinct port assignments. For more information, refer to [cluster management in the HCP documentation](https://developer.hashicorp.com/hcp/docs/consul/concepts/cluster-management#hashicorp-managed-clusters). +[HCP Consul Dedicated servers](/hcp/docs/consul) have distinct port assignments. For more information, refer to [cluster management in the HCP documentation](https://developer.hashicorp.com/hcp/docs/consul/concepts/cluster-management#hashicorp-managed-clusters). ## Consul servers -The following table lists port names, their function, their network protocols, their default port numbers, whether they are enabled or disabled by default, port assignments for HashiCorp-managed server clusters, and the direction of traffic from the Consul server's perspective. +The following table lists port names, their function, their network protocols, their default port numbers, whether they are enabled or disabled by default, port assignments for HCP Consul Dedicated server clusters, and the direction of traffic from the Consul server's perspective. -| Port name | Use | Protocol | Default port | Default status | HCP-managed port | Direction | +| Port name | Use | Protocol | Default port | Default status | HCP Consul Dedicated port | Direction | | :------------------------ | :----------------------------------------- | :---------- | :----------- | :------------- | :--------------- | :-------------------- | | [DNS](#dns) | The DNS server | TCP and UDP | `8600` | Enabled | Unsupported | Incoming | | [HTTP](#http) | The HTTP API | TCP | `8500` | Enabled | Unsupported | Incoming | @@ -47,7 +47,7 @@ The server's DNS port does not need to be open when DNS queries are sent to Cons If you configure recursors in Consul to upstream DNS servers, then you need outbound access to those servers on port `53`. -To resolve Consul DNS requests when using HashiCorp-managed servers on HCP Consul, we recommend running Consul clients and resolving DNS against the clients. If your use case cannot accomodate this recommendation, open a support ticket. +To resolve Consul DNS requests when using HCP Consul Dedicated, we recommend running Consul clients and resolving DNS against the clients. If your use case cannot accommodate this recommendation, open a support ticket. ### HTTP @@ -63,13 +63,13 @@ The server's HTTP port does not need to be open when Consul clients service all The Consul CLI uses the HTTP port to interact with Consul by default. -HCP Consul does not support the HTTP port. +HCP Consul Dedicated does not support the HTTP port. ### HTTPS The following table lists information about the Consul server API's HTTPS port defaults: -| Default port | Protocol | Default status | Hashicorp-managed server port | +| Default port | Protocol | Default status | HCP Consul Dedicated server port | | :----------- | :------- | :------------------ | :---------------------------- | | `8501` | TCP | Disabled by default | `443` | @@ -79,7 +79,7 @@ The server HTTPS port does not need to be open when Consul clients service all H This port is disabled by default. You can enable it in the [agent configuration file](/consul/docs/agent/config/config-files#ports) or using the [`consul agent` CLI command](/consul/docs/agent/config/cli-flags). -HCP Consul assigns port `443` to HashiCorp-managed clusters, instead of the default `8501`. +HCP Consul Dedicated assigns port `443` to HCP Consul Dedicated clusters, instead of the default `8501`. ### gRPC @@ -93,13 +93,13 @@ When using [Consul Dataplane](/consul/docs/connect/dataplane), this port receive We recommend using gRPC TLS instead, so this port is disabled by default. You can enable it in the [agent configuration file](/consul/docs/agent/config/config-files#ports) or using the [`consul agent` CLI command](/consul/docs/agent/config/cli-flags). -HCP Consul does not support the gRPC port. +HCP Consul Dedicated does not support the gRPC port. ### gRPC TLS The following table lists information about the Consul API's gRPC with TLS port defaults: -| Default port | Protocol | Default status | Hashicorp-managed server port | +| Default port | Protocol | Default status | HCP Consul Dedicated server port | | :----------- | :------- | :------------------ | :---------------------------- | | `8503` | TCP | Enabled by default | `8502` | @@ -113,15 +113,13 @@ In deployments with [cluster peering connections](/consul/docs/connect/cluster-p In both local and remote cases, incoming traffic comes from the mesh gateways. -HCP Consul assigns port `8502` to HashiCorp-managed clusters, instead of the default `8503`. - -When the [v2 catalog](/consul/docs/architecture/v2/catalog) is enabled, all API calls from external systems, such as the Consul CLI and Terraform provider, use this port. +HCP Consul Dedicated assigns port `8502` to clusters, instead of the default `8503`. ### Server RPC The following table lists information about the Server RPC port defaults: -| Default port | Protocol | Default status | Hashicorp-managed server port | +| Default port | Protocol | Default status | HCP Consul Dedicated server port | | :----------- | :------- | :----------------- | :---------------------------- | | `8300` | TCP | Enabled by default | `8300` | @@ -135,7 +133,7 @@ When using WAN federation with mesh gateways, Consul servers must accept server The following table lists information about the LAN serf port defaults: -| Default port | Protocol | Default status | Hashicorp-managed server port | +| Default port | Protocol | Default status | HCP Consul Dedicated server port | | :----------- | :-----------| :----------------- | :---------------------------- | | `8301` | TCP and UDP | Enabled by default | `8301` | @@ -147,7 +145,7 @@ When running Enterprise deployments that use multiple admin partitions, all Cons The following table lists information about the WAN serf port defaults: -| Default port | Protocol | Default status | Hashicorp-managed server port | +| Default port | Protocol | Default status | HCP Consul Dedicated server port | | :----------- | :---------- | :----------------- | :---------------------------- | | `8302` | TCP and UDP | Enabled by default | `8302` | diff --git a/website/content/docs/integrate/partnerships.mdx b/website/content/docs/integrate/partnerships.mdx index 024ceeaefc..024a5b5413 100644 --- a/website/content/docs/integrate/partnerships.mdx +++ b/website/content/docs/integrate/partnerships.mdx @@ -17,7 +17,7 @@ The program is intended to be largely self-service with links to resources, code ## Categories of Consul Integrations -By leveraging Consul's RESTful HTTP API system, prospective partners are able to build extensible integrations at the data plane, platform, and the infrastructure layer to extend Consul's functionalities. These integrations can be performed both with the community edition of Consul, Consul Enterprise, and HCP Consul. +By leveraging Consul's RESTful HTTP API system, prospective partners are able to build extensible integrations at the data plane, platform, and the infrastructure layer to extend Consul's functionalities. These integrations can be performed both with the community edition of Consul, Consul Enterprise, and HCP Consul Dedicated. **The Consul ecosystem of integrations:** @@ -37,9 +37,9 @@ By leveraging Consul's RESTful HTTP API system, prospective partners are able to -> **Network Infrastructure Automation (NIA)***: These integrations leverage Consul's service catalog to seamlessly integrate with Consul-Terraform-Sync (CTS) to automate changes in network infrastructure via a publisher-subscriber method. Refer to the [NIA documentation](/consul/docs/integrate/nia-integration) for details. -**HCP Consul**: HCP Consul is secure by default and offers an out-of-the-box service mesh solution to streamline operations without the hassle of managing Consul servers. [Sign up for a free HCP Consul account](https://cloud.hashicorp.com/products/consul). +**HCP Consul Dedicated**: HCP Consul Dedicated is secure by default and offers an out-of-the-box service mesh solution to streamline operations without the hassle of managing Consul servers. [Sign up for a free HCP Consul Dedicated account](https://cloud.hashicorp.com/products/consul). -**Consul integration verification badges**: Partners will be issued the Consul Enterprise badge for integrations that work with [Consul Enterprise features](/consul/docs/enterprise) such as namespaces. Partners will be issued the HCP Consul badge for integrations validated to work with [HCP Consul](/hcp/docs/consul#features). Each badge would be displayed on HashiCorp's partner page as well as be available for posting on the partner's own website to provide better visibility and differentiation of the integration for joint customers. +**Consul integration verification badges**: Partners will be issued the Consul Enterprise badge for integrations that work with [Consul Enterprise features](/consul/docs/enterprise) such as namespaces. Partners will be issued the HCP Consul Dedicated badge for integrations validated to work with [HCP Consul Dedicated](/hcp/docs/consul#features). Each badge would be displayed on HashiCorp's partner page as well as be available for posting on the partner's own website to provide better visibility and differentiation of the integration for joint customers. @@ -49,12 +49,12 @@ By leveraging Consul's RESTful HTTP API system, prospective partners are able to -![HCP Consul](/img/HCPc_badge.png) +![HCP Consul Dedicated](/img/HCPc_badge.png) -Developing a valid integration with either Consul Enterprise or HCP Consul also qualifies the partner for the Premier tier of the HashiCorp Technology Partners program. The process for verification of these integrations is detailed below. +Developing a valid integration with either Consul Enterprise or HCP Consul Dedicated also qualifies the partner for the Premier tier of the HashiCorp Technology Partners program. The process for verification of these integrations is detailed below. ## Development Process @@ -87,7 +87,7 @@ Here are links to resources, documentation, examples and best practices to guide - [Consul Telemetry Documentation](/consul/docs/agent/telemetry) - [Monitoring Consul with Datadog APM](https://www.datadoghq.com/blog/consul-datadog/) -- [Monitor HCP Consul with New Relic Instant Observability](https://github.com/newrelic-experimental/hashicorp-quickstart-annex/blob/main/hcp-consul/README.md) +- [Monitor HCP Consul Dedicated with New Relic Instant Observability](https://github.com/newrelic-experimental/hashicorp-quickstart-annex/blob/main/hcp-consul/README.md) - [HCP Consul and CloudFabrix AIOps Integration](https://bot-docs.cloudfabrix.io/Bots/consul/?h=consul) - [Consul and SnappyFlow Full Stack Observability](https://docs.snappyflow.io/docs/Integrations/hcp_consul) @@ -167,17 +167,17 @@ Here are links to resources, documentation, examples and best practices to guide The only knowledge necessary to write a plugin is basic command-line skills and knowledge of the [Go programming language](http://www.golang.org). Use the plugin interface to develop your integration. All integrations should contain unit and acceptance testing. -**HCP Consul**: As a managed service, minimal configuration is required to deploy HCP Consul server clusters. You only need to install Consul client agents. Furthermore, HashiCorp provides all new users an initial credit, which provides approximately two months worth of [development cluster](https://cloud.hashicorp.com/products/consul/pricing) access. When deployed with AWS or Azure free tier services, there should be no cost beyond the time spent by the designated tester. Refer to the [Deploy HCP Consul tutorial](/consul/tutorials/get-started-hcp/hcp-gs-deploy) for details on getting started. +**HCP Consul Dedicated**: As a managed service, minimal configuration is required to deploy HCP Consul Dedicated server clusters. You only need to install Consul client agents. Furthermore, HashiCorp provides all new users an initial credit, which provides approximately two months worth of [development cluster](https://cloud.hashicorp.com/products/consul/pricing) access. When deployed with AWS or Azure free tier services, there should be no cost beyond the time spent by the designated tester. Refer to the [Deploy HCP Consul Dedicated tutorial](/consul/tutorials/get-started-hcp/hcp-gs-deploy) for details on getting started. -HCP Consul is currently only deployed on AWS and Microsoft Azure, so your application can be deployed to or run in AWS or Azure. +HCP Consul Dedicated is currently only deployed on AWS and Microsoft Azure, so your application can be deployed to or run in AWS or Azure. -#### HCP Consul Resource Links: +#### HCP Consul Dedicated Resource Links: -- [Getting Started with HCP Consul](/consul/tutorials/get-started-hcp/hcp-gs-deploy) -- [HCP Consul End-to-End Deployment](/consul/tutorials/cloud-deploy-automation/consul-end-to-end-overview) -- [Deploy HCP Consul with EKS using Terraform](/consul/tutorials/cloud-deploy-automation/consul-end-to-end-eks) -- [HCP Consul Deployment Automation](/consul/tutorials/cloud-deploy-automation) -- [HCP Consul documentation](/hcp/docs/consul/usage) +- [Getting Started with HCP Consul Dedicated](/consul/tutorials/get-started-hcp/hcp-gs-deploy) +- [HCP Consul Dedicated End-to-End Deployment](/consul/tutorials/cloud-deploy-automation/consul-end-to-end-overview) +- [Deploy HCP Consul Dedicated with EKS using Terraform](/consul/tutorials/cloud-deploy-automation/consul-end-to-end-eks) +- [HCP Consul Dedicated Deployment Automation](/consul/tutorials/cloud-deploy-automation) +- [HCP Consul Dedicated documentation](/hcp/docs/consul/usage) **Consul Enterprise**: An integration qualifies for Consul Enterprise when it is tested and compatible with Consul Enterprise Namespaces. diff --git a/website/content/docs/intro/index.mdx b/website/content/docs/intro/index.mdx index 42655814ce..1708f3d84b 100644 --- a/website/content/docs/intro/index.mdx +++ b/website/content/docs/intro/index.mdx @@ -7,7 +7,7 @@ description: >- # What is Consul? -HashiCorp Consul is a service networking solution that enables teams to manage secure network connectivity between services and across on-prem and multi-cloud environments and runtimes. Consul offers service discovery, service mesh, traffic management, and automated updates to network infrastructure device. You can use these features individually or together in a single Consul deployment. +HashiCorp Consul is a service networking solution that enables teams to manage secure network connectivity between services and across on-prem and multi-cloud environments and runtimes. Consul offers service discovery, service mesh, traffic management, and automated updates to network infrastructure devices. You can use these features individually or together in a single Consul deployment. > **Hands-on**: Complete the Getting Started tutorials to learn how to deploy Consul: - [Get Started on Kubernetes](/consul/tutorials/get-started-kubernetes) @@ -55,7 +55,7 @@ You can also schedule Consul workloads with [HashiCorp Nomad](https://www.nomadp ### Enable zero-trust network security -Microservice architectures are complex and difficult to secure against accidental discloser to malicious actors. Consul provides several mechanisms that enhance network security without any changes to your application code, including mutual transport layer security (mTLS) encryption on all traffic between services and Consul intentions, which are service-to-service permissions that you can manage through the Consul UI, API, and CLI. +Microservice architectures are complex and difficult to secure against accidental disclosure to malicious actors. Consul provides several mechanisms that enhance network security without any changes to your application code, including mutual transport layer security (mTLS) encryption on all traffic between services and Consul intentions, which are service-to-service permissions that you can manage through the Consul UI, API, and CLI. When you deploy Consul to Kubernetes clusters, you can also integrate with [HashiCorp Vault](https://www.vaultproject.io/) to manage sensitive data. By default, Consul on Kubernetes leverages Kubernetes secrets as the backend system. Kubernetes secrets are base64 encoded, unencrypted, and lack lease or time-to-live properties. By leveraging Vault as a secrets backend for Consul on Kubernetes, you can manage and store Consul related secrets within a centralized Vault cluster to use across one or many Consul on Kubernetes datacenters. Refer to [Vault as the Secrets Backend](/consul/docs/k8s/deployment-configurations/vault) for additional information. @@ -77,7 +77,7 @@ Rolling out changes can be risky, especially in complex network environments. Up HashiCorp offers core Consul functionality for free in the community edition, which is ideal for smaller businesses and teams that want to pilot Consul within their organizations. As your business grows, you can upgrade to Consul Enterprise, which offers additional capabilities designed to address organizational complexities of collaboration, operations, scale, and governance. -### HCP Consul +### HCP Consul Dedicated HashiCorp Cloud Platform (HCP) Consul is our SaaS that delivers Consul Enterprise capabilities and shifts the burden of managing the control plane to us. Create an HCP organization and leverage our expertise to simplify control plane maintenance and configuration. Learn more at [HashiCorp Cloud Platform](https://cloud.hashicorp.com/products/consul). diff --git a/website/content/docs/k8s/compatibility.mdx b/website/content/docs/k8s/compatibility.mdx index eee92512e3..e3b7ecd743 100644 --- a/website/content/docs/k8s/compatibility.mdx +++ b/website/content/docs/k8s/compatibility.mdx @@ -27,11 +27,11 @@ compared to non-LTS Enterprise and community edition releases. Unless otherwise noted, rows in the following compatibility table apply to both Consul Enterprise and Consul community edition (CE). -| Consul version | Compatible `consul-k8s` versions | Compatible Kubernetes versions | Compatible OpenShift versions | -| -------------- | -------------------------------- | -------------------------------| -------------------------------------- | -| 1.18.x CE | 1.4.x | 1.26.x - 1.29.x | 4.13.x - 4.15.x (4.16.x not available) | -| 1.17.x | 1.3.x | 1.25.x - 1.28.x | 4.12.x - 4.15.x | -| 1.16.x | 1.2.x | 1.24.x - 1.27.x | 4.11.x - 4.14.x | +| Consul version | Compatible `consul-k8s` versions | Compatible Kubernetes versions | Compatible OpenShift versions | +| -------------- | -------------------------------- | -------------------------------| ------------------------------| +| 1.19.x | 1.5.x | 1.27.x - 1.29.x | 4.13.x - 4.15.x | +| 1.18.x CE | 1.4.x | 1.26.x - 1.29.x | 4.13.x - 4.15.x | +| 1.17.x | 1.3.x | 1.25.x - 1.28.x | 4.12.x - 4.15.x | #### Enterprise Long Term Support releases @@ -40,10 +40,10 @@ Active Consul Enterprise releases expand their Kubernetes version compatibility window until the LTS release reaches its end of maintenance. -| Consul version | Compatible `consul-k8s` versions | Compatible Kubernetes versions | Compatible OpenShift versions | -| -------------- | -------------------------------- | -------------------------------| -------------------------------------- | -| 1.18.x Ent | 1.4.x | 1.26.x - 1.29.x | 4.13.x - 4.15.x (4.16.x not available) | -| 1.15.x Ent | 1.1.x | 1.23.x - 1.28.x | 4.10.x - 4.15.x | +| Consul version | Compatible `consul-k8s` versions | Compatible Kubernetes versions | Compatible OpenShift versions | +| -------------- | -------------------------------- | -------------------------------| ------------------------------| +| 1.18.x Ent | 1.4.x | 1.26.x - 1.29.x | 4.13.x - 4.15.x | +| 1.15.x Ent | 1.1.x | 1.23.x - 1.28.x | 4.10.x - 4.15.x | ### Version-specific upgrade requirements diff --git a/website/content/docs/k8s/connect/terminating-gateways.mdx b/website/content/docs/k8s/connect/terminating-gateways.mdx index 319fedf71d..4cac88da67 100644 --- a/website/content/docs/k8s/connect/terminating-gateways.mdx +++ b/website/content/docs/k8s/connect/terminating-gateways.mdx @@ -84,6 +84,12 @@ $ export CONSUL_HTTP_TOKEN=$(kubectl get secret consul-bootstrap-acl-token --tem ## Register external services with Consul + + +Consul on Kubernetes now supports the `Registration` CRD to register services running on external nodes with a terminating gateway. We recommend registering services with this CRD. For more information, refer to [Register services running on external nodes to Consul on Kubernetes](/consul/docs/k8s/deployment-configurations/external-service). + + + Registering the external services with Consul is a multi-step process: - Register external services with Consul @@ -96,7 +102,7 @@ Registering the external services with Consul is a multi-step process: You may register an external service with Consul using `ServiceDefaults` if [`TransparentProxy`](/consul/docs/connect/transparent-proxy) is enabled. Otherwise, -you may register the service as a node in the Consul catalog. +you may register the service as a node in the Consul catalog using the [`Registration` CRD](/consul/docs/connect/config-entries/registration). @@ -317,6 +323,57 @@ $ kubectl apply --filename service-intentions.yaml As a final step, you may define and deploy the external services as upstreams for the internal mesh services that wish to talk to them. An example deployment is provided which will serve as a static client for the terminating gateway service. + + + + + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: static-client +spec: + selector: + app: static-client + ports: + - port: 80 +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: static-client +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: static-client +spec: + replicas: 1 + selector: + matchLabels: + app: static-client + template: + metadata: + name: static-client + labels: + app: static-client + annotations: + 'consul.hashicorp.com/connect-inject': 'true' + spec: + containers: + - name: static-client + image: curlimages/curl:latest + command: ['/bin/sh', '-c', '--'] + args: ['while true; do sleep 30; done;'] + serviceAccountName: static-client +``` + + + + + + ```yaml @@ -363,6 +420,9 @@ spec: + + + Deploy the service with `kubectl apply`. ```shell-session diff --git a/website/content/docs/k8s/connect/transparent-proxy/enable-transparent-proxy.mdx b/website/content/docs/k8s/connect/transparent-proxy/enable-transparent-proxy.mdx index b2cc5dac0b..ce3df409eb 100644 --- a/website/content/docs/k8s/connect/transparent-proxy/enable-transparent-proxy.mdx +++ b/website/content/docs/k8s/connect/transparent-proxy/enable-transparent-proxy.mdx @@ -16,7 +16,7 @@ Your network must meet the following environment and software requirements to us * Transparent proxy is available for Kubernetes environments. * Consul 1.10.0+ * Consul Helm chart 0.32.0+. If you want to use the Consul CNI plugin to redirect traffic, Helm chart 0.48.0+ is required. Refer to [Enable the Consul CNI plugin](#enable-the-consul-cni-plugin) for additional information. -* You must create [service intentions](/consul/docs/connect/intentions) that explicitly allow communication between intended services so that Consul can infer upstream connections and use sidecar proxies to route messages appropriately. +* You must create [service intentions](/consul/docs/connect/intentions) that explicitly allow communication between services. Consul uses service intentions to infer upstream connections and route messages appropriately between sidecar proxies. * The `ip_tables` kernel module must be running on all worker nodes within a Kubernetes cluster. If you are using the `modprobe` Linux utility, for example, issue the following command: `$ modprobe ip_tables` @@ -25,7 +25,7 @@ Your network must meet the following environment and software requirements to us ## Enable transparent proxy -Transparent proxy mode is enabled for the entire cluster by default when you install Consul on Kubernetes using the Consul Helm chart. Refer to the [Consul Helm chart reference](/consul/docs/k8s/helm) for information about all default configurations. +Transparent proxy mode is enabled for the entire cluster by default when you install Consul on Kubernetes using the Consul Helm chart. Refer to the [Consul Helm chart reference](/consul/docs/k8s/helm) for information about all default configurations. You can explicitly enable transparent proxy for the entire cluster, individual namespaces, and individual services. @@ -242,7 +242,7 @@ spec: Additional services can query the [KubeDNS](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/) at `sample-app.default.svc.cluster.local` to reach `sample-app`. If ACLs are enabled and configured with default `deny` policies, the configuration also requires a [`ServiceIntention`](/consul/docs/connect/config-entries/service-intentions) to allow it to talk to `sample-app`. -You can query the KubeDNS for a service that belongs to a sameness group at `sample-app.virtual.group-name.sg.consul`. This syntax is required when failover is desired and `spec.defaultForFailover` is set to `false` in the sameness group CRD. Refer to [sameness group configuration entry reference](/consul/docs/connect/config-entries/sameness-group) for more information. +You can query the KubeDNS for a service that belongs to a sameness group at `sample-app.virtual.group-name.sg.consul`. This syntax is required when failover is desired. To use KubeDNS with sameness groups, `spec.defaultForFailover` must be set to `true` in the sameness group CRD. Refer to [sameness group configuration entry reference](/consul/docs/connect/config-entries/sameness-group) for more information. ### Headless services For services that are not addressed using a virtual cluster IP, you must configure the upstream service using the [DialedDirectly](/consul/docs/connect/config-entries/service-defaults#dialeddirectly) option. Then, use DNS to discover individual instance addresses and dial them through the transparent proxy. When this mode is enabled on the upstream, services present service mesh certificates for mTLS and intentions are enforced at the destination. @@ -253,4 +253,4 @@ Note that when dialing individual instances, Consul ignores the HTTP routing rul - Deployment configurations with federation across or a single datacenter spanning multiple clusters must explicitly dial a service in another datacenter or cluster using annotations. -- When dialing headless services, the request is proxied using a plain TCP proxy. Consul does not take into consideration the upstream's protocol. \ No newline at end of file +- When dialing headless services, the request is proxied using a plain TCP proxy. Consul does not take into consideration the upstream's protocol. diff --git a/website/content/docs/k8s/crds/index.mdx b/website/content/docs/k8s/crds/index.mdx index b2ed48854f..3db8edaf2f 100644 --- a/website/content/docs/k8s/crds/index.mdx +++ b/website/content/docs/k8s/crds/index.mdx @@ -19,6 +19,7 @@ You can specify the following values in the `kind` field. Click on a configurati - [`PeeringAcceptor`](/consul/docs/k8s/connect/cluster-peering/tech-specs#peeringacceptor) - [`PeeringDialer`](/consul/docs/k8s/connect/cluster-peering/tech-specs#peeringdialer) - [`ProxyDefaults`](/consul/docs/connect/config-entries/proxy-defaults) +- [`Registration`](/consul/docs/connect/config-entries/registration) - [`SamenessGroup`](/consul/docs/connect/config-entries/sameness-group) - [`ServiceDefaults`](/consul/docs/connect/config-entries/service-defaults) - [`ServiceSplitter`](/consul/docs/connect/config-entries/service-splitter) diff --git a/website/content/docs/k8s/deployment-configurations/argo-rollouts-configuration.mdx b/website/content/docs/k8s/deployment-configurations/argo-rollouts-configuration.mdx new file mode 100644 index 0000000000..5bd2a1e44d --- /dev/null +++ b/website/content/docs/k8s/deployment-configurations/argo-rollouts-configuration.mdx @@ -0,0 +1,262 @@ +--- +layout: docs +page_title: Argo Rollouts Progressive Delivery with Consul on Kubernetes +description: >- + Configure the Argo Rollouts Controller to enable Canary deployments for subset-based routing. Learn how k8s Rollouts integrate with Consul's service mesh. +--- + +# Argo Rollouts Progressive Delivery with Consul on Kubernetes + +This page describes the process to configure and use the [Argo Rollouts Controller](https://argo-rollouts.readthedocs.io/en/stable/) with Consul on Kubernetes to manage advanced subset-based routing for Canary deployments. + +Consul's support for Argo Rollouts is currently limited to subset-based routing. + +## Install Argo Rollouts Controller + +There are three methods for installing the Argo Rollouts Controller with Consul on Kubernetes: + +1. [Install Rollouts Using Helm and init containers](#install-rollouts-using-helm-and-binary). We recommend installing the Argo Rollouts Controllor using this method. +1. [Install Rollouts Using Helm and binary](#install-rollouts-using-helm-and-binary) +1. [Standalone installation](#stand-alone-installation) + +After installing the controller, you must [apply the RBAC CRD](https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-consul/main/yaml/rbac.yaml) to your Kubernetes cluster. + +### Install Rollouts Using Helm and init containers + +We recommend using this method to install this plugin. + +Add the following code to your `values.yaml` file to configure the plugin: + +```yaml +controller: + initContainers: + - name: copy-consul-plugin + image: hashicorp/rollouts-plugin-trafficrouter-consul + command: ["/bin/sh", "-c"] + args: + # Copy the binary from the image to the rollout container + - cp /bin/rollouts-plugin-trafficrouter-consul /plugin-bin/hashicorp + volumeMounts: + - name: consul-plugin + mountPath: /plugin-bin/hashicorp + trafficRouterPlugins: + trafficRouterPlugins: |- + - name: "hashicorp/consul" + location: "file:///plugin-bin/hashicorp/rollouts-plugin-trafficrouter-consul" + volumes: + - name: consul-plugin + emptyDir: {} + volumeMounts: + - name: consul-plugin + mountPath: /plugin-bin/hashicorp +``` + +Then install the `argo-rollouts` and apply your updated values using Helm: + +```shell-session +$ helm install argo-rollouts argo/argo-rollouts -f values.yaml -n argo-rollouts +``` + +### Install Rollouts Using Helm and binary + +To build the binary and install Rollouts, complete the following steps: + +1. Build this plugin using your preferred tool. For example, `make build`. +1. Mount the built plugin onto the `argo-rollouts` container. +1. Add the following code to your `values.yaml` file to configure the plugin: + +```yaml +controller: + trafficRouterPlugins: + trafficRouterPlugins: |- + - name: "argoproj-labs/consul" + location: "file:///plugin-bin/hashicorp/rollouts-plugin-trafficrouter-consul" + volumes: + - name: consul-route-plugin + hostPath: + # The path being mounted to, change this depending on your mount path + path: /rollouts-plugin-trafficrouter-consul + type: DirectoryOrCreate + volumeMounts: + - name: consul-route-plugin + mountPath: /plugin-bin/hashicorp +``` + +Then install the `argo-rollouts` and apply your updated values using Helm: + +```shell-session + $ helm install argo-rollouts argo/argo-rollouts -f values.yaml -n argo-rollouts +``` + +### Stand-alone installation + +This section describes the process to create a stand-alone installation. These instructions are for illustrative purposes. We recommend using init containers to create and install this plugin. + +To create a stand-alone installation of the Rollouts plugin, complete the following steps: + +1. Build this plugin. +1. Put the plugin on the path and mount it to the `argo-rollouts`container. +1. Create a `ConfigMap` to configure `argo-rollouts` with the plugin's location. + +The following example schedules a Deployment and mounts it to the `argo-rollouts` container: + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: argo-rollouts + namespace: argo-rollouts +spec: + selector: + matchLabels: + app.kubernetes.io/name: argo-rollouts + template: + spec: + # ... + volumes: + # ... + - name: consul-plugin + hostPath: + path: /plugin-bin/hashicorp/rollouts-plugin-trafficrouter-consul + type: DirectoryOrCreate + containers: + - name: argo-rollouts + # ... + volumeMounts: + - name: consul-route-plugin + mountPath: /plugin-bin/hashicorp + +``` + +The following example creates the `ConfigMap` with the location of the plugin: + +```yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: argo-rollouts-config + namespace: argo-rollouts +data: + trafficRouterPlugins: |- + - name: "argoproj-labs/consul" + location: "file:///plugin-bin/hashicorp/rollouts-plugin-trafficrouter-consul" +binaryData: {} +``` + +### Install the RBAC + +After either mounting the binary or using an init container, configure an RBAC using [Argo Rollout Consul plugin `rbac.yaml`](https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-consul/main/yaml/rbac.yaml): + +```shell-session +$ kubectl apply -f https://raw.githubusercontent.com/argoproj-labs/rollouts-plugin-trafficrouter-consul/main/yaml/rbac.yaml +``` + +## Use the Argo Rollouts Consul plugin + +Schedule the Kubernetes Service utilized by the service being rolled out. Additionally, configure any service defaults and proxy defaults required for the service. + +```yaml +apiVersion: v1 +kind: Service +metadata: + name: test-service +spec: + selector: + app: test-service + ports: + - name: http + port: 80 + targetPort: 8080 +``` + +Next, create the service resolver and service splitter CRDs for your stable service. Argo automatically modifies these CRDs during canary deployments. + +The following example demonstrates the configuration of the service resolver CRD, which creates service subsets and sets the stable subset as the default: + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceResolver +metadata: + name: test-service +spec: + subsets: + stable: + filter: Service.Meta.version == 1 + canary: + filter: "" + defaultSubset: stable +``` + +The following example demonstrates the configuration of the service splitter CRD, which initially sends 100% of traffic to the stable deployment: + +```yaml +apiVersion: consul.hashicorp.com/v1alpha1 +kind: ServiceSplitter +metadata: + name: test-service +spec: + splits: + - weight: 100 + serviceSubset: stable + - weight: 0 + serviceSubset: canary +``` + +Then configure your Argo Rollout resource to incrementally rollout the canary deployment: + +```yaml +apiVersion: argoproj.io/v1alpha1 +kind: Rollout +metadata: + name: test-service +spec: + replicas: 3 + selector: + matchLabels: + app: test-service + template: + metadata: + labels: + app: test-service + annotations: + consul.hashicorp.com/connect-inject: "true" + consul.hashicorp.com/service-meta-version: "1" + consul.hashicorp.com/service-tags: "v1" + spec: + containers: + - name: test-service + # Using alpine vs latest as there is a build issue with M1s. Also other tests in multiport-app reference + # alpine so standardizing this. + image: docker.mirror.hashicorp.services/hashicorp/http-echo:alpine + args: + - -text="I am v1" + - -listen=:8080 + ports: + - containerPort: 8080 + name: http + serviceAccountName: test-service + terminationGracePeriodSeconds: 0 # so deletion is quick + strategy: + canary: + trafficRouting: + plugins: + hashicorp/consul: + stableSubsetName: stable # subset name of the stable service + canarySubsetName: canary # subset name of the canary service + serviceName: test-service + steps: + - setWeight: 20 + - pause: {} + - setWeight: 40 + - pause: {duration: 10} + - setWeight: 60 + - pause: {duration: 10} + - setWeight: 80 + - pause: {duration: 10} +``` + +Finally, perform the Rollout operation using the Argo Rollouts Kubectl plugin. + +```shell-session +$ kubectl argo rollouts promote test-service +``` diff --git a/website/content/docs/k8s/deployment-configurations/datadog.mdx b/website/content/docs/k8s/deployment-configurations/datadog.mdx index cc627cb59b..c3df799f4d 100644 --- a/website/content/docs/k8s/deployment-configurations/datadog.mdx +++ b/website/content/docs/k8s/deployment-configurations/datadog.mdx @@ -465,4 +465,4 @@ Use of this method maps to Datadog as described in [Mapping Prometheus Metrics t The integration, by default, uses a wildcard (`".*"`) to collect **_all_** metrics emitted from the `/v1/agent/metrics` endpoint. -Please refer to the [Agent Telemetry](https://developer.hashicorp.com/consul/docs/agent/telemetry) documentation for a full list and desription of the metrics data collected. +Please refer to the [Agent Telemetry](https://developer.hashicorp.com/consul/docs/agent/telemetry) documentation for a full list and description of the metrics data collected. diff --git a/website/content/docs/k8s/deployment-configurations/external-service.mdx b/website/content/docs/k8s/deployment-configurations/external-service.mdx new file mode 100644 index 0000000000..711f016408 --- /dev/null +++ b/website/content/docs/k8s/deployment-configurations/external-service.mdx @@ -0,0 +1,38 @@ +--- +layout: docs +page_title: Register services running on external nodes to Consul on Kubernetes +description: >- + Learn how to register a service running on an external node to the Consul catalog when running Consul on Kubernetes. +--- + +# Register services running on external nodes to Consul on Kubernetes + +This page provides an overview for registering services in the Consul catalog when the service runs on a node that is not part of the Kubernetes cluster. + +## Introduction + +Because Kubernetes has built-in service discovery capabilities, Consul on Kubernetes includes components such as [Consul dataplanes](/consul/docs/connect/dataplane) and [service sync](/consul/docs/k8s/service-sync) so that operators can continue to use Kubernetes tools and processes. However, this approach to service networking still requires a service to run on a node that Consul is aware of, either through a local Consul client agent or the Kubernetes cluster. We call services that run on external nodes that Consul cannot automatically recognize _external services_. + +Previously, the only way to register an external service when running Consul on Kubernetes was using Consul's HTTP API. This approach requires additional ACLs and direct access to Consul's HTTP API endpoint. Consul now supports registering external services and their associated health checks in the Consul catalog using a [`Registration` custom resource definition (CRD)](/consul/docs/connect/config-entries/registration) that follows the format of [Consul's service definitions](/consul/docs/services/configuration/services-configuration-reference). + +## Workflows + +The process to register an external service in Consul on Kubernetes consists of the following steps: + +1. [Start Consul ESM](/consul/tutorials/connect-services/service-registration-external-services#monitor-the-external-service-with-consul-esm). You must use Consul ESM to run health checks on external services. +1. Define the external service and its health checks in a [`Registration` CRD](/consul/docs/connect/config-entries/registration). +1. Apply the CRD to your Kubernetes cluster. Internally, this action triggers an API call to Consul's [`/catalog/register` endpoint](/consul/api-docs/catalog#register-entity) to register the service. +1. When using Consul's service mesh, you should also: + - Deploy a [terminating gateway](/consul/docs/k8s/connect/terminating-gateways) so that downstream services can communicate with the external service. + - Define [service intentions](/consul/docs/connect/intentions) for the external service and the downstream services that communicate with it. + +## Guidance + +The following resources are available to help you register external services to Consul on Kubernetes. + +### Reference + +- [`Registration` custom resource definition (CRD) configuration reference](/consul/docs/connect/config-entries/registration) +- [`/catalog/register` HTTP API endpoint reference](/consul/api-docs/catalog#register-entity) +- [Service configuration reference](/consul/docs/services/configuration/services-configuration-reference) +- [Health check configuration reference](/consul/docs/services/configuration/checks-configuration-reference) \ No newline at end of file diff --git a/website/content/docs/k8s/dns.mdx b/website/content/docs/k8s/dns/enable.mdx similarity index 96% rename from website/content/docs/k8s/dns.mdx rename to website/content/docs/k8s/dns/enable.mdx index 8e713a86e1..708ca9241c 100644 --- a/website/content/docs/k8s/dns.mdx +++ b/website/content/docs/k8s/dns/enable.mdx @@ -5,16 +5,15 @@ description: >- Use a k8s ConfigMap to configure KubeDNS or CoreDNS so that you can use Consul's `.service.consul` syntax for queries and other DNS requests. In Kubernetes, this process uses either stub-domain or proxy configuration. --- -# Resolve Consul DNS Requests in Kubernetes +# Resolve Consul DNS requests in Kubernetes -One of the primary query interfaces to Consul is the -[DNS interface](/consul/docs/services/discovery/dns-overview). You can configure Consul DNS in +This topic describes how to configure Consul DNS in Kubernetes using a [stub-domain configuration](https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/#configure-stub-domain-and-upstream-dns-servers) if using KubeDNS or a [proxy configuration](https://coredns.io/plugins/forward/) if using CoreDNS. Once configured, DNS requests in the form `.service.consul` will -resolve for services in Consul. This will work from all Kubernetes namespaces. +resolve for services in Consul. This works from all Kubernetes namespaces. -> **Note:** If you want requests to just `` (without the `.service.consul`) to resolve, then you'll need to turn on [Consul to Kubernetes Service Sync](/consul/docs/k8s/service-sync#consul-to-kubernetes). diff --git a/website/content/docs/k8s/dns/views/enable.mdx b/website/content/docs/k8s/dns/views/enable.mdx new file mode 100644 index 0000000000..3f29eac346 --- /dev/null +++ b/website/content/docs/k8s/dns/views/enable.mdx @@ -0,0 +1,102 @@ +--- +layout: docs +page_title: Enable Consul DNS proxy for Kubernetes +description: -> + Learn how to schedule a Consul DNS proxy for a Kubernetes Pod so that your services can return Consul DNS results for service discovery. +--- + +# Enable Consul DNS proxy for Kubernetes + +This page describes the process to deploy a Consul DNS proxy in a Kubernetes Pod so that Services can resolve Consul DNS requests. For more information, refer to [Consul DNS views for Kubernetes](/consul/docs/k8s/dns/views). + +## Prerequisites + +You must meet the following minimum application versions to enable the Consul DNS proxy for Kubernetes: + +- Consul v1.20.0 or higher +- Either Consul on Kubernetes or the Consul Helm chart, v1.6.0 or higher + +## Update Helm values + +To enable the Consul DNS proxy, add the required [Helm values](/consul/docs/k8s/helm) to your Consul on Kubernetes deployment. + +```yaml +connectInject: + enabled: true +dns: + enabled: true + proxy: true +``` + +### ACLs + +We recommend you create a dedicated [ACL token with DNS permissions](/consul/docs/security/acl/tokens/create/create-a-dns-token) for the Consul DNS proxy. The Consul DNS proxy requires these ACL permissions. + +```hcl +node_prefix "" { + policy = "read" +} + +service_prefix "" { + policy = "read" +} +``` + +You can manage ACL tokens with Consul on Kubernetes, or you can configure the DNS proxy to access a token stored in Kubernetes secret. To use a Kubernetes secret, add the following configuration to your Helm chart. + +```yaml +dns: + proxy: + aclToken: + secretName: + secretKey: +``` + +## Retrieve Consul DNS proxy's address + +To look up the IP address for the Consul DNS proxy in the Kubernetes Pod, run the following command. + +```shell-session +$ kubectl get services –-all-namespaces --selector="app=consul,component=dns-proxy" --output jsonpath='{.spec.clusterIP}' +10.96.148.46 +``` + +Use this address when you update the ConfigMap resource. + +## Update Kubernetes ConfigMap + +Create or update a [ConfigMap object in the Kubernetes cluster](https://kubernetes.io/docs/concepts/configuration/configmap/) so that Kubernetes forwards DNS requests with the `.consul` domain to the IP address of the Consul DNS proxy. + +The following example of a `coredns-custom` ConfigMap configures Kubernetes to forward Consul DNS requests in the cluster to the Consul DNS Proxy running on `10.96.148.46`. This resource modifies the CoreDNS without modifications to the original `Corefile`. + +```yaml +kind: ConfigMap +metadata: + name: coredns-custom + namespace: kube-system +data: + consul.server: | + consul:53 { + errors + cache 30 + forward . 10.96.148.46 + reload + } +``` + +After updating the DNS configuration, perform a rolling restart of the CoreDNS. + +```shell-session +kubectl -n kube-system rollout restart deployment coredns +``` + +For more information about using a `coredns-custom` resource, refer to the [Rewrite DNS guide in the Azure documentation](https://learn.microsoft.com/en-us/azure/aks/coredns-custom#rewrite-dns). For general information about modifying a ConfigMap, refer to [the Kubernetes documentation](https://kubernetes.io/docs/tasks/administer-cluster/dns-custom-nameservers/#coredns). + +## Next steps + +After you enable the Consul DNS proxy, services in the Kubernetes cluster can resolve Consul DNS addresses. + +- To learn more about Consul DNS for service discovery, refer to [DNS usage overview](/consul/docs/services/discovery/dns-overview). +- If your datacenter has ACLs enabled, create a [Consul ACL token](/consul/docs/security/acl/tokens) for the Consul DNS proxy and then restart the DNS proxy. +- To enable service discovery across admin partitions, [export services between partitions](/consul/docs/connect/config-entries/exported-services). +- To use Consul DNS for service discovery with other runtimes, across cloud regions, or between cloud providers, [establish a cluster peering connection](/consul/docs/k8s/connect/cluster-peering/usage/establish-peering). diff --git a/website/content/docs/k8s/dns/views/index.mdx b/website/content/docs/k8s/dns/views/index.mdx new file mode 100644 index 0000000000..7d482a9d3e --- /dev/null +++ b/website/content/docs/k8s/dns/views/index.mdx @@ -0,0 +1,48 @@ +--- +layout: docs +page_title: Consul DNS views for Kubernetes +description: -> + Kubernetes clusters can use the Consul DNS proxy to return service discovery results from the Consul catalog. Learn about how to configure your k8s cluster so that applications can resolve Consul DNS addresses without gossip communication. +--- + +# Consul DNS views for Kubernetes + +This topic describes how to schedule a dedicated Consul DNS proxy in a Kubernetes Pod so that applications in Kubernetes can resolve Consul DNS addresses. You can use the Consul DNS proxy to enable service discovery across admin partitions in Kubernetes deployments without needing to deploy Consul client agents. + +## Introduction + +Kubernetes operators typically choose networking tools such as [kube-dns](https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/) or [CoreDNS](https://kubernetes.io/docs/tasks/administer-cluster/coredns/) for their service discovery operations, and choose to bypass Consul DNS entirely. These DNS options are often sufficient for service networking operations within a single Kubernetes cluster. + +Consul on Kubernetes supports [configuring Kubernetes to resolve Consul DNS](/consul/docs/k8s/dns). However, two common challenges result when you rely on these configurations: + +- Kubernetes requires Consul to use gossip communication with agents or dataplanes in order to enable Consul DNS. +- Consul requires that admin partitions be included in the DNS address. Otherwise, DNS queries assume the `default` partition by default. + +The `consul-dns` proxy does not require the presence of Consul client agents or Consul dataplanes, removing gossip communication as a requirement for Consul DNS on Kubernetes. The proxy is also designed for deployment in a Kubernetes cluster with [external servers enabled](/consul/docs/k8s/deployment-configurations/servers-outside-kubernetes). When a cluster runs in a non-default admin partition and uses the proxy to query external servers, Consul automatically recognizes the admin partition that originated the request and returns service discovery results scoped to that specific admin partition. + +To use Consul DNS for service discovery on Kubernetes, deploy a `dns-proxy` service in each Kubernetes Pod that needs to resolve Consul DNS. Kubernetes sends all DNS requests to the Kubernetes controller first. The controller forwards requests for the `.consul` domain to the `dns-proxy` service, which then queries the Consul catalog and returns service discovery results. + +## Workflows + +The process to enable Consul DNS views for service discovery in Kubernetes deployments consists of the following steps: + +1. In a cluster configured to use [external Consul servers](/consul/docs/k8s/deployment-configurations/servers-outside-kubernetes), update the Helm values for your Consul on Kubernetes deployment so that `dns.proxy.enabled=true`. When you apply the updated configuration, Kubernetes deploys the Consul DNS proxy. +1. Look up the IP address for the Consul DNS proxy in the Kubernetes cluster. +1. Update the ConfigMap resource in the Kubernetes cluster so that it forwards requests for the `.consul` domain to the IP address of the Consul DNS proxy. + +For more information about the underlying concepts described in this workflow, refer to [DNS forwarding overview](/consul/docs/services/discovery/dns-forwarding). + +## Benefits + +Consul on Kubernetes currently uses [Consul dataplanes](/consul/docs/connect/dataplane) by default. These lightweight processes provide Consul access to the sidecar proxies in the service mesh, but leave Kubernetes in charge of most other service discovery and service mesh operations. + +- **Use Kubernetes DNS and Consul DNS in a single deployment**. The Consul DNS proxy enables any application in a Pod to resolve an address through Consul DNS without disrupting the underlying Kubernetes DNS functionality. +- **Consul service discovery using fewer resources**. When you use the Consul DNS proxy for service discovery, you do not need to schedule Consul client agents or dataplanes as sidecars. One Kubernetes Service that uses the same resources as a single Consul dataplane provides Pods access to the Consul service catalog. +- **Consul DNS without gossip communication**. The Consul DNS service runs on both Consul server and Consul client agents, which use [gossip communication](/consul/docs/security/encryption/gossip) to ensure that service discovery results are up-to-date. The Consul DNS proxy provides access to Consul DNS without the security overhead of agent-to-agent gossip. + +## Constraints and limitations + +If you experience issues using the Consul DNS proxy for Kubernetes, refer to the following list of technical constraints and limitations. + +- You must use Kubernetes as your runtime to use the Consul DNS proxy. You cannot schedule the Consul DNS proxy in other container-based environments. +- To perform DNS lookups on other admin partitions, you must [export services between partitions](/consul/docs/connect/config-entries/exported-services) before you can query them. \ No newline at end of file diff --git a/website/content/docs/k8s/helm.mdx b/website/content/docs/k8s/helm.mdx index a2525a8b38..42635dc2ca 100644 --- a/website/content/docs/k8s/helm.mdx +++ b/website/content/docs/k8s/helm.mdx @@ -108,6 +108,9 @@ Use these links to navigate to a particular top-level stanza. image that is used for functionality such as catalog sync. This can be overridden per component. + - `imagePullPolicy` ((#v-global-imagepullpolicy)) (`string: ""`) - The image pull policy used globally for images controlled by Consul (consul, consul-dataplane, consul-k8s, consul-telemetry-collector). + One of "IfNotPresent", "Always", "Never", and "". Refer to https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy + - `datacenter` ((#v-global-datacenter)) (`string: dc1`) - The name of the datacenter that the agents should register as. This can't be changed once the Consul cluster is up and running since Consul doesn't support an automatic way to change this value currently: @@ -693,7 +696,7 @@ Use these links to navigate to a particular top-level stanza. This can either be used to [configure a new cluster](/hcp/docs/consul/self-managed/new) or [link an existing one](/hcp/docs/consul/self-managed/existing). - Note: this setting should not be enabled for [HashiCorp-managed clusters](/hcp/docs/consul/hcp-managed). + Note: this setting should not be enabled for [HCP Consul Dedicated clusters](/hcp/docs/consul/dedicated). It is strictly for linking self-managed clusters. - `resourceId` ((#v-global-cloud-resourceid)) - The resource id of the HCP Consul Central cluster to link to. Eg: @@ -763,25 +766,11 @@ Use these links to navigate to a particular top-level stanza. - `experiments` ((#v-global-experiments)) (`array: []`) - Consul feature flags that will be enabled across components. Supported feature flags: - - `resource-apis`: - _**Warning**_! This feature is under active development. It is not - recommended for production use. Setting this flag during an - upgrade could risk breaking your Consul cluster. - If this flag is set, Consul components will use the - V2 resources APIs for all operations. - - `v2tenancy`: - _**Warning**_! This feature is under active development. It is not - recommended for production use. Setting this flag during an - upgrade could risk breaking your Consul cluster. - If this flag is set, Consul V2 resources (catalog, mesh, auth, etc) - will use V2 implementations for tenancy (partitions and namesapces) - instead of bridging to the existing V1 implementations. The - `resource-apis` feature flag must also be set. Example: ```yaml - experiments: [ "resource-apis" ] + experiments: [ "" ] ``` ### server ((#h-server)) @@ -1795,6 +1784,9 @@ Use these links to navigate to a particular top-level stanza. or may not be broadly accessible depending on your Kubernetes cluster. Set this to false to skip syncing ClusterIP services. + - `syncLoadBalancerEndpoints` ((#v-synccatalog-syncloadbalancerendpoints)) (`boolean: false`) - If true, LoadBalancer service endpoints instead of ingress addresses will be synced to Consul. + If false, LoadBalancer endpoints are not synced to Consul. + - `ingress` ((#v-synccatalog-ingress)) - `enabled` ((#v-synccatalog-ingress-enabled)) (`boolean: false`) - Syncs the hostname from a Kubernetes Ingress resource to service registrations @@ -1965,6 +1957,15 @@ Use these links to navigate to a particular top-level stanza. - external-dns.alpha.kubernetes.io/hostname ``` + - `metrics` ((#v-connectinject-apigateway-managedgatewayclass-metrics)) - Metrics settings for gateways created with this gateway class configuration. + + - `enabled` ((#v-connectinject-apigateway-managedgatewayclass-metrics-enabled)) (`boolean: -`) - This value enables or disables metrics collection on a gateway, overriding the global gateway metrics collection settings. + + - `port` ((#v-connectinject-apigateway-managedgatewayclass-metrics-port)) (`int: null`) - This value sets the port to use for scraping gateway metrics via prometheus, defaults to 20200 if not set. Must be in the port + range of 1024-65535. + + - `path` ((#v-connectinject-apigateway-managedgatewayclass-metrics-path)) (`string: null`) - This value sets the path to use for scraping gateway metrics via prometheus, defaults to /metrics if not set. + - `resources` ((#v-connectinject-apigateway-managedgatewayclass-resources)) (`map`) - The resource settings for Pods handling traffic for Gateway API. - `deployment` ((#v-connectinject-apigateway-managedgatewayclass-deployment)) - This value defines the number of pods to deploy for each Gateway as well as a min and max number of pods for all Gateways @@ -2309,8 +2310,10 @@ Use these links to navigate to a particular top-level stanza. - `consul.hashicorp.com/enable-sidecar-proxy-lifecycle` - `consul.hashicorp.com/enable-sidecar-proxy-shutdown-drain-listeners` - `consul.hashicorp.com/sidecar-proxy-lifecycle-shutdown-grace-period-seconds` + - `consul.hashicorp.com/sidecar-proxy-lifecycle-startup-grace-period-seconds` - `consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-port` - `consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-shutdown-path` + - `consul.hashicorp.com/sidecar-proxy-lifecycle-graceful-startup-path` - `defaultEnabled` ((#v-connectinject-sidecarproxy-lifecycle-defaultenabled)) (`boolean: true`) @@ -2318,10 +2321,14 @@ Use these links to navigate to a particular top-level stanza. - `defaultShutdownGracePeriodSeconds` ((#v-connectinject-sidecarproxy-lifecycle-defaultshutdowngraceperiodseconds)) (`integer: 30`) + - `defaultStartupGracePeriodSeconds` ((#v-connectinject-sidecarproxy-lifecycle-defaultstartupgraceperiodseconds)) (`integer: 0`) + - `defaultGracefulPort` ((#v-connectinject-sidecarproxy-lifecycle-defaultgracefulport)) (`integer: 20600`) - `defaultGracefulShutdownPath` ((#v-connectinject-sidecarproxy-lifecycle-defaultgracefulshutdownpath)) (`string: /graceful_shutdown`) + - `defaultGracefulStartupPath` ((#v-connectinject-sidecarproxy-lifecycle-defaultgracefulstartuppath)) (`string: /graceful_startup`) + - `defaultStartupFailureSeconds` ((#v-connectinject-sidecarproxy-defaultstartupfailureseconds)) (`integer: 0`) - Configures how long the k8s startup probe will wait before the proxy is considered to be unhealthy and the container is restarted. A value of zero disables the probe. @@ -2841,8 +2848,8 @@ Use these links to navigate to a particular top-level stanza. - `resourceId` ((#v-telemetrycollector-cloud-resourceid)) - The resource id of the HCP Consul Central cluster to push metrics for. Eg: `organization/27109cd4-a309-4bf3-9986-e1d071914b18/project/fcef6c24-259d-4510-bb8d-1d812e120e34/hashicorp.consul.global-network-manager.cluster/consul-cluster` - This is used for HCP Consul Central-linked or managed clusters where global.cloud.resourceId is unset. For example, when using externalServers - with HCP Consul-managed clusters or HCP Consul Central-linked clusters in a different admin partition. + This is used for HCP Consul Central-linked or HCP Consul Dedicated clusters where global.cloud.resourceId is unset. For example, when using externalServers + with HCP Consul Dedicated clusters or HCP Consul Central-linked clusters in a different admin partition. If global.cloud.resourceId is set, this should either be unset (defaulting to global.cloud.resourceId) or be the same as global.cloud.resourceId. diff --git a/website/content/docs/k8s/multiport/configure.mdx b/website/content/docs/k8s/multiport/configure.mdx deleted file mode 100644 index e8fd916468..0000000000 --- a/website/content/docs/k8s/multiport/configure.mdx +++ /dev/null @@ -1,491 +0,0 @@ ---- -layout: docs -page_title: Configure multi-port services -description: Learn how to enable the v2 catalog and configure services to support multiple ports in Consul on Kubernetes. You can configure multiple ports on a single service or multiple services and ports in a single container. ---- - -# Configure multi-port services - - - -The v2 catalog API and Traffic Permissions API are currently in beta. This documentation supports testing and development scenarios. Do not use these APIs in secure production environments. - - - - - -Multi-port services and selecting workloads using multiple services require enabling [Consul's v2 architecture](/consul/docs/architecture/v2). - - - -This page describes the process for integrating a service that uses multiple ports in a single container when running Consul on Kubernetes deployments. It includes example configurations to demonstrate an end-to-end deployment test of Consul's multi-port features. - -## Requirements - -Registering multi-port services with Consul requires Kubernetes. Multi-port services are not supported on VM deployments. - -### Version requirements - -Consul deployments that use the v2 catalog enabled must meet the following minimum version requirements: - -- `consul` v1.18.0 -- `consul-k8s` CLI v1.4.0 or `hashicorp/consul` Helm chart release v1.4.0 -- `consul-dataplane` v1.4.0 - -To install or update the `consul-k8s CLI`, refer to [install the latest version](/consul/docs/k8s/installation/install-cli#install-the-latest-version) or [upgrade the CLI](/consul/docs/k8s/upgrade/upgrade-cli#upgrade-the-cli). - -The required version of Consul dataplanes deploy automatically when using the latest version of `consul-k8s`. Dataplane version is configured manually when you [modify `imageConsulDataplane`](/consul/docs/k8s/helm#v-global-imageconsuldataplane) in the Helm chart. - -For more information about upgrading Helm charts, refer to [Upgrade Helm chart version](/consul/docs/k8s/upgrade#upgrade-helm-chart-version). - -### Annotation requirements - -The examples on this page include the following required annotations in Kubernetes Deployment resources: - -```yaml -spec: - template: - metadata: - annotations: - "consul.hashicorp.com/mesh-inject": "true" - "consul.hashicorp.com/transparent-proxy": "true" -``` - -These annotations inject Consul dataplanes and enable transparent proxy mode so that the services can curl each other on ports configured in the Kubernetes Service resource. Refer to [annotations and labels](/consul/docs/k8s/annotations-and-labels) for more information. - -## Enable the v2 catalog - -To enable the v2 catalog and its support for multi-port services, set `global.experiments: ["resource-apis"]` and `ui.enabled: false`. The following example includes these parameters in a Helm chart with additional configurations for the Consul installation: - - - -```yaml -global: - enabled: true - name: consul - image: hashicorp/consul:1.18.0 - datacenter: dc1 - tls: - enabled: true - acls: - manageSystemACLs: true - experiments: ["resource-apis"] -server: - enabled: true - replicas: 1 -connectInject: - enabled: true -ui: - enabled: false -``` - - - -Then install Consul to your Kubernetes cluster using either the `consul-k8s` CLI or Helm. - - - - - -```shell-session -$ consul-k8s install -config-file=values.yaml -``` - - - - - -```shell-session -$ helm install consul hashicorp/consul --create-namespace --namespace consul --version 1.4.0 --values values.yaml -``` - - - - -## Define the multi-port service - -Consul's v2 catalog supports two new methods for registering services on the mesh in Kubernetes: - -- **Method 1**: Register a Kubernetes service that select workloads which expose multiple ports -- **Method 2**: Register multiple Kubernetes Services that direct traffic to an explicit port on the same workload - -These methods affect how the Services are addressed in Kubernetes. - -Each tab in the following example contains a configuration that defines an `api` service using one of these methods. Both definitions schedule a Pod running two containers that each support traffic to one of the ports exposed by the Service. In `Method 1`, both services are addressed using `api` because both services are exposed by a single service. In `Method 2`, `api` and `api-admin` are defined as separate services and can be addressed using distinct names. - - - - - - - -```yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: api ---- -apiVersion: v1 -kind: Service -metadata: - name: api -spec: - selector: - app: api - ports: - - name: api - port: 80 - targetPort: api - - name: admin - port: 90 - targetPort: admin ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: api -spec: - replicas: 1 - selector: - matchLabels: - app: api - template: - metadata: - labels: - app: api - annotations: - "consul.hashicorp.com/mesh-inject": "true" - "consul.hashicorp.com/transparent-proxy": "true" - spec: - containers: - - name: api - image: hashicorp/http-echo:latest - args: - - -text="hello world" - - -listen=:8080 - ports: - - containerPort: 8080 - name: api - - name: api-admin - image: hashicorp/http-echo:latest - args: - - -text="hello world from 9090 admin" - - -listen=:9090 - ports: - - containerPort: 9090 - name: admin - serviceAccountName: api -``` - - - - - - - - -```yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: api ---- -apiVersion: v1 -kind: Service -metadata: - name: api -spec: - selector: - app: api - ports: - - name: api - port: 80 - targetPort: api ---- -apiVersion: v1 -kind: Service -metadata: - name: api-admin -spec: - selector: - app: api - ports: - - name: admin - port: 90 - targetPort: admin ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: api -spec: - replicas: 1 - selector: - matchLabels: - app: api - template: - metadata: - labels: - app: api - annotations: - "consul.hashicorp.com/mesh-inject": "true" - "consul.hashicorp.com/transparent-proxy": "true" - spec: - containers: - - name: api - image: hashicorp/http-echo:latest - args: - - -text="hello world" - - -listen=:8080 - ports: - - containerPort: 8080 - name: api - - name: api-admin - image: hashicorp/http-echo:latest - args: - - -text="hello world from 9090 admin" - - -listen=:9090 - ports: - - containerPort: 9090 - name: admin - serviceAccountName: api -``` - - - - - -For testing purposes, the following example defines a Service to function as a static client that you can use to verify that the multi-port services function correctly. - - - -```yaml -apiVersion: v1 -kind: ServiceAccount -metadata: - name: web ---- -apiVersion: v1 -kind: Service -metadata: - name: web -spec: - selector: - app: web - ports: - - port: 80 ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: web -spec: - replicas: 1 - selector: - matchLabels: - app: web - template: - metadata: - labels: - app: web - annotations: - "consul.hashicorp.com/mesh-inject": "true" - "consul.hashicorp.com/transparent-proxy": "true" - spec: - containers: - - name: web - image: curlimages/curl:latest - # Just spin & wait forever, we'll use `kubectl exec` to demo - command: ['/bin/sh', '-c', '--'] - args: ['while true; do sleep 30; done;'] - serviceAccountName: web -``` - - - -To apply these services to your Kubernetes deployment and register them with Consul, run the following command: - -```shell-session -$ kubectl apply -f api.yaml -f web.yaml -``` - -## Configure traffic permissions - -Consul uses traffic permissions to validate communication between services based on L4 identity. When ACLs are enabled for the service mesh, traffic permissions deny all services by default. In order to allow traffic between the static client and the multi-port service, create CRDs that allow traffic to each port. - -The following examples create Consul CRDs that allow traffic to only one port of the multi-port service. Each resource separately denies `web` permission when it is a source of traffic to one of the services. These traffic permissions work with either method for defining a multi-port service. When following the instructions on this page, apply these permissions individually when you validate the ports. - - - - - -```yaml -apiVersion: auth.consul.hashicorp.com/v2beta1 -kind: TrafficPermissions -metadata: - name: web-to-api -spec: - destination: - identityName: api - action: ACTION_ALLOW - permissions: - - sources: - - identityName: web - destinationRules: - - portNames: ["api"] -``` - - - - - -```yaml -apiVersion: auth.consul.hashicorp.com/v2beta1 -kind: TrafficPermissions -metadata: - name: web-to-admin -spec: - destination: - identityName: api - action: ACTION_ALLOW - permissions: - - sources: - - identityName: web - destinationRules: - - portNames: ["admin"] -``` - - - - -## Validate multi-port connection - -To open a shell to the `web` container, you need the name of the Pod it currently runs on. Run the following command to return a list of Pods: - -```shell-session -$ kubectl get pods -NAME READY STATUS RESTARTS AGE -api-5784b54bcc-tp98l 3/3 Running 0 6m55s -web-6dcbd684bc-gk8n5 2/2 Running 0 6m55s -``` - -Set environment variables to remember the pod name for the web workload for use in future commands. - - - -```shell-session -$ export WEB_POD=web-6dcbd684bc-gk8n5 -``` - - - -### Apply traffic permissions - -Use the `web` Pod's name to open a shell session and test the `api` service on both ports. When ACLs are enabled, these commands fail until you apply a traffic permissions resource. - - - - - -Test the `api` service on port 80. - -```shell-session -$ kubectl exec -it ${WEB_POD} -c web -- curl api:80 -``` - -Then test the `api` service on port 90. - -```shell-session -$ kubectl exec -it ${WEB_POD} -c web -- curl api:90 -``` - - - - - -Test the `api` service on port 80. - -```shell-session -$ kubectl exec -it ${WEB_POD} -c web -- curl api:80 -``` - -Then test the `api-admin` service on port 90. - -```shell-session -$ kubectl exec -it ${WEB_POD} -c web -- curl api-admin:90 -``` - - - - -Apply the CRD to allow traffic to port 80: - -```shell-session -$ kubectl apply -f allow-80.yaml -``` - - - - - -Then, open a shell session in the `web` container and test the `api` service on port 80. - -```shell-session -$ kubectl exec -it ${WEB_POD} -c web -- curl api:80 -hello world -``` - - - - -Then, open a shell session in the `web` container and test the `api` service on port 80. - -```shell-session -$ kubectl exec -it ${WEB_POD} -c web -- curl api:80 -hello world -``` - - - - -Apply the CRD to allow traffic to port 90: - -```shell-session -$ kubectl apply -f allow-90.yaml -``` - - - - - -Then, open a shell session in the `web` container and test the `api` service on port 90. - -```shell-session -$ kubectl exec -it ${WEB_POD} -c web -- curl api:90 -hello world from 9090 admin -``` - - - - - -Then, open a shell session in the `web` container and test the `api-admin` service on port 90. - -```shell-session -$ kubectl exec -it ${WEB_POD} -c web -- curl api-admin:90 -hello world from 9090 admin -``` - - - - -## Next steps - -After applying traffic permissions and validating service-to-service communication within your service mesh, you can manage traffic between multi-port services, filter traffic between ports based on L7 header information, or direct match HTTP query parameters to a specific port. - -Refer to the following pages for more information: - -- [Split traffic between services](/consul/docs/k8s/multiport/traffic-split) -- [gRPC route example: route traffic by matching header](/consul/docs/k8s/multiport/reference/httproute#route-traffic-by-matching-header) -- [HTTP route example: route traffic by matching header](/consul/docs/k8s/multiport/reference/httproute#route-traffic-by-matching-header) -- [HTTP route example: route traffic by matching header and query parameter](/consul/docs/k8s/multiport/reference/httproute#route-traffic-by-matching-header-and-query-parameter) diff --git a/website/content/docs/k8s/multiport/index.mdx b/website/content/docs/k8s/multiport/index.mdx deleted file mode 100644 index bc03fcbb05..0000000000 --- a/website/content/docs/k8s/multiport/index.mdx +++ /dev/null @@ -1,69 +0,0 @@ ---- -layout: docs -page_title: Use the v2 Catalog API -description: Consul on Kubernetes supports the Catalog V2 API, which unlocks new service discovery and service mesh workflows. Learn how Consul’s Catalog V2 API supports workloads with multiple ports and selecting a workload using multiple services. ---- - - - -The v2 catalog API and Traffic Permissions API are currently in beta. This documentation supports testing and development scenarios. Do not use these APIs in secure production environments. - - - -# Use the v2 Catalog API - -This topic describes the process to use the v2 catalog API to register a service with multiple ports or select a workload using multiple services on Kubernetes deployments. For information about the v2 catalog’s contents and structure, refer to [v2 catalog architecture](/consul/docs/architecture/v2/catalog). - -## Workflow - -To use a multi-port service in Consul on Kubernetes deployments, complete the following steps: - -1. Enable the v2 catalog with ACLs enabled. Add `global.experiments: ["resource-apis"]`, `ui.enabled: false`, and `manageSystemACLs: true` to a cluster's Helm chart before deploying Consul. -1. Use the `"consul.hashicorp.com/mesh-inject": "true"` and `"consul.hashicorp.com/transparent-proxy": "true"` annotations so that Consul automatically registers the service with transparent proxy mode enabled when Kubernetes deploys containers. -1. Configure traffic permissions. When ACLs are enabled, Consul denies all traffic by default. You can use the `TrafficPermissions` CRD to allow traffic to services. - -For an example configuration and instructions for each of the steps in this workflow, refer to [configure multi-port services](/consul/docs/k8s/multiport/configure). - -### Advanced proxy and route configuration workflow - -You can also configure Envoy proxies and sidecar behavior with the proxy configurations resource, and manage traffic between services at the L4 and L7 networking layers with the TCP, HTTP, and gRPC route resources. After you [configure multi-port services](/consul/docs/k8s/multiport/configure), complete the following steps: - -1. Define the resource's behavior in a custom resource definition (CRD). For specifications and example configurations, refer to the [configuration reference](#reference-documentation) for each resource. -1. Apply the resource to your cluster. - -For an example configuration and instructions for each of the steps in this workflow, refer to [split TCP service traffic between ports](/consul/docs/k8s/multiport/traffic-split). - -## Constraints and limitations - -Be aware of the following constraints and technical limitations on using multi-port services and the v2 catalog API: - -- Multi-port services are available for deployments using [Consul dataplanes](/consul/docs/connect/dataplane) instead of client agents. Consul on Kubernetes uses dataplanes by default. -- When running the v2 catalog for multi-port services, you cannot run the v1 catalog API at the same time. -- Because configuration entries are part of the v1 catalog, you cannot use them when the v2 catalog is enabled. Use v2 resources instead to configure multi-port service behavior in the service mesh. -- The Consul UI does not support multi-port services in this release. You must disable the UI in the Helm chart in order to use multi-port services. -- HCP Consul does not support multi-port services in this release. You cannot [link a self-managed cluster to HCP Consul](/hcp/docs/consul/self-managed) to access its UI or view observability metrics when it uses the v2 catalog. -- We do not recommend updating existing clusters to enable the v2 catalog in this release. To register multi-port services, deploy a new Consul cluster that enables the v2 catalog. - -## Guidance - -The following resources are available to help you use the v2 Catalog API: - -### Architecture - -- [v2 architecture overview](/consul/docs/architecture/v2/catalog) -- [v2 Consul catalog](/consul/docs/architecture/v2/catalog) -- [v2 resource groups](/consul/docs/architecture/v2/catalog) - -### Usage documentation - -- [Configure v2 services with multiple ports](/consul/docs/k8s/multiport/configure) -- [Split TCP traffic between v2 services with multiple ports](/consul/docs/k8s/multiport/traffic-split) - -### Reference documentation - -- [`consul resource` CLI command](/consul/commands/resource) -- [`GRPCRoute` resource configuration reference](/consul/docs/k8s/multiport/reference/grpcroute) -- [`HTTPRoute` resource configuration reference](/consul/docs/k8s/multiport/reference/httproute) -- [`ProxyConfiguration` resource configuration reference](/consul/docs/k8s/multiport/reference/proxyconfiguration) -- [`TCPRoute` resource configuration reference](/consul/docs/k8s/multiport/reference/tcproute) -- [`TrafficPermissions` resource configuration reference](/consul/docs/k8s/multiport/reference/trafficpermissions) diff --git a/website/content/docs/k8s/multiport/reference/grpcroute.mdx b/website/content/docs/k8s/multiport/reference/grpcroute.mdx deleted file mode 100644 index 1a8910c5b7..0000000000 --- a/website/content/docs/k8s/multiport/reference/grpcroute.mdx +++ /dev/null @@ -1,808 +0,0 @@ ---- -layout: docs -page_title: GRPCRoute resource configuration reference -description: The GRPCRoute resource CRD configures L7 gRPC traffic behavior within the service mesh. GRPCRoute requires the v2 catalog API. Learn how to configure the GRPCRoute CRD with specifications and example configurations. ---- - -# GRPCRoute resource configuration reference - -This page provides reference information for the `GRPCRoute` resource, which defines L7 gRPC traffic within the service mesh. - -This custom resource definition (CRD) describes a resource related to the [Kubernetes GAMMA initiative](https://gateway-api.sigs.k8s.io/concepts/gamma/) that requires the [v2 catalog API](/consul/docs/architecture/v2/catalog). It is not compatible with the [v1 catalog API](/consul/docs/architecture/catalog). For more information about GAMMA resources, refer to the [Kubernetes Gateway API documentation](https://gateway-api.sigs.k8s.io/concepts/gamma/). - -## Configuration model - -The following list outlines field hierarchy, language-specific data types, and requirements in a gRPC route CRD. Click on a property name to view additional details, including default values. - -- [`apiVersion`](#apiversion): string | required | must be set to `mesh.consul.hashicorp.com/v2beta1` -- [`kind`](#kind): string | required | must be set to `GRPCRoute` -- [`metadata`](#metadata): map | required - - [`name`](#metadata-name): string | required - - [`namespace`](#metadata-namespace): string | optional -- [`spec`](#spec): map | required - - [`parentRefs`](#spec-parentrefs): map | required - - [`port`](#spec-parentrefs-port): string - - [`ref`](#spec-parentrefs-ref): string | required - - [`name`](#spec-parentrefs-ref-name): string - - [`type`](#spec-parentrefs-ref-type): map - - [`group`](#spec-parentrefs-ref-type): string - - [`groupVersion`](#spec-parentrefs-ref-type): string - - [`kind`](#spec-parentrefs-ref-type): string - - [`rules`](#spec-rules): map | required - - [`backendRefs`](#spec-rules-backendrefs): map - - [`backendRef`](#spec-rules-backendrefs-backendref): map - - [`datacenter`](#spec-rules-backendrefs-backendref-datacenter): string - - [`port`](#spec-rules-backendrefs-backendref-port): string - - [`ref`](#spec-rules-backendrefs-backendref-ref): map - - [`name`](#spec-rules-backendrefs-backendref-ref-name): string - - [`type`](#spec-rules-backendrefs-backendref-ref-type): map - - [`group`](#spec-rules-backendrefs-backendref-ref-type): string - - [`groupVersion`](#spec-rules-backendrefs-backendref-ref-type): string - - [`kind`](#spec-rules-backendrefs-backendref-ref-type): string - - [`filters`](#spec-rules-backendrefs-filters): map - - [`requestHeaderModifier`](#spec-rules-backendrefs-filters-requestheadermodifier): map - - [`add`](#spec-rules-backendrefs-filters-requestheadermodifier): map - - [`set`](#spec-rules-backendrefs-filters-requestheadermodifier): map - - [`remove`](#spec-rules-backendrefs-filters-requestheadermodifier): map - - [`responseHeaderModifier`](#spec-rules-backendrefs-filters-responseheadermodifier): map - - [`add`](#spec-rules-backendrefs-filters-responseheadermodifier): map - - [`set`](#spec-rules-backendrefs-filters-responseheadermodifier): map - - [`remove`](#spec-rules-backendrefs-filters-responseheadermodifier): map - - [`urlRewrite`](#spec-rules-backendrefs-filters-urlrewrite): map - - [`pathPrefix`](#spec-rules-backendrefs-filters-urlrewrite): string - - [`weight`](#spec-rules-backendrefs-weight): number | `1` - - [`filters`](#spec-rules-filters): map - - [`requestHeaderModifier`](#spec-rules-filters-requestheadermodifier): map - - [`add`](#spec-rules-filters-requestheadermodifier): map - - [`set`](#spec-rules-filters-requestheadermodifier): map - - [`remove`](#spec-rules-filters-requestheadermodifier): map - - [`responseHeaderModifier`](#spec-rules-filters-responseheadermodifier): map - - [`add`](#spec-rules-filters-responseheadermodifier): map - - [`set`](#spec-rules-filters-responseheadermodifier): map - - [`remove`](#spec-rules-filters-responseheadermodifier): map - - [`urlRewrite`](#spec-rules-filters-urlrewrite): map - - [`pathPrefix`](#spec-rules-filters-urlrewrite): string - - [`matches`](#spec-rules-matches): map - - [`headers`](#spec-rules-matches-headers): map - - [`name`](#spec-rules-matches-headers-name): string - - [`type`](#spec-rules-matches-headers-type): string - - [`value`](#spec-rules-matches-headers-value): string - - [`method`](#spec-rules-matches-method): map - - [`method`](#spec-rules-matches-method-method): string - - [`service`](#spec-rules-matches-method-service): string - - [`type`](#spec-rules-matches-method-type): string - - [`retries`](#spec-rules-retries): map - - [`number`](#spec-rules-retries-number): map - - [`value`](#spec-rules-retries-number): number - - [`onConditions`](#spec-rules-retries-onconditions): map of strings - - [`onConnectFailure`](#spec-rules-retries-onconnectfailure): boolean | `false` - - [`onStatusCodes`](#spec-rules-retries-onconditions): map of numbers - - [`timeouts`](#spec-rules-timeouts): map - - [`idle`](#spec-rules-timeouts-idle): string - - [`request`](#spec-rules-timeouts-request): string - -## Complete configuration - -When every field is defined, a gRPC route resource CRD has the following form: - -```yaml -apiVersion: mesh.consul.hashicorp.com/v2beta1 # required -kind: GRPCRoute # required -metadata: - name: - namespace: -spec: - parentRefs: - port: - - ref: - name: - type: - group: - groupVersion: - kind: - rules: - - backendRefs: - - backendRef: - datacenter: - port: "" - ref: - name: - type: - group: - groupVersion: - kind: - filters: - - requestHeaderModifier: - add: - name: - value: - - responseHeaderModifier: - set: - name: - value: - urlRewrite: - pathPrefix: - weight: 1 - filters: - requestHeaderModifier: - remove: - name: - value: - responseHeaderModifier: - add: - name: - value: - urlRewrite: - pathPrefix: - matches: - headers: - name: - type: - value: - method: - method: - service: - type: - retries: - number: - value: 1 - onConditions: ["cancelled", "unavailable"] - onConnectFailure: false - onStatusCodes: [400, 404] - timeouts: - idle: <1s> - request: <1s> -``` - -## Specification - -This section provides details about the fields you can configure in the `GRPCRoute` custom resource definition (CRD). - -### `apiVersion` - -Specifies the version of the Consul API for integrating with Kubernetes. The value must be `mesh.consul.hashicorp.com/v2beta1`. - -#### Values - -- Default: None -- This field is required. -- String value that must be set to `mesh.consul.hashicorp.com/v2beta1`. - -### `kind` - -Specifies the type of CRD to implement. Must be set to `GRPCRoute`. - -#### Values - -- Default: None -- This field is required. -- Data type: String value that must be set to `GRPCRoute`. - -### `metadata` - -Map that contains an arbitrary name for the CRD and the namespace it applies to. - -#### Values - -- Default: None -- Data type: Map - -### `metadata.name` - -Specifies a name for the CRD. The name is metadata that you can use to reference the resource when performing Consul operations, such as using the `consul resource` command. Refer to [`consul resource`](/consul/docs/k8s/connect/multiport/reference/resource-command) for more information. - -#### Values - -- Default: None -- This field is required. -- Data type: String - -### `metadata.namespace` - -Specifies the namespace that the service resolver applies to. Refer to [namespaces](/consul/docs/enterprise/namespaces) for more information. - -#### Values - -- Default: None -- Data type: String - -### `spec` - -Map that contains the details about the `GRPCRoute` CRD. The `apiVersion`, `kind`, and `metadata` fields are siblings of the spec field. All other configurations are children. - -When using this CRD, the `spec` field closely resembles the `GRPCRoute` GAMMA resource. Refer to [GRPCRoute in the Kubernetes documentation](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.GRPCRoute). - -#### Values - -- Default: None -- This field is required. -- Data type: Map - -### `spec.parentRefs` - -Specifies the services and other resources to attach the route to. You can only define one `parentsRefs` configuration for each route. To attach the route to multiple resources, specify additional [`spec.parentRefs.ref`](#spec-parentrefs-ref) configurations in the `parentsRefs` block. You can only specify the name of one port for the route. Any resources that send traffic through the route use the same port. - -#### Values - -- Default: None -- This field is required. -- Data type: Map - -### `spec.parentRefs.port` - -Specifies the port for the Consul service that the configuration applies to. This parameter can be either a virtual port number, such as a Kubernetes service port, or a non-numeric target port value representing a named workload port. - -If you are not targeting a virtual port, specify the workload port name directly. - -#### Values - -- Default: None -- Data type: String - -### `spec.parentRefs.ref` - -Specifies the resource that the route attaches to. - -#### Values - -- Default: None -- Data type: Map - -### `spec.parentRefs.ref.name` - -Specifies the user-defined name of the resource that the configuration applies to. - -#### Values - -- Default: None -- Data type: String - -### `spec.parentRefs.ref.type` - -Specifies the type of resource that the configuration applies to. To reference a service in the Consul catalog, configure the resource type as `catalog.v2beta1.Service`. - -#### Values - -- Default: None -- Data type: Map containing the following parameters: - - | Parameter | Description | Data type | Default | - | :------------ | :------------------------------------------------------------------- | :-------- | :------- | - | `group` | Specifies a group for the resource type within the Kubernetes cluster. To reference a service in the Consul catalog, set this parameter to `catalog`. | String | None | - | `groupVersion` | Specifies a groupVersion for the resource type within the Kubenretes cluster. To reference a service in the Consul catalog, set this parameter to `v2beta1`. | String | None | - | `kind` | Specifies the kind of the Kubernetes object the resource applies to. To reference a service in the Consul catalog, set this parameter to `Service`. | String | None | - -### `spec.rules` - -Specifies rules for sidecar proxies to direct a service's gRPC traffic within the service mesh, including filtering, retry, and timeout behavior. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.backendRefs` - -Specifies the Kubernetes Service backend to direct GRPC traffic to when a request matches the service described in [`spec.parentRefs`](#spec-parentrefs). The Service backend is the collection of endpoint IPs for the service. Refer to [the Kubernetes Gateway API specification](https://gateway-api.sigs.k8s.io/concepts/gamma/#an-overview-of-the-gateway-api-for-service-mesh) for more information about Service backends. - -When a valid Service backend cannot be reached and no additional filters apply, traffic that matches the rule returns a 500 status code. - -When different Service backends are specified in [`spec.rules.backendRefs.weight`](#spec-rules-backendrefs-weight) and one of the backends is invalid, Consul continues to apply the specified weights instead of adjusting the relative weights to exclude traffic to the invalid backend. For example, when traffic is configured in a 50-50 split between `api` and `admin` and no valid endpoints for `admin` can be reached, the 50% of traffic intended for `admin` returns with a 500 status code. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.backendRefs.backendRef` - -Specifies an individual Service backend where matching requests should be sent. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.backendRefs.backendRef.datacenter` - -Specifies the name of the Consul datacenter that registered the Service backend that the configuration routes traffic to. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.backendRefs.backendRef.port` - -Specifies the port for the Consul service that the configuration routes traffic to. This parameter can be either a virtual port number, such as a Kubernetes service port, or a non-numeric target port value representing a named workload port. - -If you are not targeting a virtual port, specify the workload port name directly. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.backendRefs.backendRef.ref` - -The Consul service that the configuration routes traffic to. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.backendRefs.backendRef.ref.name` - -Specifies the user-defined name of the resource that the configuration routes traffic to. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.backendRefs.backendRef.ref.type` - -Specifies the type of resource that the configuration routes traffic to. To reference a service in the Consul catalog, configure the resource type as `catalog.v2beta1.Service`. - -#### Values - -- Default: None -- Data type: Map containing the following parameters: - - | Parameter | Description | Data type | Default | - | --- | --- | --- | --- | - | `group` | Specifies a group for the resource type within the Kubernetes cluster. To reference a service in the Consul catalog, set this parameter to `catalog`. | String | None | - | `groupVersion` | Specifies a groupVersion for the resource type within the Kubenretes cluster. To reference a service in the Consul catalog, set this parameter to `v2beta1`. | String | None | - | `kind` | Specifies the kind of the Kubernetes object for the resource. To reference a service in the Consul catalog, set this parameter to `Service`. | String | None | - -### `spec.rules.backendRefs.filters` - -Specifies filtering behavior for services configured in the same [`spec.rules.backendRefs`](#spec-rules-backendrefs) block. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.backendRefs.filters.requestHeaderModifier` - -Specifies a set of header modification rules applied to requests routed with the gRPC route resource. - -#### Values - -- Default: None -- Values: Object containing one or more fields that define header modification rules: - - `add`: Map of one or more key-value pairs. - - `set`: Map of one or more key-value pairs. - - `remove`: Map of one or more key-value pairs. - -The following table describes how to configure values for request headers: - -| Rule | Description | Type | -| --- | --- | --- | -| `add` | Defines a set of key-value pairs to add to the header. Use header names as the keys. Header names are not case-sensitive. If header values with the same name already exist, the value is appended and Consul applies both headers. You can [use variable placeholders](#use-variable-placeholders). | Map of strings | -| `set` | Defines a set of key-value pairs to add to the request header or to replace existing header values with. Use header names as the keys. Header names are not case-sensitive. If header values with the same names already exist, Consul replaces the header values. You can [use variable placeholders](#use-variable-placeholders). | Map of strings | -| `remove` | Defines a list of headers to remove. Consul removes only headers containing exact matches. Header names are not case-sensitive. | List of strings | - -##### Use variable placeholders - -For `add` and `set`, if the service is configured to use Envoy as the proxy, the value may contain variables to interpolate dynamic metadata into the value. For example, using the variable `%DOWNSTREAM_REMOTE_ADDRESS%` allows you to pass a value that is generated when the split occurs. - -### `spec.rules.backendRefs.filters.responseHeaderModifier` - -Specifies a set of header modification rules applied to responses routed with the gRPC route resource. - -#### Values - -- Default: None -- Values: Object containing one or more fields that define header modification rules: - - `add`: Map of one or more key-value pairs. - - `set`: Map of one or more key-value pairs. - - `remove`: Map of one or more key-value pairs. - -The following table describes how to configure values for request headers: - -| Rule | Description | Type | -| --- | --- | --- | -| `add` | Defines a set of key-value pairs to add to the header. Use header names as the keys. Header names are not case-sensitive. If header values with the same name already exist, the value is appended and Consul applies both headers. You can [use variable placeholders](#use-variable-placeholders). | Map of strings | -| `set` | Defines a set of key-value pairs to add to the request header or to replace existing header values with. Use header names as the keys. Header names are not case-sensitive. If header values with the same names already exist, Consul replaces the header values. You can [use variable placeholders](#use-variable-placeholders). | Map of strings | -| `remove` | Defines a list of headers to remove. Consul removes only headers containing exact matches. Header names are not case-sensitive. | List of strings | - -##### Use variable placeholders - -For `add` and `set`, if the service is configured to use Envoy as the proxy, the value may contain variables to interpolate dynamic metadata into the value. For example, using the variable `%DOWNSTREAM_REMOTE_ADDRESS%` allows you to pass a value that is generated when the split occurs. - -### `spec.rules.backendRefs.filters.urlRewrite` - -Specifies a path to modify the URL with when a request is forwarded. - -#### Values - -- Default: None -- Data type: Map containing the following parameter: - - | Parameter | Description | Data type | Default | - | :------------ | :------------------------------------------------------------------------------------------------- | --------- | ------- | - | `pathPrefix` | Specifies the path that is prepended to the URL. | String | None | - -### `spec.rules.backendRefs.weight` - -Specifies the proportion of requests routed to the specified service. - -This proportion is relative to the sum of all weights in the [`spec.rules.backendRefs`](#spec-rules-backendrefs) block. As a result, weights do not need to add up to 100. When only one backend is specified and the weight is greater then 0, Consul forwards 100% of traffic to the backend. - -When this parameter is not specified, Consul defaults to `1`. - -#### Values - -- Default: `1` -- Data type: Integer - -### `spec.rules.filters` - -Specifies filtering behavior for all requests that match the service defined in [`spec.parentRefs`](#spec-parent-refs). - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.filters.requestHeaderModifier` - -Specifies a set of header modification rules applied to requests that match the service defined in [`spec.parentRefs`](#spec-parent-refs). - -#### Values - -- Default: None -- Values: Object containing one or more fields that define header modification rules: - - `add`: Map of one or more key-value pairs. - - `set`: Map of one or more key-value pairs. - - `remove`: Map of one or more key-value pairs. - -The following table describes how to configure values for request headers: - -| Rule | Description | Type | -| --- | --- | --- | -| `add` | Defines a set of key-value pairs to add to the header. Use header names as the keys. Header names are not case-sensitive. If header values with the same name already exist, the value is appended and Consul applies both headers. You can [use variable placeholders](#use-variable-placeholders). | Map of strings | -| `set` | Defines a set of key-value pairs to add to the request header or to replace existing header values with. Use header names as the keys. Header names are not case-sensitive. If header values with the same names already exist, Consul replaces the header values. You can [use variable placeholders](#use-variable-placeholders). | Map of strings | -| `remove` | Defines a list of headers to remove. Consul removes only headers containing exact matches. Header names are not case-sensitive. | List of strings | - -##### Use variable placeholders - -For `add` and `set`, if the service is configured to use Envoy as the proxy, the value may contain variables to interpolate dynamic metadata into the value. For example, using the variable `%DOWNSTREAM_REMOTE_ADDRESS%` allows you to pass a value that is generated when the split occurs. - -### `spec.rules.filters.responseHeaderModifier` - -Specifies a set of header modification rules applied to responses from services matching [`spec.parentRefs`](#spec-parent-refs). - -#### Values - -- Default: None -- Values: Object containing one or more fields that define header modification rules: - - `add`: Map of one or more key-value pairs. - - `set`: Map of one or more key-value pairs. - - `remove`: Map of one or more key-value pairs. - -The following table describes how to configure values for request headers: - -| Rule | Description | Type | -| --- | --- | --- | -| `add` | Defines a set of key-value pairs to add to the header. Use header names as the keys. Header names are not case-sensitive. If header values with the same name already exist, the value is appended and Consul applies both headers. You can [use variable placeholders](#use-variable-placeholders). | Map of strings | -| `set` | Defines a set of key-value pairs to add to the request header or to replace existing header values with. Use header names as the keys. Header names are not case-sensitive. If header values with the same names already exist, Consul replaces the header values. You can [use variable placeholders](#use-variable-placeholders). | Map of strings | -| `remove` | Defines a list of headers to remove. Consul removes only headers containing exact matches. Header names are not case-sensitive. | List of strings | - -##### Use variable placeholders - -For `add` and `set`, if the service is configured to use Envoy as the proxy, the value may contain variables to interpolate dynamic metadata into the value. For example, using the variable `%DOWNSTREAM_REMOTE_ADDRESS%` allows you to pass a value that is generated when the split occurs. - -### `spec.rules.filters.urlRewrite` - -Specifies a path to modify the URL with when a request matching [`spec.parentRefs`](#spec-parent-refs) is forwarded. - -#### Values - -- Default: None -- Data type: Map containing the following parameter: - - | Parameter | Description | Data type | Default | - | :------------ | :------------------------------------------------------------------------------------------------- | --------- | ------- | - | `pathPrefix` | Specifies a path to prepend to the URL when a request matching [`spec.parentRefs`](#spec-parent-refs) is forwarded. | String | None | - -### `spec.rules.matches` - -Specifies rules for matching traffic to services described in [`spec.parentRefs`](#spec-parent-refs) according to the request header or method. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.matches.headers` - -Specifies criteria for matching gRPC request headers. - -When [`spec.rules.matches.headers.value`] is specified multiple times, a request must match all of the specified values for the route to be selected. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.matches.headers.name` - -Specifies the name of a gRPC request header to match on. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.matches.headers.type` - -Specifies a type of match to perform on the gRPC request header. Supported match types include: unspecified, exact, regex, present, prefix, and suffix. - -#### Values - -- Default: None -- Data type: String that should match one of the following values: - - - `HEADER_MATCH_TYPE_UNSPECIFIED` - - `HEADER_MATCH_TYPE_EXACT` - - `HEADER_MATCH_TYPE_REGEX` - - `HEADER_MATCH_TYPE_PRESENT` - - `HEADER_MATCH_TYPE_PREFIX` - - `HEADER_MATCH_TYPE_SUFFIX` - -### `spec.rules.matches.headers.value` - -Specifies the value of the gRPC request header to match. When this field is specified multiple times, a request must match all of the specified values for the route to be selected. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.matches.method` - -Specifies criteria for matching a service and a gRPC request method. When configuring this field, either [`spec.rules.matches.method.method`](#spec-rules-matches-method-method) or [`spec.rules.matches.method.service`](#spec-rules-matches-method-service) must be a non-empty string. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.matches.method.method` - -Specifies the value of the method to match. When empty or omitted, all methods match. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.matches.method.service` - -Specifies the value of the service to match. When empty or omitted, all services match. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.matches.method.type` - -Specifies a type of match to perform on the gRPC request method. Supported match types include: unspecified, exact, and regex. - -#### Values - -- Default: None -- Data type: String that should match one of the following values: - - - `GRPC_METHOD_MATCH_TYPE_UNSPECIFIED` - - `GRPC_METHOD_MATCH_TYPE_EXACT` - - `GRPC_METHOD_MATCH_TYPE_REGEX` - -### `spec.rules.retries` - -Specifies retry logic for routing gRPC traffic. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.retries.number` - -Specifies the number of retries to attempt when a request fails. - -#### Values - -- Default: None -- Data type: Map that contains the following parameter: - - | Parameter | Description | Data type | Default | - | :-------- | :------------------------------------------- | --------- | ------- | - | `value` | Specifies the number of retries to attempt. | Integer | None | - -### `spec.rules.retries.onConditions` - -Specifies Envoy conditions that cause an automatic retry attempt. - -#### Values - -- Default: None -- Data type: Map of strings - -### `spec.rules.retries.onConnnectFailure` - -Enables an automatic retry attempt when a connection failure error occurs. - -#### Values - -- Default: `false` -- Data type: Boolean - -### `spec.rules.retries.onStatusCodes` - -Specifies the response status codes that are eligible for retry attempts. - -#### Values - -- Default: None -- Data type: Map of integers - -### `spec.rules.timeouts` - -Specifies timeout logic when routing gRPC traffic - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.timeouts.idle` - -Specifies the total amount of time permitted for the request stream to be idle before a timeout occurs. - -This field accepts a string indicating the number of seconds. For example, indicate five seconds with `5s` and five milliseconds with `0.005s`. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.timeouts.request` - -Specifies the total amount of time spent processing the entire downstream request, including retries, before triggering a timeout. - -This field accepts a string indicating the number of seconds. For example, indicate five seconds with `5s` and five milliseconds with `0.005s`. - -#### Values - -- Default: None -- Data type: String - -## Examples - -The following examples demonstrate common GRPCRoute CRD configuration patterns for specific use cases. - -### Split gRPC traffic between two ports - -The following example splits traffic for the `api` service. GRPC traffic for services registered to the Consul catalog that are available at the `api-workload` port is split so that 50% of the traffic routes to the service at the `api-workload` port and 50% routes to the service at the `admin-workload` port. - -```yaml -apiVersion: mesh.consul.hashicorp.com/v2beta1 -kind: GRPCRoute -metadata: - name: api-split - namespace: default -spec: - parentRefs: - - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api - # The virtual port number for the "api-workload" target port. - port: "80" - rules: - - backendRefs: - - backendRef: - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api - # The virtual port number for the "api-workload" target port. - port: "80" - weight: 50 - - backendRef: - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api - # The virtual port number for the "admin-workload" target port. - port: "90" - weight: 50 -``` - -### Route traffic by matching header - -The following example routes gRPC traffic for the `api` service according to its header. GRPC traffic for services registered to the Consul catalog that are available at the `api-workload` port are routed according to the following criteria: - -- For traffic with a header that contains a `x-debug` value of exactly `1`, Consul modifies the response and request headers and routes to the `api` service at the `api-workload` port. -- For traffic with a header that contains a `x-debug` value of exactly `2`, Consul modifies the response and request headers and routes to the `api-admin` service at the `admin-workload` port. - -This example also includes how to include filters that modify request or response headers. - -```yaml -apiVersion: mesh.consul.hashicorp.com/v2beta1 -kind: GRPCRoute -metadata: - name: api-match-split - namespace: default -spec: - parentRefs: - - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api - # The virtual port number for the "api-workload" target port. - port: "80" - rules: - - matches: - - headers: - - type: "HEADER_MATCH_TYPE_EXACT" - name: "x-debug" - value: "1" - filters: - - requestHeaderModifier: - add: - - name: "request-foo" - value: "request-bar" - - responseHeaderModifier: - add: - - name: "response-foo" - value: "response-bar" - backendRefs: - - backendRef: - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api - # The virtual port number for the "api-workload" target port. - port: "80" - - matches: - - headers: - - type: "HEADER_MATCH_TYPE_EXACT" - name: "x-debug" - value: "2" - filters: - - requestHeaderModifier: - add: - - name: "request-foo" - value: "request-bar" - - responseHeaderModifier: - add: - - name: "response-foo" - value: "response-bar" - backendRefs: - - backendRef: - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api-admin - # The virtual port number for the "admin-workload" target port. - port: "90" -``` \ No newline at end of file diff --git a/website/content/docs/k8s/multiport/reference/httproute.mdx b/website/content/docs/k8s/multiport/reference/httproute.mdx deleted file mode 100644 index f739ecba84..0000000000 --- a/website/content/docs/k8s/multiport/reference/httproute.mdx +++ /dev/null @@ -1,925 +0,0 @@ ---- -layout: docs -page_title: HTTPRoute resource configuration reference -description: The HTTPRoute resource CRD configures L7 HTTP traffic behavior within the service mesh. HTTPRoute is a GAMMA resource that requires the v2 catalog API. Learn how to configure the HTTPRoute CRD with specifications and example configurations. ---- - -# HTTPRoute resource configuration reference - -This page provides reference information for the `HTTPRoute` resource, which defines behavior for L7 HTTP traffic within the service mesh. - -This custom resource definition (CRD) describes a resource related to the [Kubernetes GAMMA initiative](https://gateway-api.sigs.k8s.io/concepts/gamma/) that requires the [v2 catalog API](/consul/docs/architecture/v2/catalog). It is not compatible with the [v1 catalog API](/consul/docs/architecture/catalog). For more information about GAMMA resources, refer to the [Kubernetes Gateway API documentation](https://gateway-api.sigs.k8s.io/concepts/gamma/). - -## Configuration model - -The following list outlines field hierarchy, language-specific data types, and requirements in an HTTP route CRD. Click on a property name to view additional details, including default values. - -- [`apiVersion`](#apiversion): string | required | must be set to `mesh.consul.hashicorp.com/v2beta1` -- [`kind`](#kind): string | required | must be set to `HTTPRoute` -- [`metadata`](#metadata): map | required - - [`name`](#metadata-name): string | required - - [`namespace`](#metadata-namespace): string | optional -- [`spec`](#spec): map | required - - [`parentRefs`](#spec-parentrefs): map | required - - [`port`](#spec-parentrefs-port): string - - [`ref`](#spec-parentrefs-ref): string | required - - [`name`](#spec-parentrefs-ref-name): string - - [`type`](#spec-parentrefs-ref-type): map - - [`group`](#spec-parentrefs-ref-type): string - - [`groupVersion`](#spec-parentrefs-ref-type): string - - [`kind`](#spec-parentrefs-ref-type): string - - [`rules`](#spec-rules): map | required - - [`backendRefs`](#spec-rules-backendrefs): map - - [`backendRef`](#spec-rules-backendrefs-backendref): map - - [`datacenter`](#spec-rules-backendrefs-backendref-datacenter): string - - [`port`](#spec-rules-backendrefs-backendref-port): string - - [`ref`](#spec-rules-backendrefs-backendref-ref): map - - [`name`](#spec-rules-backendrefs-backendref-ref-name): string - - [`type`](#spec-rules-backendrefs-backendref-ref-type): map - - [`group`](#spec-rules-backendrefs-backendref-ref-type): string - - [`groupVersion`](#spec-rules-backendrefs-backendref-ref-type): string - - [`kind`](#spec-rules-backendrefs-backendref-ref-type): string - - [`filters`](#spec-rules-backendrefs-filters): map - - [`requestHeaderModifier`](#spec-rules-backendrefs-filters-requestheadermodifier): map - - [`add`](#spec-rules-backendrefs-filters-requestheadermodifier): map - - [`set`](#spec-rules-backendrefs-filters-requestheadermodifier): map - - [`remove`](#spec-rules-backendrefs-filters-requestheadermodifier): map - - [`responseHeaderModifier`](#spec-rules-backendrefs-filters-responseheadermodifier): map - - [`add`](#spec-rules-backendrefs-filters-responseheadermodifier): map - - [`set`](#spec-rules-backendrefs-filters-responseheadermodifier): map - - [`remove`](#spec-rules-backendrefs-filters-responseheadermodifier): map - - [`urlRewrite`](#spec-rules-backendrefs-filters-urlrewrite): map - - [`pathPrefix`](#spec-rules-backendrefs-filters-urlrewrite): string - - [`weight`](#spec-rules-backendrefs-weight): number | `1` - - [`filters`](#spec-rules-filters): map - - [`requestHeaderModifier`](#spec-rules-filters-requestheadermodifier): map - - [`add`](#spec-rules-filters-requestheadermodifier): map - - [`set`](#spec-rules-filters-requestheadermodifier): map - - [`remove`](#spec-rules-filters-requestheadermodifier): map - - [`responseHeaderModifier`](#spec-rules-filters-responseheadermodifier): map - - [`add`](#spec-rules-filters-responseheadermodifier): map - - [`set`](#spec-rules-filters-responseheadermodifier): map - - [`remove`](#spec-rules-filters-responseheadermodifier): map - - [`urlRewrite`](#spec-rules-filters-urlrewrite): map - - [`pathPrefix`](#spec-rules-filters-urlrewrite): string - - [`matches`](#spec-rules-matches): map - - [`headers`](#spec-rules-matches-headers): map - - [`name`](#spec-rules-matches-headers-name): string - - [`type`](#spec-rules-matches-headers-type): string - - [`value`](#spec-rules-matches-headers-value): string - - [`method`](#spec-rules-matches-method): string - - [`path`](#spec-rules-matches-path): map - - [`type`](#spec-rules-matches-path-type): string - - [`value`](#spec-rules-matches-path-value): string - - [`queryParams`](#spec-rules-matches-queryparams): map - - [`name`](#spec-rules-matches-queryparams-name): string - - [`type`](#spec-rules-matches-queryparams-type): string - - [`value`](#spec-rules-matches-queryparams-value): string - - [`retries`](#spec-rules-retries): map - - [`number`](#spec-rules-retries-number): map - - [`value`](#spec-rules-retries-number): number - - [`onConditions`](#spec-rules-retries-onconditions): map of strings - - [`onConnectFailure`](#spec-rules-retries-onconnectfailure): boolean | `false` - - [`onStatusCodes`](#spec-rules-retries-onconditions): map of integers - - [`timeouts`](#spec-rules-timeouts): map - - [`idle`](#spec-rules-timeouts-idle): string - - [`request`](#spec-rules-timeouts-request): string - -## Complete configuration - -When every field is defined, an HTTP route CRD has the following form: - -```yaml -apiVersion: mesh.consul.hashicorp.com/v2beta1 # required -kind: HTTPRoute # required -metadata: - name: - namespace: -spec: - parentRefs: - port: - - ref: - name: - type: - group: - groupVersion: - kind: - rules: - - backendRefs: - - backendRef: - datacenter: - port: - ref: - name: - type: - group: - groupVersion: - kind: - filters: - - requestHeaderModifier: - add: - name: - value: - - responseHeaderModifier: - set: - name: - value: - urlRewrite: - pathPrefix: - weight: 1 - filters: - requestHeaderModifier: - remove: - name: - value: - responseHeaderModifier: - add: - name: - value: - urlRewrite: - pathPrefix: - matches: - headers: - name: - type: - value: - method: - path: - type: - value: / - queryParams: - name: - type: - value: - retries: - number: - value: 1 - onConditions: ["cancelled", "unavailable"] - onConnectFailure: false - onStatusCodes: [400, 404] - timeouts: - idle: <1s> - request: <1s> -``` - -## Specification - -This section provides details about the fields you can configure in the `HTTPRoute` custom resource definition (CRD). - -### `apiVersion` - -Specifies the version of the Consul API for integrating with Kubernetes. The value must be `mesh.consul.hashicorp.com/v2beta1`. - -#### Values - -- Default: None -- This field is required. -- String value that must be set to `mesh.consul.hashicorp.com/v2beta1`. - -### `kind` - -Specifies the type of CRD to implement. Must be set to `HTTPRoute`. - -#### Values - -- Default: None -- This field is required. -- Data type: String value that must be set to `HTTPRoute`. - -### `metadata` - -Map that contains an arbitrary name for the CRD and the namespace it applies to. - -#### Values - -- Default: None -- Data type: Map - -### `metadata.name` - -Specifies a name for the CRD. The name is metadata that you can use to reference the resource when performing Consul operations, such as using the `consul resource` command. Refer to [`consul resource`](/consul/docs/k8s/connect/multiport/reference/resource-command) for more information. - -#### Values - -- Default: None -- This field is required. -- Data type: String - -### `metadata.namespace` - -Specifies the namespace that the service resolver applies to. Refer to [namespaces](/consul/docs/enterprise/namespaces) for more information. - -#### Values - -- Default: None -- Data type: String - -### `spec` - -Map that contains the details about the `HTTPRoute` CRD. The `apiVersion`, `kind`, and `metadata` fields are siblings of the spec field. All other configurations are children. - -When using this CRD, the `spec` field closely resembles the `HTTPRoute` GAMMA resource. Refer to [HTTPRoute in the Kubernetes documentation](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.HTTPRoute). - -#### Values - -- Default: None -- This field is required. -- Data type: Map - -### `spec.parentRefs` - -Specifies the services and other resources to attach the route to. You can only define one `parentsRefs` configuration for each route. To attach the route to multiple resources, specify additional [`spec.parentRefs.ref`](#spec-parentrefs-ref) configurations in the `parentsRefs` block. You can only specify the name of one port for the route. Any resources that send traffic through the route use the same port. - -#### Values - -- Default: None -- This field is required. -- Data type: Map - -### `spec.parentRefs.port` - -Specifies the port for the Consul service that the configuration applies to. This parameter can be either a virtual port number, such as a Kubernetes service port, or a non-numeric target port value representing a named workload port. - -If you are not targeting a virtual port, specify the workload port name directly. - -#### Values - -- Default: None -- Data type: String - -### `spec.parentRefs.ref` - -Specifies the resource that the route attaches to. - -#### Values - -- Default: None -- Data type: Map - -### `spec.parentRefs.ref.name` - -Specifies the user-defined name of the resource that the configuration applies to. - -#### Values - -- Default: None -- Data type: String - -### `spec.parentRefs.ref.type` - -Specifies the type of resource that the configuration applies to. To reference a service in the Consul catalog, configure the resource type as `catalog.v2beta1.Service`. - -#### Values - -- Default: None -- Data type: Map containing the following parameters: - - | Parameter | Description | Data type | Default | - | :------------ | :------------------------------------------------------------------- | :-------- | :------- | - | `group` | Specifies a group for the resource type within the Kubernetes cluster. To reference a service in the Consul catalog, set this parameter to `catalog`. | String | None | - | `groupVersion` | Specifies a groupVersion for the resource type within the Kubenretes cluster. To reference a service in the Consul catalog, set this parameter to `v2beta1`. | String | None | - | `kind` | Specifies the kind of the Kubernetes object the resource applies to. To reference a service in the Consul catalog, set this parameter to `Service`. | String | None | - -### `spec.rules` - -Specifies rules for sidecar proxies to direct a service's HTTP traffic within the service mesh, including filtering, retry, and timeout behavior. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.backendRefs` - -Specifies the Kubernetes Service backend to direct HTTP traffic to when a request matches the service described in [`spec.parentRefs`](#spec-parentrefs). The Service backend is the collection of endpoint IPs for the service. Refer to [the Kubernetes Gateway API specification](https://gateway-api.sigs.k8s.io/concepts/gamma/#an-overview-of-the-gateway-api-for-service-mesh) for more information about Service backends. - -When a valid Service backend cannot be reached and no additional filters apply, traffic that matches the rule returns a 500 status code. - -When different Service backends are specified in [`spec.rules.backendRefs.weight`](#spec-rules-backendrefs-weight) and one of the backends is invalid, Consul continues to apply the specified weights instead of adjusting the relative weights to exclude traffic to the invalid backend. For example, when traffic is configured in a 50-50 split between `api` and `admin` and no valid endpoints for `admin` can be reached, the 50% of traffic intended for `admin` returns with a 500 status code. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.backendRefs.backendRef` - -Specifies an individual Service backend where matching requests should be sent. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.backendRefs.backendRef.datacenter` - -Specifies the name of the Consul datacenter that registered the Service backend that the configuration routes traffic to. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.backendRefs.backendRef.port` - -Specifies the name of the port for the Consul service that the configuration routes traffic to. This parameter can be either a virtual port number, such as a Kubernetes service port, or a non-numeric target port value representing a named workload port. - -If you are not targeting a virtual port, specify the workload port name directly. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.backendRefs.backendRef.ref` - -The Consul service that the configuration routes traffic to. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.backendRefs.backendRef.ref.name` - -Specifies the user-defined name of the resource that the configuration routes traffic to. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.backendRefs.backendRef.ref.type` - -Specifies the type of resource that the configuration routes traffic to. To reference a service in the Consul catalog, configure the resource type as `catalog.v2beta1.Service`. - -#### Values - -- Default: None -- Data type: Map containing the following parameters: - - | Parameter | Description | Data type | Default | - | `group` | Specifies a group for the resource type within the Kubernetes cluster. To reference a service in the Consul catalog, set this parameter to `catalog`. | String | None | - | `groupVersion` | Specifies a groupVersion for the resource type within the Kubenretes cluster. To reference a service in the Consul catalog, set this parameter to `v2beta1`. | String | None | - | `kind` | Specifies the kind of the Kubernetes object for the resource. To reference a service in the Consul catalog, set this parameter to `Service`. | String | None | - -### `spec.rules.backendRefs.filters` - -Specifies filtering behavior for services configured in the same [`spec.rules.backendRefs`](#spec-rules-backendrefs) block. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.backendRefs.filters.requestHeaderModifier` - -Specifies a set of header modification rules applied to requests routed with the HTTPRoute resource. - -#### Values - -- Default: None -- Values: Object containing one or more fields that define header modification rules: - - `add`: Map of one or more key-value pairs. - - `set`: Map of one or more key-value pairs. - - `remove`: Map of one or more key-value pairs. - -The following table describes how to configure values for request headers: - -| Rule | Description | Type | -| --- | --- | --- | -| `add` | Defines a set of key-value pairs to add to the header. Use header names as the keys. Header names are not case-sensitive. If header values with the same name already exist, the value is appended and Consul applies both headers. You can [use variable placeholders](#use-variable-placeholders). | map of strings | -| `set` | Defines a set of key-value pairs to add to the request header or to replace existing header values with. Use header names as the keys. Header names are not case-sensitive. If header values with the same names already exist, Consul replaces the header values. You can [use variable placeholders](#use-variable-placeholders). | map of strings | -| `remove` | Defines a list of headers to remove. Consul removes only headers containing exact matches. Header names are not case-sensitive. | list of strings | - -##### Use variable placeholders - -For `add` and `set`, if the service is configured to use Envoy as the proxy, the value may contain variables to interpolate dynamic metadata into the value. For example, using the variable `%DOWNSTREAM_REMOTE_ADDRESS%` allows you to pass a value that is generated when the split occurs. - -### `spec.rules.backendRefs.filters.responseHeaderModifier` - -Specifies a set of header modification rules applied to responses routed with the HTTPRoute resource. - -#### Values - -- Default: None -- Values: Object containing one or more fields that define header modification rules: - - `add`: Map of one or more key-value pairs. - - `set`: Map of one or more key-value pairs. - - `remove`: Map of one or more key-value pairs. - -The following table describes how to configure values for request headers: - -| Rule | Description | Type | -| --- | --- | --- | -| `add` | Defines a set of key-value pairs to add to the header. Use header names as the keys. Header names are not case-sensitive. If header values with the same name already exist, the value is appended and Consul applies both headers. You can [use variable placeholders](#use-variable-placeholders). | map of strings | -| `set` | Defines a set of key-value pairs to add to the request header or to replace existing header values with. Use header names as the keys. Header names are not case-sensitive. If header values with the same names already exist, Consul replaces the header values. You can [use variable placeholders](#use-variable-placeholders). | map of strings | -| `remove` | Defines a list of headers to remove. Consul removes only headers containing exact matches. Header names are not case-sensitive. | list of strings | - -##### Use variable placeholders - -For `add` and `set`, if the service is configured to use Envoy as the proxy, the value may contain variables to interpolate dynamic metadata into the value. For example, using the variable `%DOWNSTREAM_REMOTE_ADDRESS%` allows you to pass a value that is generated when the split occurs. - -### `spec.rules.backendRefs.filters.urlRewrite` - -Specifies a path to modify the URL with when a request is forwarded. - -#### Values - -- Default: None -- Data type: Map containing the following parameter: - - | Parameter | Description | Data type | Default | - | :------------ | :------------------------------------------------------------------------------------------------- | --------- | ------- | - | `pathPrefix` | Specifies the path that is prepended to the URL. | String | None | - -### `spec.rules.backendRefs.weight` - -Specifies the proportion of requests routed to the specified service. - -This proportion is relative to the sum of all weights in the [`spec.rules.backendRefs`](#spec-rules-backendrefs) block. As a result, weights do not need to add up to 100. When only one backend is specified and the weight is greater then 0, Consul forwards 100% of traffic to the backend. - -When this parameter is not specified, Consul defaults to `1`. - -#### Values - -- Default: `1` -- Data type: Integer - -### `spec.rules.filters` - -Specifies filtering behavior for all requests that match the service defined in [`spec.parentRefs`](#spec-parent-refs). - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.filters.requestHeaderModifier` - -Specifies a set of header modification rules applied to requests that match the service defined in [`spec.parentRefs`](#spec-parent-refs). - -#### Values - -- Default: None -- Values: Object containing one or more fields that define header modification rules: - - `add`: Map of one or more key-value pairs. - - `set`: Map of one or more key-value pairs. - - `remove`: Map of one or more key-value pairs. - -The following table describes how to configure values for request headers: - -| Rule | Description | Type | -| --- | --- | --- | -| `add` | Defines a set of key-value pairs to add to the header. Use header names as the keys. Header names are not case-sensitive. If header values with the same name already exist, the value is appended and Consul applies both headers. You can [use variable placeholders](#use-variable-placeholders). | map of strings | -| `set` | Defines a set of key-value pairs to add to the request header or to replace existing header values with. Use header names as the keys. Header names are not case-sensitive. If header values with the same names already exist, Consul replaces the header values. You can [use variable placeholders](#use-variable-placeholders). | map of strings | -| `remove` | Defines a list of headers to remove. Consul removes only headers containing exact matches. Header names are not case-sensitive. | list of strings | - -##### Use variable placeholders - -For `add` and `set`, if the service is configured to use Envoy as the proxy, the value may contain variables to interpolate dynamic metadata into the value. For example, using the variable `%DOWNSTREAM_REMOTE_ADDRESS%` allows you to pass a value that is generated when the split occurs. - -### `spec.rules.filters.responseHeaderModifier` - -Specifies a set of header modification rules applied to responses from services matching [`spec.parentRefs`](#spec-parent-refs). - -#### Values - -- Default: None -- Values: Object containing one or more fields that define header modification rules: - - `add`: Map of one or more key-value pairs. - - `set`: Map of one or more key-value pairs. - - `remove`: Map of one or more key-value pairs. - -The following table describes how to configure values for request headers: - -| Rule | Description | Type | -| --- | --- | --- | -| `add` | Defines a set of key-value pairs to add to the header. Use header names as the keys. Header names are not case-sensitive. If header values with the same name already exist, the value is appended and Consul applies both headers. You can [use variable placeholders](#use-variable-placeholders). | map of strings | -| `set` | Defines a set of key-value pairs to add to the request header or to replace existing header values with. Use header names as the keys. Header names are not case-sensitive. If header values with the same names already exist, Consul replaces the header values. You can [use variable placeholders](#use-variable-placeholders). | map of strings | -| `remove` | Defines a list of headers to remove. Consul removes only headers containing exact matches. Header names are not case-sensitive. | list of strings | - -##### Use variable placeholders - -For `add` and `set`, if the service is configured to use Envoy as the proxy, the value may contain variables to interpolate dynamic metadata into the value. For example, using the variable `%DOWNSTREAM_REMOTE_ADDRESS%` allows you to pass a value that is generated when the split occurs. - -### `spec.rules.filters.urlRewrite` - -Specifies a path to modify the URL with when a request matching [`spec.parentRefs`](#spec-parent-refs) is forwarded. - -#### Values - -- Default: None -- Data type: Map containing the following parameter: - - | Parameter | Description | Data type | Default | - | :------------ | :------------------------------------------------------------------------------------------------- | --------- | ------- | - | `pathPrefix` | Specifies a path to prepend to the URL when a request matching [`spec.parentRefs`](#spec-parent-refs) is forwarded. | String | None | - -### `spec.rules.matches` - -Specifies rules for matching traffic to services described in [`spec.parentRefs`](#spec-parent-refs) according to the request header or method. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.matches.headers` - -Specifies criteria for matching HTTP request headers. - -When [`spec.rules.matches.headers.value`] is specified multiple times, a request must match all of the specified values for the route to be selected. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.matches.headers.name` - -Specifies the name of a HTTP request header to match on. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.matches.headers.type` - -Specifies a type of match to perform on the HTTP request header. Supported match types include: unspecified, exact, regex, present, prefix, and suffix. - -#### Values - -- Default: None -- Data type: String that should match one of the following values: - - - `HEADER_MATCH_TYPE_UNSPECIFIED` - - `HEADER_MATCH_TYPE_EXACT` - - `HEADER_MATCH_TYPE_REGEX` - - `HEADER_MATCH_TYPE_PRESENT` - - `HEADER_MATCH_TYPE_PREFIX` - - `HEADER_MATCH_TYPE_SUFFIX` - -### `spec.rules.matches.headers.value` - -Specifies the value of the HTTP request header to match. When this field is specified multiple times, a request must match all of the specified values for the route to be selected. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.matches.method` - -Specifies the value of the HTTP method to match. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.matches.path` - -Specifies an HTTP request path to match. When this field is not specified, the CRD matches on the `/` path by default. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.matches.path.type` - -Specifies a type of match to perform on the HTTP path. Supported match types include: unspecified, exact, prefix, and regex. - -#### Values - -- Default: None -- Data type: String that should match one of the following values: - -- `PATH_MATCH_TYPE_UNSPECIFIED` -- `PATH_MATCH_TYPE_EXACT` -- `PATH_MATCH_TYPE_PREFIX` -- `PATH_MATCH_TYPE_REGEX` - -### `spec.rules.matches.path.value` - -Value of the HTTP path to match on. - -#### Values - -- Default: `/` -- Data type: String - -### `spec.rules.matches.queryParams` - -Specifies HTTP query parameters to match on. When [`spec.rules.matches.queryParams.value`] is specified multiple times, a request must match all of the specified values for the route to be selected. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.matches.queryParams.name` - -Specifies the name of the HTTP query parameter to match. Query parameters matches require exact matches between strings. - -If multiple entries specify identical query parameter names, only the first entry with an equivalent name matches. Subsequent entries with an equivalent query parameter name are ignored. If an HTTP request repeats a query parameter, the behavior is intentionally undefined because different data planes have different capabilities. However, we recommend that matching against the first value of the parameter if the data plane supports it, as this behavior is expected in other load balancing contexts. - -Do not route traffic based on repeated query parameters, as differences in data plane implementations may produce errors. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.matches.queryParams.type` - -Specifies a type of match to perform on the HTTP request header. Supported match types include: unspecified, exact, regex, and present. - -#### Values - -- Default: None -- Data type: String that should match one of the following values: - - - `HEADER_MATCH_TYPE_UNSPECIFIED` - - `HEADER_MATCH_TYPE_EXACT` - - `HEADER_MATCH_TYPE_REGEX` - - `HEADER_MATCH_TYPE_PRESENT` - -### `spec.rules.matches.queryParams.value` - -Specifies the value of the HTTP query parameter to match. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.retries` - -Specifies retry logic for routing HTTP traffic. - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.retries.number` - -Specifies the number of retries to attempt when a request fails. - -#### Values - -- Default: None -- Data type: Map that contains the following parameter: - - | Parameter | Description | Data type | Default | - | :-------- | :------------------------------------------- | --------- | ------- | - | `value` | Specifies the number of retries to attempt. | Integer | None | - -### `spec.rules.retries.onConditions` - -Specifies Envoy conditions that cause an automatic retry attempt. - -#### Values - -- Default: None -- Data type: Map of strings - -### `spec.rules.retries.onConnnectFailure` - -Enables an automatic retry attempt when a connection failure error occurs. - -#### Values - -- Default: `false` -- Data type: Boolean - -### `spec.rules.retries.onStatusCodes` - -Specifies the response status codes that are eligible for retry attempts. - -#### Values - -- Default: None -- Data type: Map of integers - -### `spec.rules.timeouts` - -Specifies timeout logic when routing HTTP traffic - -#### Values - -- Default: None -- Data type: Map - -### `spec.rules.timeouts.idle` - -Specifies the total amount of time permitted for the request stream to be idle before a timeout occurs. - -This field accepts a string indicating the number of seconds. For example, indicate five seconds with `5s` and five milliseconds with `0.005s`. - -#### Values - -- Default: None -- Data type: String - -### `spec.rules.timeouts.request` - -Specifies the total amount of time spent processing the entire downstream request, including retries, before triggering a timeout. - -This field accepts a string indicating the number of seconds. For example, indicate five seconds with `5s` and five milliseconds with `0.005s`. - -#### Values - -- Default: None -- Data type: String - -## Examples - -The following examples demonstrate common HTTP route CRD configuration patterns for specific use cases. - -### Split HTTP traffic between two ports - -The following example splits traffic for the `api` service. HTTP traffic for services registered to the Consul catalog that are available at the `api` port is split so that 50% of the traffic routes to the service at the `api` port and 50% routes to the service at the `admin` port. - -```yaml -apiVersion: mesh.consul.hashicorp.com/v2beta1 -kind: HTTPRoute -metadata: - name: api-split - namespace: default -spec: - parentRefs: - - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api - # The virtual port number for the "api-workload" target port. - port: "80" - rules: - - backendRefs: - - backendRef: - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api - # The virtual port number for the "api-workload" target port. - port: "80" - weight: 50 - - backendRef: - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api - # The virtual port number for the "admin-workload" target port. - port: "90" - weight: 50 -``` - -### Route traffic by matching header - -The following examples routes HTTP traffic for the `api` service according to its header HTTP traffic for services registered to the Consul catalog that are available at the `api-workload` port are routed according to the following criteria: - -- For traffic with a header that contains a `x-debug` value of exactly `1`, Consul routes to the `api` service at the `api-workload` port. -- For traffic with a header that contains a `x-debug` value of exactly `2`, Consul routes to the `api-admin` service at the `admin-workload` port. - -This example also configures Consul to modify request and response headers when routing traffic. - -```yaml -apiVersion: mesh.consul.hashicorp.com/v2beta1 -kind: HTTPRoute -metadata: - name: api-filter - namespace: default -spec: - parentRefs: - - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api - # The virtual port number for the "api-workload" target port. - port: "80" - rules: - - matches: - - headers: - - type: "HEADER_MATCH_TYPE_EXACT" - name: "x-debug" - value: "1" - filters: - - requestHeaderModifier: - add: - - name: "request-foo" - value: "request-bar" - - responseHeaderModifier: - add: - - name: "response-foo" - value: "response-bar" - backendRefs: - - backendRef: - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api - # The virtual port number for the "api-workload" target port. - port: "80" - - matches: - - headers: - - type: "HEADER_MATCH_TYPE_EXACT" - name: "x-debug" - value: "2" - filters: - - requestHeaderModifier: - add: - - name: "request-foo" - value: "request-bar" - - responseHeaderModifier: - add: - - name: "response-foo" - value: "response-bar" - backendRefs: - - backendRef: - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api-admin - # The virtual port number for the "admin-workload" target port. - port: "90" -``` - -### Route traffic by matching header and query parameter - -The following examples routes HTTP traffic for the `api` service according to its header HTTP traffic for services registered to the Consul catalog that are available at the `api-workload` port are routed according to the following criteria: - -- For traffic with a header _and_ a query parameter that both contain `x-debug` values of exactly `1`, Consul routes to the `api` service at the `api-workload` port. -- For traffic with a header _and_ a query parameter that both contain `x-debug` values of exactly `2`, Consul routes to the `api-admin` service at the `admin-workload` port. - -```yaml -apiVersion: mesh.consul.hashicorp.com/v2beta1 -kind: HTTPRoute -metadata: - name: api-match-split - namespace: default -spec: - parentRefs: - - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api - # The virtual port number for the "api-workload" target port. - port: "80" - rules: - - matches: - - headers: - - type: "HEADER_MATCH_TYPE_EXACT" - name: "x-debug" - value: "1" - queryParams: - - type: "QUERY_PARAM_MATCH_TYPE_EXACT" - name: "x-debug" - value: "1" - backendRefs: - - backendRef: - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api - # The virtual port number for the "api-workload" target port. - port: "80" - - matches: - - headers: - - type: "HEADER_MATCH_TYPE_EXACT" - name: "x-debug" - value: "2" - queryParams: - - type: "QUERY_PARAM_MATCH_TYPE_EXACT" - name: "x-debug" - value: "2" - backendRefs: - - backendRef: - ref: - type: - group: catalog - groupVersion: v2beta1 - kind: Service - name: api-admin - # The virtual port number for the "admin-workload" target port. - port: "90" -``` \ No newline at end of file diff --git a/website/content/docs/k8s/multiport/reference/proxyconfiguration.mdx b/website/content/docs/k8s/multiport/reference/proxyconfiguration.mdx deleted file mode 100644 index 2ad24cc4b9..0000000000 --- a/website/content/docs/k8s/multiport/reference/proxyconfiguration.mdx +++ /dev/null @@ -1,691 +0,0 @@ ---- -layout: docs -page_title: ProxyConfiguration resource configuration reference -description: The ProxyConfiguration resource CRD configures sidecar proxy behavior within the service mesh. Learn how to configure bootstrap and dynamic configurations for proxies according to Workload characteristics with specifications and example configurations. ---- - -# ProxyConfiguration resource configuration reference - -This page provides reference information for the `ProxyConfigurations` resource, which defines proxy behavior for traffic within the service mesh. The resource specifies both bootstrap and dynamic configurations for proxies. - -This custom resource definition (CRD) describes a resource related to the [Kubernetes GAMMA initiative](https://gateway-api.sigs.k8s.io/concepts/gamma/) that requires the [v2 catalog API](/consul/docs/architecture/v2/catalog). It is not compatible with the [v1 catalog API](/consul/docs/architecture/catalog). For more information about GAMMA resources, refer to the [Kubernetes Gateway API documentation](https://gateway-api.sigs.k8s.io/concepts/gamma/). - -## Configuration model - -The following list outlines field hierarchy, language-specific data types, and requirements in a ProxyConfiguration CRD. Click on a property name to view additional details, including default values. - -- [`apiVersion`](#apiversion): string | required | must be set to `mesh.consul.hashicorp.com/v2beta1` -- [`kind`](#kind): string | required | must be set to `ProxyConfiguration` -- [`metadata`](#metadata): map | required - - [`name`](#metadata-name): string | required - - [`namespace`](#metadata-namespace): string | optional -- [`spec`](#spec): map | required - - [`workloads`](#spec-workloads): map - - [`filter`](#spec-workloads): string - - [`names`](#spec-workloads): array of strings - - [`prefixes`](#spec-workloads): array of strings - - [`bootstrapConfig`](#spec-bootstrapconfig): map - - [`dogstatsdUrl`](#spec-bootstrapconfig): string - - [`overrideJsonTpl`](#spec-bootstrapconfig): string - - [`prometheusBindAddr`](#spec-bootstrapconfig): string - - [`readyBindAddr`](#spec-bootstrapconfig): string - - [`staticClustersJson`](#spec-bootstrapconfig): string - - [`staticListenersJson`](#spec-bootstrapconfig): string - - [`statsBindAddr`](#spec-bootstrapconfig): string - - [`statsConfigJson`](#spec-bootstrapconfig): string - - [`statsFlushInterval`](#spec-bootstrapconfig): string - - [`statsSinksJson`](#spec-bootstrapconfig): string - - [`statsTags`](#spec-bootstrapconfig): array of strings - - [`statsdUrl`](#spec-bootstrapconfig): string - - [`telemetryCollectorBindSocketDir`](#spec-bootstrapconfig): string - - [`tracingConfigJson`](#spec-bootstrapconfig): string - - [`dynamicConfig`](#spec-dynamicconfig): map - - [`accessLogs`](#spec-dynamicconfig-accesslogs): map - - [`enabled`](#spec-dynamicconfig-accesslogs-enabled): boolean | `false` - - [`disableListenerLogs`](#spec-dynamicconfig-accesslogs-disablelistenerlogs): boolean | `false` - - [`jsonFormat`](#spec-dynamicconfig-accesslogs-jsonformat): string - - [`path`](#spec-dynamicconfig-accesslogs-path): string - - [`textFormat`](#spec-dynamicconfig-accesslogs-textformat): string - - [`type`](#spec-dynamicconfig-accesslogs-type): string - - [`exposeConfig`](#spec-dynamicconfig-exposeconfig): map - - [`exposePaths`](#spec-dynamicconfig-exposeconfig-exposepaths): map - - [`listenerPort`](#spec-dynamicconfig-exposeconfig-exposepaths-listenerport): integer - - [`localPathPort`](#spec-dynamicconfig-exposeconfig-exposepaths-localpathport): integer - - [`path`](#spec-dynamicconfig-exposeconfig-exposepaths-path): string - - [`protocol`](#spec-dynamicconfig-exposeconfig-exposepaths-protocol): string - - [`inboundConnections`](#spec-dynamicconfig-inboundconnections): map - - [`balanceInboundConnections`](#spec-dynamicconfig-inboundconnections-balanceinboundconnections): string - - [`maxInboundConnections`](#spec-dynamicconfig-exposeconfig-inboundconnections-maxinboundconnections): integer - - [`listenerTracingJson`](#spec-dynamicconfig-listenertracingjson): string - - [`localClusterJson`](#spec-dynamicconfig-localclusterjson): string - - [`localConnection`](#spec-dynamicconfig-localconnection): map - - [`connectTimeout`](#spec-dynamicconfig-localconnection-connecttimeout): string - - [`requestTimeout`](#spec-dynamicconfig-localconnection-requesttimeout): string - - [`meshGatewayMode`](#spec-dynamicconfig-meshgatewaymode): string - - [`mode`](#spec-dynamicconfig-mode): string - - [`publicListenerJson`](#spec-dynamicconfig-publiclistenerjson): string - - [`transparentProxy`](#spec-dynamicconfig-transparentproxy): map - - [`dialedDirectly`](#spec-dynamicconfig-transparentproxy-dialeddirectly): boolean | `false` - - [`outboundListenerPort`](#spec-dynamicconfig-transparentproxy-outboundlistenerport): integer | `15001` - -## Complete configuration - -When every field is defined, an ProxyConfigurations CRD has the following form: - -```yaml -apiVersion: mesh.consul.hashicorp.com/v2beta1 # required -kind: ProxyConfiguration # required -metadata: - name: - namespace: -spec: - workloads: # required - filter: "Service.Meta.version == v1" - names: ["api", "admin"] - prefixes: ["
", ""]
-  bootstrapConfig: 
-    dogstatsdUrl: 
-    overrideJsonTpl: 
-    prometheusBindAddr: <0.0.0.0>
-    readyBindAddr: 
-    staticClustersJson: 
-    staticListenersJson: 
-    statsBindAddr: <0.0.0.0>
-    statsConfigJson: 
-    statsFlushInterval: 5000ms
-    statsSinkJson: 
-    statsTags:
-      - 
-    statsdUrl: 
-    telemetryCollectorBindSocketDir: 
-    tracingConfigJson: 
-  dynamicConfig:
-    accessLogs:
-      enabled: false
-      disableListenerLogs: false
-      jsonFormat: 
-      path: 
-      textFormat: 
-      type: 
-    exposeConfig:
-      exposePaths:
-        listenerPort: 42
-        localPathPort: 4242
-        path: 
-        protocol: 
-      inboundConnections:
-        balanceInboundConnections: 
-        maxInboundConnections: 1024
-    listenerTracingJson: 
-    localClusterJson: 
-    localConnection:
-      connectTimeout: <1s>
-      requestTimeout: <1s>
-    meshGatewayMode: 
-    mode: 
-    publicListenerJson: 
-    transparentProxy:
-      dialedDirectly: false
-      outboundListenerPort: 15001
-```
-
-## Specification
-
-This section provides details about the fields you can configure in the `ProxyConfiguration` custom resource definition (CRD).
-
-### `apiVersion`
-
-Specifies the version of the Consul API for integrating with Kubernetes. The value must be `mesh.consul.hashicorp.com/v2beta1`.
-
-#### Values
-
-- Default: None
-- This field is required.
-- String value that must be set to `mesh.consul.hashicorp.com/v2beta1`.
-
-### `kind`
-
-Specifies the type of CRD to implement. Must be set to `ProxyConfiguration`.
-
-#### Values
-
-- Default: None
-- This field is required.
-- Data type: String value that must be set to `ProxyConfiguration`.
-
-### `metadata`
-
-Map that contains an arbitrary name for the CRD and the namespace it applies to.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `metadata.name`
-
-Specifies a name for the CRD. The name is metadata that you can use to reference the resource when performing Consul operations, such as using the `consul resource` command. Refer to [`consul resource`](/consul/docs/k8s/connect/multiport/reference/resource-command) for more information.
-
-#### Values
-
-- Default: None
-- This field is required.
-- Data type: String
-
-### `metadata.namespace` 
-
-Specifies the namespace that the proxy configuration applies to. Refer to [namespaces](/consul/docs/enterprise/namespaces) for more information.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec`
-
-Map that contains the details about the `ProxyConfiguration` CRD. The `apiVersion`, `kind`, and `metadata` fields are siblings of the spec field. All other configurations are children.
-
-#### Values
-
-- Default: None
-- This field is required.
-- Data type: Map
-
-### `spec.workloads`
-
-Specifies the workloads that the proxy configuration applies to. You can select workloads by name, prefix, or by using a filter expression.
-
-#### Values
-
-- Default: None
-- This field is required.
-- Data type: Map that can contain the following parameters:
-
- | Parameter     | Description                                                                                                                                                                                                                                                                                   | Data type | Default |
-    | :------------ | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ------- |
-    | `filter`      | Specifies an expression that filters workloads.  For more information about creating and using expressions to filter, refer to [filtering](/consul/api-docs/features/filtering).   | String    | None    |
-    | `names` | Specifies one or more names of workloads the proxy configuration applies to. | Array of strings | None |
-    | `prefixes` | Specifies one or more prefixes. Proxy configurations apply to workloads with one of the prefixes. | Array of strings | None |
-
-### `spec.bootstrapConfig`
-
-Specifies initial bootstrap settings for the Envoy proxy, as well as proxy configuration settings that require the proxy to restart when applied. For more information, refer to [Envoy proxy bootstrap configuration](/consul/docs/connect/proxies/envoy#bootstrap-configuration).
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.bootstrapConfig.dogstatsdUrl`
-
-Specifies a URL that identifies a DataDog DogStatsD listener. Envoy sends metrics to this listener.
-
-Format the URL as `udp://ip:port` or `unix://uds/path`. For example, `udp://127.0.0.1:8125` configures a local UDP DogStatsD listener for every host. TCP is not supported.
-
-The UDP URL must use an IP address. DNS names are not supported.
-
-For more information about configuring a UNIX domain socket with DogStatsD, refer to [the DataDog documentation](https://docs.datadoghq.com/developers/dogstatsd/unix_socket?tab=kubernetes#setup).
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.bootstrapConfig.overrideJsonTpl`
-
-
-This field offers complete control of the proxy's bootstrap. Be aware that major deviations from the default template may break Consul's ability to correctly manage the proxy or enforce its security model.
-
-
-Specifies a template in Go template syntax to use in place of [the default template](https://github.com/hashicorp/consul/blob/71d45a34601423abdfc0a64d44c6a55cf88fa2fc/command/connect/envoy/bootstrap_tpl.go#L129) when generating the bootstrap configuration using the [`consul connect envoy` command](/consul/commands/connect/envoy). For information about the variables Consul can interpolate in the template, refer to the [documentation in `bootstrap_tpl.go`](https://github.com/hashicorp/consul/blob/71d45a34601423abdfc0a64d44c6a55cf88fa2fc/command/connect/envoy/bootstrap_tpl.go#L5).
-
-Refer to [Envoy proxy escape-hatch overrides](/consul/docs/connect/proxies/envoy#escape-hatch-overrides) for more information.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.bootstrapConfig.prometheusBindAddr`
-
-Configures the proxy to expose a Prometheus metrics endpoint to the public network. Format the endpoint as `ip:port`. The port and IP and port combination must be free within the network namespace where the proxy runs. The IP `0.0.0.0` binds to all available interfaces or a pod IP address if supported by your existing network settings.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.bootstrapConfig.readyBindAddr`
-
-Specifies the location to configure the proxy's readiness probe. Format as `ip:port`. 
-
- By default, the proxy does not have a readiness probe configured on it.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.bootstrapConfig.staticClustersJson`
-
-Specifies one or more [Envoy clusters](https://www.envoyproxy.io/docs/envoy/v1.17.2/api-v3/config/cluster/v3/cluster.proto) to append to the array of [static clusters](https://www.envoyproxy.io/docs/envoy/v1.17.2/api-v3/config/bootstrap/v3/bootstrap.proto#envoy-v3-api-field-config-bootstrap-v3-bootstrap-staticresources-clusters) in the bootstrap configuration.
-
-This field enables you to add custom clusters, such as tracing sinks, to the bootstrap configuration. In order to configure a single cluster, specify a single JSON object with the cluster details.
-
-Refer to [Envoy proxy advanced bootstrap options](/consul/docs/connect/proxies/envoy#advanced-bootstrap-options) for more information and examples.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.bootstrapConfig.staticListenersJson`
-
-Specifies one or more [Envoy listeners](https://www.envoyproxy.io/docs/envoy/v1.17.2/api-v3/config/listener/v3/listener.proto) to append to the array of [static listeners](https://www.envoyproxy.io/docs/envoy/v1.17.2/api-v3/config/bootstrap/v3/bootstrap.proto#envoy-v3-api-field-config-bootstrap-v3-bootstrap-staticresources-listeners) definitions.
-
-You can use this field to set up limited access that bypasses the service mesh's mTLS or authorization for health checks or metrics.
-
-Refer to [Envoy proxy advanced bootstrap options](/consul/docs/connect/proxies/envoy#advanced-bootstrap-options) for more information and examples.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.bootstrapConfig.statsBindAddr`
-
-Specifies that the proxy should expose the `/stats` prefix to the _public_ network at the `ip:port` provided. The IP and port combination must be free within the network namespace where the proxy runs. The IP `0.0.0.0` binds to all available interfaces or a pod IP address if supported by your existing network settings.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.bootstrapConfig.statsConfigJson`
-
-Specifies a complete [stats config](https://www.envoyproxy.io/docs/envoy/v1.17.2/api-v3/config/bootstrap/v3/bootstrap.proto#envoy-v3-api-field-config-bootstrap-v3-bootstrap-stats-config).
-
-When provided, this field overrides [`spec.bootstrapConfig.statsTags`](#spec-bootstrapconfig-statstags) and enables full control over dynamic tag replacements.
-
-Refer to [Envoy proxy advanced bootstrap options](/consul/docs/connect/proxies/envoy#advanced-bootstrap-options) for more information and examples.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.bootstrapConfig.statsFlushInterval`
-
- Configures Envoy's [`stats_flush_interval`](https://www.envoyproxy.io/docs/envoy/v1.17.2/api-v3/config/bootstrap/v3/bootstrap.proto#envoy-v3-api-field-config-bootstrap-v3-bootstrap-stats-flush-interval), which measures the length of the interval between stats sink flushes.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.bootstrapConfig.statsSinkJson`
-
-Specifies one or more [stats sinks](https://www.envoyproxy.io/docs/envoy/v1.17.2/api-v3/config/bootstrap/v3/bootstrap.proto#envoy-v3-api-field-config-bootstrap-v3-bootstrap-stats-sinks) to append to sinks defined using [`spec.bootstrapConfig.statsdUrl`](#spec-bootstrapconfig-statsdurl) or [`spec.bootstrapConfig.dogstatsdUrl`](#spec-bootstrapconfig-dogstatsdurl).
-
-Refer to [Envoy proxy advanced bootstrap options](/consul/docs/connect/proxies/envoy#advanced-bootstrap-options) for more information and examples.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.bootstrapConfig.statsTags`
-
-Specifies one or more static tags to add to all metrics produced by the proxy.
-
-#### Values
-
-- Default: None
-- Data type: Array of strings
-
-### `spec.bootstrapConfig.statsdUrl`
-
-Specifies a URL that identifies a StatsD listener. Envoy sends metrics to this listener.
-
-Format the URL as `udp://ip:port`. For example, `udp://127.0.0.1:8125` configures a local StatsD listener for every host. TCP is not supported.
-
-The URL must use an IP address. DNS names are not supported.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.bootstrapConfig.telemetryCollectorBindSocketDir`
-
-Specifies the directory of the Unix socket Envoy sends metrics to so that the Consul telemetry collector can collect them.
-
-The socket is not configured by default.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.bootstrapConfig.tracingConfigJson`
-
-Specifies a configuration for an external tracing provider as described in [the Envoy documentation](https://www.envoyproxy.io/docs/envoy/v1.17.2/api-v3/config/bootstrap/v3/bootstrap.proto#envoy-v3-api-field-config-bootstrap-v3-bootstrap-tracing). Most tracing providers also require adding static clusters to define the endpoints to send tracing data to.
-
-Refer to [Envoy proxy advanced bootstrap options](/consul/docs/connect/proxies/envoy#advanced-bootstrap-options) for more information and examples.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.dynamicConfig`
-
-Specifies configuration settings for the Envoy proxy that are applied without restarting the proxy. For more information, refer to [Envoy proxy dynamic configuration](/consul/docs/connect/proxies/envoy#dynamic-configuration).
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.dynamicConfig.accessLogs`
-
-Specifies the format and output for the proxy's access logs.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.dynamicConfig.accessLogs.enabled`
-
-When set to `true`, this parameter enables access logs for the proxy.
-
-#### Values
-
-- Default: `false`
-- Data type: Boolean
-
-### `spec.dynamicConfig.accessLogs.usableListenerLogs`
-
-When set to `true`, this parameter disables listener logs for connections that the proxy rejected because they did not have a matching listener filter.
-
-#### Values
-
-- Default: `false`
-- Data type: Boolean
-
-### `spec.dynamicConfig.accessLogs.jsonFormat`
-
-Specifies a JSON format dictionary for the access logs. Refer to [format dictionaries in the Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#format-dictionaries) for more information.
-
-When this field is specified, you cannot also specify [`spec.dynamicConfig.accessLogs.textFormat`](#spec-dynamicconfig-accesslogs-textformat) in the same configuration.
-
-### `spec.dynamicConfig.accessLogs.path`
-
-Specifies a file output path for the access logs.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.dynamicConfig.accessLogs.textFormat`
-
-Specifies a format string for the access logs. Refer to [default format string in the Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#default-format-string) for more information.
-
-When this field is specified, you cannot also specify [`spec.dynamicConfig.accessLogs.jsonFormat`](#spec-dynamicconfig-accesslogs-jsonformat) in the same configuration.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.dynamicConfig.accessLogs.type`
-
-Specifies the output type for the access logs.
-
-#### Values
-
-- Default: None
-- Data type: String containing one of the following values:
-
-  - `LOG_SINK_TYPE_DEFAULT`
-  - `LOG_SINK_TYPE_FILE`
-  - `LOG_SINK_TYPE_STDERR`
-  - `LOG_SINK_TYPE_STDOUT`
-
-### `spec.dynamicConfig.exposeConfig`
-
-Specifies configurations for exposing the proxy.
-
-Refer to [expose paths configuration reference](/consul/docs/connect/proxies/proxy-config-reference#expose-paths-configuration-reference) for more information.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.dynamicConfig.exposeConfig.exposePaths`
-
-Specifies a configuration for exposing an HTTP path through the proxy.
-
-Refer to [expose paths configuration reference](/consul/docs/connect/proxies/proxy-config-reference#expose-paths-configuration-reference) for more information.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.dynamicConfig.exposeConfig.exposePaths.listenerPort`
-
-Specifies the port where the proxy listens for connections. This port must be available for the listener to be set up. If the port is not free, Envoy does not expose a listener for the path but the proxy registration does not fail.
-
-#### Values
-
-- Default: None
-- Data type: Integer
-
-### `spec.dynamicConfig.exposeConfig.exposePaths.localPathPort`
-
-Specifies the port where the local service is listening for connections to the path.
-
-#### Values
-
-- Default: None
-- Data type: Integer
-
-### `spec.dynamicConfig.exposeConfig.exposePaths.path`
-
-The HTTP path to expose. Prefix the path with a slash. For example, `/metrics`.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.dynamicConfig.exposeConfig.exposePaths.protocol`
-
-Specifies the protocol of the listener, either HTTP or HTTP/2. For gRPC, use HTTP/2.
-
-#### Values
-
-- Default: None
-- Data type: String containing one of the following values:
-
-  - `EXPOSE_PATH_PROTOCOL_HTTP`
-  - `EXPOSE_PATH_PROTOCOL_HTTP2`
-
-### `spec.dynamicConfig.inboundConnections`
-
-Specifies configurations for how the proxy handles inbound connections.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.dynamicConfig.inboundConnections.balanceInboundConnections`
-
-Specifies the strategy for balancing inbound connections across Envoy worker threads. Consul's service mesh Envoy integration supports the following values:
-
-| Value | Description |
-| :---- | :---------- |
-| Empty string | Inbound connections are not balanced. |
-| `exact_balance` | Balances inbound connections with [Envoy's Exact Balance Strategy](https://cloudnative.to/envoy/api-v3/config/listener/v3/listener.proto.html#config-listener-v3-listener-connectionbalanceconfig-exactbalance). |
-
-#### Values
-
-- Default: `""`
-- Data type: String
-
-### `spec.dynamicConfig.inboundConnections.maxInboundConnections`
-
-Specifies the maximum number of concurrent inbound connections to the local application instance. If not specified, inherits the Envoy default of `1024`.
-
-#### Values
-
-- Default: `1024`
-- Data type: Integer
-
-### `spec.dynamicConfig.listenerTracingJson`
-
-Specifies a tracing configuration to be inserted in the proxy's public and upstreams listeners. Refer to [the Envoy documentation](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-msg-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-tracing) for more information.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.dynamicConfig.localClusterJson`
-
-Specifies a complete Envoy cluster to be delivered in place of the local application cluster. Use this field to customize timeouts, rate limits, and load balancing strategy.
-
-Refer to [cluster configuration in the Envoy documentation](https://www.envoyproxy.io/docs/envoy/v1.17.2/api-v3/config/cluster/v3/cluster.proto) for more information.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.dynamicConfig.localConnection`
-
-Specifies timeout settings for the proxy's connection to the local application. Apply settings separately for each port.
-
-Specify this field as a map containing a key/value. The map keys correspond to port names on the workload. The value contains this parameter's sub-fields.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.dynamicConfig.localConnection.connectTimeout`
-
-Specifies the length of time the proxy is allowed to attempt connections to the local application instance before timing out.
-
-This field accepts a string indicating the number of seconds. For example, indicate five seconds with `5s` and five milliseconds with `0.005s`.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.dynamicConfig.localConnection.requestTimeout`
-
-Specifies the length of time the proxy is allowed to attempt HTTP requests to the local application instance. Applies to HTTP-based protocols only.
-
-This field accepts a string indicating the number of seconds. For example, indicate five seconds with `5s` and five milliseconds with `0.005s`.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.dynamicConfig.meshGatewayMode`
-
-Specifies the proxy's mode of operation for resolving upstreams in remote datacenter destinations. Refer to [Mesh gateway configuration reference](/consul/docs/connect/proxies/proxy-config-reference#mesh-gateway-configuration-reference) for more information.
-
-#### Values
-
-- Default: None
-- Data type: String containing one of the following values:
-
-  - `MESH_GATEWAY_MODE_UNSPECIFIED`
-  - `MESH_GATEWAY_MODE_NONE`
-  - `MESH_GATEWAY_MODE_LOCAL`
-  - `MESH_GATEWAY_MODE_REMOTE`
-
-### `spec.dynamicConfig.mode`
-
-Configures the proxy to operate in transparent, direct, or default mode. Refer to [proxy modes](/consul/docs/connect/proxies/proxy-config-reference#proxy-modes) for more information.
-
-#### Values
-
-- Default: None
-- Data type: String containing one of the following values:
-
-  - `PROXY_MODE_DEFAULT`
-  - `PROXY_MODE_TRANSPARENT`
-  - `PROXY_MODE_DIRECT`
-
-### `spec.dynamicConfig.publicListenerJson`
-
-Specifies a complete Envoy listener for Consul to deliver in place of the main public listener that the proxy uses to accept inbound connections. Refer to [escape-hatch overrides](/consul/docs/connect/proxies/envoy#escape-hatch-overrides) for more information
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.dynamicConfig.transparentProxy`
-
-Specifies additional configurations for operating proxies in transparent proxy mode.  Refer to [transparent proxy configuration reference](/consul/docs/connect/proxies/proxy-config-reference#transparent-proxy-configuration-reference) for more information.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.dynamicConfig.transparentProxy.dialedDirectly`
-
-Determines whether this proxy instance's IP address can be dialed directly by transparent proxies. Transparent proxies typically dial upstreams using the _virtual_ tagged address, which load balances across instances. A database cluster with a leader is an example where dialing individual instances can be helpful.
-
-#### Values
-
-- Default: `false`
-- Data type: Boolean
-
-### `spec.dynamicConfig.transparentProxy.outboundListenerPort`
-
-Specifies the port the proxy listens on for outbound traffic to capture and redirect.
-
-#### Values
-
-- Default: `15001`
-- Data type: Integer
-
-## Examples
-
-The following examples demonstrate common ProxyConfiguration CRD configuration patterns for specific use cases.
-
-### Set a timeout on a subset of proxies
-
-The following example configures sidecar proxies scheduled with workloads whose names begin with `static-server-`. When these workloads attempt connections with the local `web` application, both requests and responses time out after 123 second.
-
-```yaml
-apiVersion: mesh.consul.hashicorp.com/v2beta1
-kind: ProxyConfiguration
-metadata:
-  name: static-server-override-one
-spec:
-  workloads:
-    prefixes:
-    - "static-server-"
-  bootstrapConfig:
-    statsBindAddr: "127.0.0.1:6666"
-  dynamicConfig:
-    # Only the web port should be enabled using the TPs
-    localConnection:
-      web:
-        connectTimeout: "123s" # This ALWAYS has to end in 's'. If you want ms, you need to use "0.123s"
-        requestTimeout: "123s"
-```
diff --git a/website/content/docs/k8s/multiport/reference/tcproute.mdx b/website/content/docs/k8s/multiport/reference/tcproute.mdx
deleted file mode 100644
index dcf96517f7..0000000000
--- a/website/content/docs/k8s/multiport/reference/tcproute.mdx
+++ /dev/null
@@ -1,345 +0,0 @@
----
-layout: docs
-page_title: TCPRoute resource configuration reference
-description: The TCPRoute resource CRD configures L4 TCP behavior within the service mesh. TCPRoute is a GAMMA resource that requires the v2 catalog API. Learn how to configure the TCPRoute CRD with specifications and example configurations.
----
-
-# TCPRoute resource configuration reference
-
-This page provides reference information for the `TCPRoute` resource, which defines Transport Layer (L4) TCP traffic within the service mesh.
-
-This custom resource definition (CRD) describes a resource related to the [Kubernetes GAMMA initiative](https://gateway-api.sigs.k8s.io/concepts/gamma/) that requires the [v2 catalog API](/consul/docs/architecture/v2/catalog). It is not compatible with the [v1 catalog API](/consul/docs/architecture/catalog). For more information about GAMMA resources, refer to the [Kubernetes Gateway API documentation](https://gateway-api.sigs.k8s.io/concepts/gamma/).
-
-## Configuration model
-
-The following list outlines field hierarchy, language-specific data types, and requirements in an TCP route CRD. Click on a property name to view additional details, including default values.
-
-
-
-
-
-- [`apiVersion`](#apiversion): string | required | must be set to `mesh.consul.hashicorp.com/v2beta1`
-- [`kind`](#kind): string | required | must be set to `TCPRoute`
-- [`metadata`](#metadata): map  | required
-  - [`name`](#metadata-name): string | required
-  - [`namespace`](#metadata-namespace): string | optional 
-- [`spec`](#spec): map | required
-  - [`parentRefs`](#spec-parentrefs): map | required
-    - [`port`](#spec-parentrefs-port): string
-    - [`ref`](#spec-parentrefs-ref):  string | required
-      - [`name`](#spec-parentrefs-ref-name): string
-      - [`type`](#spec-parentrefs-ref-type): map
-         - [`group`](#spec-parentrefs-ref-type): string
-         - [`groupVersion`](#spec-parentrefs-ref-type): string
-         - [`kind`](#spec-parentrefs-ref-type): string
-  - [`rules`](#spec-rules): map | required
-    - [`backendRefs`](#spec-rules-backendrefs): map
-      - [`backendRef`](#spec-rules-backendrefs-backendref): map
-        - [`datacenter`](#spec-rules-backendrefs-backendref-datacenter): string
-        - [`port`](#spec-rules-backendrefs-backendref-port): string
-        - [`ref`](#spec-rules-backendrefs-backendref-ref): map
-          - [`name`](#spec-rules-backendrefs-backendref-ref-name): string
-          - [`type`](#spec-rules-backendrefs-backendref-ref-type): map
-            - [`group`](#spec-rules-backendrefs-backendref-ref-type): string
-            - [`groupVersion`](#spec-rules-backendrefs-backendref-ref-type): string
-            - [`kind`](#spec-rules-backendrefs-backendref-ref-type): string
-      - [`weight`](#spec-rules-backendrefs-weight): number
-
-
-
-
-## Complete configuration
-
-When every field is defined, a TCP route CRD has the following form:
-
-```yaml
-apiVersion: mesh.consul.hashicorp.com/v2beta1        # required 
-kind: TCPRoute                                       # required
-metadata:
-  name: 
-  namespace: 
-spec:
-  parentRefs:
-    port: 
-    - ref:
-      name: 
-      type: 
-        group: 
-        groupVersion: 
-        kind: 
-  rules:
-    - backendRefs:
-      - backendRef:
-        datacenter: 
-        port: 
-        ref:
-          name: 
-          type:
-            group: 
-            groupVersion: 
-            kind: 
-      weight: 1
-```
-
-## Specification
-
-This section provides details about the fields you can configure in the `TCPRoute` custom resource definition (CRD).
-
-### `apiVersion`
-
-Specifies the version of the Consul API for integrating with Kubernetes. The value must be `mesh.consul.hashicorp.com/v2beta1`.
-
-#### Values
-
-- Default: None
-- This field is required.
-- String value that must be set to `mesh.consul.hashicorp.com/v2beta1`.
-
-### `kind`
-
-Specifies the type of CRD to implement. Must be set to `TCPRoute`.
-
-#### Values
-
-- Default: None
-- This field is required.
-- Data type: String value that must be set to `TCPRoute`.
-
-### `metadata`
-
-Map that contains an arbitrary name for the CRD and the namespace it applies to.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `metadata.name`
-
-Specifies a name for the CRD. The name is metadata that you can use to reference the resource when performing Consul operations, such as using the `consul resource` command. Refer to [`consul resource`](/consul/docs/k8s/connect/multiport/reference/resource-command) for more information.
-
-#### Values
-
-- Default: None
-- This field is required.
-- Data type: String
-
-### `metadata.namespace` 
-
-Specifies the namespace that the service resolver applies to. Refer to [namespaces](/consul/docs/enterprise/namespaces) for more information.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec`
-
-Map that contains the details about the `TCPRoute` CRD. The `apiVersion`, `kind`, and `metadata` fields are siblings of the spec field. All other configurations are children.
-
-When using this CRD, the `spec` field closely resembles the `TCPRoute` GAMMA resource.  Refer to [TCPRoute in the Kubernetes documentation](https://gateway-api.sigs.k8s.io/references/spec/#gateway.networking.k8s.io/v1alpha2.TCPRoute).
-
-#### Values
-
-- Default: None
-- This field is required.
-- Data type: Map
-
-### `spec.parentRefs`
-
-Specifies the services and other resources to attach the route to. You can only define one `parentsRefs` configuration for each route. To attach the route to multiple resources, specify additional [`spec.parentRefs.ref`](#spec-parentrefs-ref) configurations in the `parentsRefs` block. You can only specify one port for the route. Any resources that send traffic through the route use the same port.
-
-#### Values
-
-- Default: None
-- This field is required.
-- Data type: Map
-
-### `spec.parentRefs.port`
-
-Specifies the port for the Consul service that the configuration applies to. This parameter can be either a virtual port number, such as a Kubernetes service port, or a non-numeric target port value representing a named workload port.
-
-If you are not targeting a virtual port, specify the workload port name directly.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.parentRefs.ref`
-
-Specifies the resource that the route attaches to.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.parentRefs.ref.name`
-
-Specifies the user-defined name of the resource that the configuration applies to.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.parentRefs.ref.type`
-
-Specifies the type of resource that the configuration applies to. To reference a service in the Consul catalog, configure the resource type as `catalog.v2beta1.Service`.
-
-#### Values
-
-- Default: None
-- Data type: Map containing the following parameters:
-
-  | Parameter     | Description                                                          | Data type | Default  |
-  | :------------ | :------------------------------------------------------------------- | :-------- | :------- |
-  | `group`   | Specifies a group for the resource type within the Kubernetes cluster. To reference a service in the Consul catalog, set this parameter to `catalog`. | String    | None     |
-  | `groupVersion` | Specifies a groupVersion for the resource type within the Kubenretes cluster. To reference a service in the Consul catalog, set this parameter to `v2beta1`. | String | None     |
-  | `kind`    | Specifies the kind of the Kubernetes object the resource applies to. To reference a service in the Consul catalog, set this parameter to `Service`.                | String    | None     |
-
-### `spec.rules`
-
-Specifies rules for sidecar proxies to direct a service's TCP traffic within the service mesh.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.rules.backendRefs`
-
-Specifies the Kubernetes Service backend to direct TCP traffic to when a request matches the service described in [`spec.parentRefs`](#spec-parentrefs). The Service backend is the collection of endpoint IPs for the service. Refer to [the Kubernetes Gateway API specification](https://gateway-api.sigs.k8s.io/concepts/gamma/#an-overview-of-the-gateway-api-for-service-mesh) for more information about Service backends.
-
-When a valid Service backend cannot be reached and no additional filters apply, traffic that matches the rule returns a 500 status code.
-
-When different Service backends are specified in [`spec.rules.backendRefs.weight`](#spec-rules-backendrefs-weight) and one of the backends is invalid, Consul continues to apply the specified weights instead of adjusting the relative weights to exclude traffic to the invalid backend. For example, when traffic is configured in a 50-50 split between `api` and `admin` and no valid endpoints for `admin` can be reached, the 50% of traffic intended for `admin` returns with a 500 status code.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.rules.backendRefs.backendRef`
-
-Specifies an individual Service backend where matching requests should be sent.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.rules.backendRefs.backendRef.datacenter`
-
-Specifies the name of the Consul datacenter that registered the Service backend that the configuration routes traffic to.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.rules.backendRefs.backendRef.port`
-
-Specifies the port for the Consul service that the configuration routes traffic to. This parameter can be either a virtual port number, such as a Kubernetes service port, or a non-numeric target port value representing a named workload port.
-
-If you are not targeting a virtual port, specify the workload port name directly.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.rules.backendRefs.backendRef.ref`
-
-The Consul service that the configuration routes traffic to.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.rules.backendRefs.backendRef.ref.name`
-
-Specifies the user-defined name of the resource that the configuration routes traffic to.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.rules.backendRefs.backendRef.ref.type`
-
-Specifies the type of resource that the configuration routes traffic to. To reference a service in the Consul catalog, configure the resource type as `catalog.v2beta1.Service`.
-
-#### Values
-
-- Default: None
-- Data type: Map containing the following parameters:
-
-  | Parameter     | Description                                                          | Data type | Default  |
-  | :------------ | :------------------------------------------------------------------- | :-------- | :------- |
-  | `group`   | Specifies a group for the resource type within the Kubernetes cluster. To reference a service in the Consul catalog, set this parameter to `catalog`. | String    | None     |
-  | `groupVersion` | Specifies a groupVersion for the resource type within the Kubenretes cluster. To reference a service in the Consul catalog, set this parameter to `v2beta1`. | String | None     |
-  | `kind`    | Specifies the kind of the Kubernetes object for the resource. To reference a service in the Consul catalog, set this parameter to `Service`.                | String    | None     |
-
-### `spec.rules.backendRefs.weight`
-
-Specifies the proportion of requests routed to the specified service. 
-
-This proportion is relative to the sum of all weights in the [`spec.rules.backendRefs`](#spec-rules-backendrefs) block. As a result, weights do not need to add up to 100. When only one backend is specified and the weight is greater then 0, Consul forwards 100% of traffic to the backend. 
-
-When this parameter is not specified, Consul defaults to `1`.
-
-#### Values
-
-- Default: `1`
-- Data type: Integer
-
-## Examples
-
-The following examples demonstrate common TCPRoute CRD configuration patterns for specific use cases.
-
-### Split TCP traffic between two ports
-
-The following example splits traffic for the `api` service. TCP traffic for services registered to the Consul catalog that are available at the `api-workload` port is split so that 50% of the traffic routes to the service at the `api-workload` port and 50% routes to the service at the `admin-workload` port.
-
-```yaml
-apiVersion: mesh.consul.hashicorp.com/v2beta1
-kind: TCPRoute
-metadata:
-  name: api-split
-  namespace: default
-spec:
-  parentRefs:
-    - ref:
-        type: 
-          group: catalog
-          groupVersion: v2beta1
-          kind: Service
-        name: api
-      # The virtual port number for the "api-workload" target port.
-      port: "80"
-  rules:
-    - backendRefs:
-      - backendRef:
-          ref: 
-            type:
-              group: catalog
-              groupVersion: v2beta1
-              kind: Service
-            name: api
-          # The virtual port number for the "api-workload" target port.
-          port: "80"
-        weight: 50
-      - backendRef:
-          ref: 
-            type:
-              group: catalog
-              groupVersion: v2beta1
-              kind: Service
-            name: api
-          # The virtual port number for the "admin-workload" target port.
-          port: "90"
-        weight: 50
-```
\ No newline at end of file
diff --git a/website/content/docs/k8s/multiport/reference/trafficpermissions.mdx b/website/content/docs/k8s/multiport/reference/trafficpermissions.mdx
deleted file mode 100644
index 58a58fee6f..0000000000
--- a/website/content/docs/k8s/multiport/reference/trafficpermissions.mdx
+++ /dev/null
@@ -1,245 +0,0 @@
----
-layout: docs
-page_title: TrafficPermissions resource configuration reference
-description: The TrafficPermissions CRD defines rules for allowing and denying traffic between services within the service mesh. Learn how to configure traffic permissions for the v2 catalog to authorize service-to-service communication in a network with zero-trust security.
----
-
-# TrafficPermissions configuration reference
-
-This page provides reference information for the `TrafficPermissions` resource, which authorizes east-west traffic between services within the service mesh. The traffic permissions CRD replaces the service intentions CRD when using the v2 catalog API. Refer to [changes to Consul's existing architecture](/consul/docs/architecture/v2/catalog#changes-to-consul-s-existing-architecture) for more information.
-
-This custom resource definition (CRD) describes a resource related to the [Kubernetes GAMMA initiative](https://gateway-api.sigs.k8s.io/concepts/gamma/) that requires the [v2 catalog API](/consul/docs/architecture/v2/catalog). It is not compatible with the [v1 catalog API](/consul/docs/architecture/catalog). For more information about GAMMA resources, refer to the [Kubernetes Gateway API documentation](https://gateway-api.sigs.k8s.io/concepts/gamma/).
-
-## Configuration model
-
-The following list outlines field hierarchy, language-specific data types, and requirements in a traffic permissions CRD. Click on a property name to view additional details, including default values.
-
-- [`apiVersion`](#apiversion): string | required | must be set to `auth.consul.hashicorp.com/v2beta1`
-- [`kind`](#kind): string | required | must be set to `TrafficPermissions`
-- [`metadata`](#metadata): map | required
-  - [`name`](#metadata-name): string | required
-  - [`namespace`](#metadata-namespace): string | optional 
-- [`spec`](#spec): map | required
-  - [`destination`](#spec-destination): map
-    - [`identityName`](#spec-destination-identityname): string
-  - [`action`](#spec-action): string
-  - [`permissions`](#spec-permissions): list of maps
-    - [`sources`](#spec-permissions-sources): list of maps
-      - [`identityName`](#spec-permissions-sources-identityname): string
-    - [`destinationRules`](#spec-permissions-destinationrules): list of maps
-      - [`portNames`](#spec-permissions-destinationrules-portnames): array of strings
-
-## Complete configuration
-
-When every field is defined, a traffic permissions CRD has the following form:
-
-```yaml
-apiVersion: auth.consul.hashicorp.com/v2beta1    # required
-kind: TrafficPermissions                         # required
-metadata:
-  name: 
-  namespace: 
-spec:
-  destination:
-    identityName: 
-  action: allow
-  permissions:
-    - sources:
-        - identityName: 
-      destinationRules:
-        - portNames:
-            - 
-```
-
-## Specification
-
-This section provides details about the fields you can configure in the `TrafficPermissions` custom resource definition (CRD).
-
-### `apiVersion`
-
-Specifies the version of the Consul API for integrating with Kubernetes. The value must be `auth.consul.hashicorp.com/v2beta1`.
-
-#### Values
-
-- Default: None
-- This field is required.
-- String value that must be set to `auth.consul.hashicorp.com/v2beta1`.
-
-### `kind`
-
-Specifies the type of CRD to implement. Must be set to `TrafficPermissions`.
-
-#### Values
-
-- Default: None
-- This field is required.
-- Data type: String value that must be set to `TrafficPermissions`.
-
-### `metadata`
-
-Map that contains an arbitrary name for the CRD and the namespace it applies to.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `metadata.name`
-
-Specifies a name for the CRD. The name is metadata that you can use to reference the resource when performing Consul operations, such as using the `consul resource` command. Refer to [`consul resource`](/consul/docs/k8s/connect/multiport/reference/resource-command) for more information.
-
-#### Values
-
-- Default: None
-- This field is required.
-- Data type: String
-
-### `metadata.namespace` 
-
-Specifies the namespace that the service resolver applies to. Refer to [namespaces](/consul/docs/enterprise/namespaces) for more information.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec`
-
-Map that contains the details about the `TrafficPermissions` CRD. The `apiVersion`, `kind`, and `metadata` fields are siblings of the spec field. All other configurations are children.
-
-#### Values
-
-- Default: None
-- This field is required.
-- Data type: Map
-
-### `spec.destination`
-
-Specifies the proxies of the services where these traffic permissions apply.
-
-#### Values
-
-- Default: None
-- Data type: Map
-
-### `spec.destination.identityName`
-
-Specifies the Workload identity for a service. The permissions you configure in this `TrafficPermissions` CRD apply to sidecar proxies when a request has this identity as their destination.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.action`
-
-Specifies whether the proxy should _allow traffic_ or _deny traffic_ between the destination in [`spec.destination`](#spec-destination) and the sources in [`spec.permissions.sources`](#spec-permissions-sources).
-
-`ACTION_DENY` is a governance feature available in Consul Enterprise that cannot be overridden by another `ACTION_ALLOW`.
-
-By default, Consul allows traffic between all services. When the Helm value `global.acls.manageSystemACLs` is set to `true`, then Consul operates in "default-deny" mode. In this mode, `TrafficPermissions` CRDs that allow traffic between services are required for service-to-service traffic.
-
-#### Values
-
-- Default: None
-- Data type: String that must contain one of the following values:
-
-  - `ACTION_ALLOW`
-  - `ACTION_DENY` 
-
-### `spec.permissions`
-
-Permissions is a list of the traffic permissions Consul evaluates requests against. When the list contains more than one permission, Consul follows OR semantics to select the permission.
-
-#### Values
-
-- Default: None
-- Data type: List of maps
-
-### `spec.permissions.sources`
-
-Lists sources for traffic in the permission. This block contains information Consul uses to evaluate the service that originated the request when the sidecar proxy authorizes incoming traffic.
-
-To specify wildcard references in this block using `*`, omit all other fields. For example, you can apply traffic permissions to all namespaces using the wildcard, but you cannot specify an identity name, partition, peer, or sameness group in the same source.
-
-#### Values
-
-- Default: None
-- Data type: List of maps
-
-### `spec.permissions.sources.identityName`
-
-Specifies the Workload identity for the service that originates the request.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-### `spec.permissions.destinationRules`
-
-Specifies L7 properties to match against when Consul enforces traffic permissions.
-
-When [`spec.action`](#spec-action) _allows traffic_, Consul authorizes requests to the destination service when the request matches one or more rules defined in this block. Requests that do not match any rules are denied.
-
-When [`spec.action`](#spec-action) _denies traffic_, Consul denies authorization to requests that match one or more rules defined in this block. Requests that do not match any rules are allowed.
-
-#### Values
-
-- Default: None
-- Data type: List of maps
-
-### `spec.permissions.destinationRules.portNames`
-
-Specifies a port name that the Kubernetes Pod's container exposes at the destination.
-
-#### Values
-
-- Default: None
-- Data type: String
-
-## Examples
-
-The following examples demonstrate common `TrafficPermissions` CRD configuration patterns for specific use cases.
-
-### Allow traffic to multiple ports
-
-The following example configures traffic permissions to allow traffic when the `web` service makes a request to the `api` service on the `api` port or `admin` port.
-
-```yaml
-apiVersion: auth.consul.hashicorp.com/v2beta1
-kind: TrafficPermissions
-metadata:
-  name: api-allow-web-all
-spec:
-  destination:
-    identityName: "api"
-  action: ACTION_ALLOW
-  permissions:
-    - sources:
-        - identityName: "web"
-      destinationRules:
-        - portNames: ["api", "admin"]
-
-```
-
-### Deny traffic between a service and a specific port 
-
-The following example configures traffic permissions to deny traffic when the `web` service makes a request to the `api` service on the `admin` port.
-This `ACTION_DENY` cannot be overridden by another `ACTION_ALLOW`.
-
-```yaml
-apiVersion: auth.consul.hashicorp.com/v2beta1
-kind: TrafficPermissions
-metadata:
-  name: web-to-admin-port-deny
-spec:
-  destination:
-    identityName: api
-  action: ACTION_DENY
-  permissions:
-    - sources:
-        - identityName: web
-      destinationRules:
-        - portNames: ["admin"]
-```
diff --git a/website/content/docs/k8s/multiport/traffic-split.mdx b/website/content/docs/k8s/multiport/traffic-split.mdx
deleted file mode 100644
index 4493c3d792..0000000000
--- a/website/content/docs/k8s/multiport/traffic-split.mdx
+++ /dev/null
@@ -1,113 +0,0 @@
----
-layout: docs
-page_title: Split traffic between multi-port services 
-description: Learn how to configure Consul to split TCP traffic between two ports of a multi-port service using the TCPRoute resource and the v2 catalog API
----
-
-# Split TCP traffic between multi-port services
-
-
-
-The v2 catalog API is currently in beta. This documentation supports testing and development scenarios. Do not use the v2 catalog API in secure production environments.
-
-
-
-This page describes the process for splitting TCP, HTTP, and gRPC traffic between two ports of a multi-port service. It includes an example TCPRoute resource configuration to demonstrate Consul's multi-port features.
-
-## Prerequisites
-
-Splitting traffic between two ports of a multi-port service requires the [v2 catalog API](/consul/docs/architecture/v2/catalog).
-
-In addition, they are two different workflows for registering Services in Kubernetes using the v2 catalog API. The instructions on this page offer examples for two configuration methods:
-
-- **Method 1**: Register a Kubernetes service that select workloads which expose multiple ports
-- **Method 2**: Register multiple Kubernetes Services that direct traffic to an explicit port on the same workload
-
-For guidance on enabling the v2 catalog, deploying multi-port services using these methods, and applying traffic permissions to the services, refer to [configure multi-port services](/consul/docs/k8s/multiport/configure).
-
-## Overview
-
-Complete the following steps to implement a split in TCP traffic between two services:
-
-1. Define the resource's behavior in a custom resource definition (CRD).
-1. Apply the resource to your cluster.
-
-## Define route resource
-
-The following example splits traffic for the `api` service. TCP traffic for services registered to the Consul catalog that are available at the `api-workload` port is split so that 50% of the traffic routes to the service at the `api-workload` port and 50% routes to the service at the `admin-workload` port.
-
-
-
-```yaml
-apiVersion: mesh.consul.hashicorp.com/v2beta1
-kind: TCPRoute
-metadata:
-  name: api-split
-spec:
-  parentRefs:
-    - ref:
-        type:
-          group: catalog
-          groupVersion: v2beta1
-          kind: Service
-        name: api
-      # The virtual port number for the "api-workload" target port.
-      port: "80"
-  rules:
-    - backendRefs:
-      - backendRef:
-          ref:
-            type:
-              group: catalog
-              groupVersion: v2beta1
-              kind: Service
-            name: api
-          # The virtual port number for the "api-workload" target port.
-          port: "80"
-        weight: 50
-      - backendRef:
-          ref:
-            type:
-              group: catalog
-              groupVersion: v2beta1
-              kind: Service
-            name: api
-          # The virtual port number for the "admin-workload" target port.
-          port: "90"
-        weight: 50
-```
-
-
-
-## Apply the resource
-
-Use the `kubectl` command to apply the resource to your Consul cluster.
-
-```shell-session
-$ kubectl apply -f api-split.yaml
-```
-
-
-
-
-
-Then, open a shell session in the `web` container and test the `api` service on port 90.
-
-```shell-session
-$ kubectl exec -it ${WEB_POD} -c web -- curl api:90
-```
-
-
-
-
-
-Then, open a shell session in the `web` container and test the `api-admin` service on port 90.
-
-```shell-session
-$ kubectl exec -it ${WEB_POD} -c web -- curl api-admin:90
-```
-
-
-
-
-Half of the traffic should respond with the `hello world` response from port 80, instead of port 90's response of `hello world from 9090 admin`. Repeat the command several times to verify that you receive both responses.
diff --git a/website/content/docs/k8s/upgrade/index.mdx b/website/content/docs/k8s/upgrade/index.mdx
index da345de80b..690ed380f3 100644
--- a/website/content/docs/k8s/upgrade/index.mdx
+++ b/website/content/docs/k8s/upgrade/index.mdx
@@ -15,6 +15,8 @@ As of Consul v1.14.0 and the corresponding Helm chart version v1.0.0, Kubernetes
 
 The v1.0.0 release of the Consul on Kubernetes Helm chart also introduced a change to the [`externalServers[].hosts` parameter](/consul/docs/k8s/helm#v-externalservers-hosts). Previously, you were able to enter a provider lookup as a string in this field. Now, you must include `exec=` at the start of a string containing a provider lookup. Otherwise, the string is treated as a DNS name. Refer to the [`go-netaddrs`](https://github.com/hashicorp/go-netaddrs) library and command line tool for more information.
 
+If you configured your Consul agents to use [`ports.grpc_tls`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#grpc_tls_port) instead of [`ports.grpc`](https://developer.hashicorp.com/consul/docs/agent/config/config-files#grpc_port) and you want to upgrade a multi-datacenter deployment with Consul servers running outside of the Kubernetes cluster to v1.0.0 or higher, set [`externalServers.tlsServerName`](/consul/docs/k8s/helm#v-externalservers-tlsservername) to `server..domain`.
+
 ## Upgrade types
 
 We recommend updating Consul on Kubernetes when:
diff --git a/website/content/docs/nia/compatibility.mdx b/website/content/docs/nia/compatibility.mdx
index d74e740ece..7e473bc356 100644
--- a/website/content/docs/nia/compatibility.mdx
+++ b/website/content/docs/nia/compatibility.mdx
@@ -13,7 +13,7 @@ This topic describes Consul-Terraform-Sync (CTS) cross-compatibility with Consul
 
 Below are CTS versions with supported Consul versions. The latest CTS binary supports the three most recent Consul minor versions, along with their latest patch versions.
 
-| CTS Version            | Consul Community Edition & Enterprise Version | HCP Consul Version |
+| CTS Version            | Consul Community Edition & Enterprise Version | HCP Consul Dedicated Version |
 | :--------------------- | :------------------------------ | :----------------- |
 | CTS Enterprise 0.6+    | 1.8+                            | 1.9+               |
 | CTS Enterprise 0.3-0.5 | 1.8+                            | N/A                |
diff --git a/website/content/docs/nia/configuration.mdx b/website/content/docs/nia/configuration.mdx
index b6ae92105f..ec07f3474d 100644
--- a/website/content/docs/nia/configuration.mdx
+++ b/website/content/docs/nia/configuration.mdx
@@ -86,7 +86,7 @@ license {
 
 You can use the `auto_retrieval` block to configure the automatic license retrieval in CTS. When enabled, CTS attempts to retrieve a new license from its configured Consul Enterprise backend once a day. If CTS cannot retrieve a license and the current license is reaching its expiration date, CTS attempts to retrieve a license with increased frequency, as defined by the [License Expiration Date Handling](/consul/docs/nia/enterprise/license#license-expiration-handling).
 
-~> Enabling `auto_retrieval` is recommended when using HCP Consul, as HCP Consul licenses expire more frequently than Consul Enterprise licenses. Without auto-retrieval enabled, you have to restart CTS every time you load a new license.
+~> Enabling `auto_retrieval` is recommended when using HCP Consul Dedicated, as HCP Consul Dedicated licenses expire more frequently than Consul Enterprise licenses. Without auto-retrieval enabled, you have to restart CTS every time you load a new license.
 
 | Parameter | Required | Type | Description | Default |
 | --------- | -------- | ---- | ----------- | ------- |
diff --git a/website/content/docs/nia/enterprise/license.mdx b/website/content/docs/nia/enterprise/license.mdx
index fd71d01047..338e681344 100644
--- a/website/content/docs/nia/enterprise/license.mdx
+++ b/website/content/docs/nia/enterprise/license.mdx
@@ -20,7 +20,7 @@ To get a trial license for CTS, you can sign-up for the [trial license for Consu
 ## Automatic License Retrieval
 CTS automatically retrieves a license from Consul on startup and then attempts to retrieve a new license once a day. If the current license is reaching its expiration date, CTS attempts to retrieve a license with increased frequency, as defined by the [License Expiration Date Handling](/consul/docs/nia/enterprise/license#license-expiration-handling).
 
-~> Enabling automatic license retrieval is recommended when using HCP Consul, as HCP Consul licenses expire more frequently than Consul Enterprise licenses. Without auto-retrieval enabled, you have to restart CTS every time you load a new license.
+~> Enabling automatic license retrieval is recommended when using HCP Consul Dedicated, as HCP Consul Dedicated licenses expire more frequently than Consul Enterprise licenses. Without auto-retrieval enabled, you have to restart CTS every time you load a new license.
 
 ## Setting the License Manually
 
diff --git a/website/content/docs/release-notes/consul-api-gateway/v0_1_x.mdx b/website/content/docs/release-notes/consul-api-gateway/v0_1_x.mdx
index efb65ec71a..6f20ae97af 100644
--- a/website/content/docs/release-notes/consul-api-gateway/v0_1_x.mdx
+++ b/website/content/docs/release-notes/consul-api-gateway/v0_1_x.mdx
@@ -36,7 +36,7 @@ This release includes the following features and capabilities:
    - Google GKE
    - Azure AKS.
 1. Install via the  HashiCorp Consul Helm chart.
-1. Works with self-managed Consul servers and HCP Consul servers
+1. Works with self-managed Consul Enterprise servers and HCP Consul Dedicated servers
 1. Configure via Kubernetes Gateway API - v1alpha2
 1. Deploy 1 or more logical API Gateways per Kubernetes cluster
 1. Support for HTTP, HTTPS, TCP, and TCP+TLS
diff --git a/website/content/docs/release-notes/consul-k8s/v1_2_x.mdx b/website/content/docs/release-notes/consul-k8s/v1_2_x.mdx
index 3f9ba28083..60c6597fd5 100644
--- a/website/content/docs/release-notes/consul-k8s/v1_2_x.mdx
+++ b/website/content/docs/release-notes/consul-k8s/v1_2_x.mdx
@@ -75,6 +75,14 @@ We are pleased to announce the following Consul updates.
 
 For more detailed information, please refer to the [upgrade details page](/consul/docs/upgrading/upgrade-specific) and the changelogs.
 
+## Known Issues
+
+The following issues are known to exist in the v1.2.x releases. Refer to the changelog for more information.
+
+- v1.2.8 - Service-to-service networking is broken when deployed on OpenShift. OpenShift users are advised to avoid deploying this version of consul-k8s.
+    A fix is present in the v1.2.9 release [[GH-4038](https://github.com/hashicorp/consul-k8s/pull/4038)].
+
+
 ## Changelogs
 
 The changelogs for this major release version and any maintenance versions are listed below.
diff --git a/website/content/docs/release-notes/consul-k8s/v1_3_x.mdx b/website/content/docs/release-notes/consul-k8s/v1_3_x.mdx
index bacbcae9f4..4ac0284255 100644
--- a/website/content/docs/release-notes/consul-k8s/v1_3_x.mdx
+++ b/website/content/docs/release-notes/consul-k8s/v1_3_x.mdx
@@ -16,7 +16,7 @@ Catalog v2 supports multi-port application deployments with a single Envoy proxy
 
   The v1 and v2 catalogs are not cross compatible, and not all Consul features are available within this v2 feature preview.
   - The Consul UI must be disabled. It does not support multi-port services or the v2 catalog API in this release.
-  - HCP Consul does not support multi-port services or the v2 catalog API in this release.
+  - HCP Consul Dedicated does not support multi-port services or the v2 catalog API in this release.
   - The v2 API only supports transparent proxy mode where services that have permissions to connect to each other can use Kube DNS to connect.
 
    The v2 Catalog and Resources API is currently in feature preview for Consul on Kubernetes 1.3.0 and should not be used in production environments. 
@@ -45,6 +45,8 @@ For more detailed information, please refer to the [upgrade details page](/consu
 The following issues are known to exist in the v1.3.x releases. Refer to the changelog for more information.
 
 - When using the v2 API with transparent proxy, Kubernetes pods cannot use L7 liveness, readiness, or startup probes.
+- v1.3.5 - Service-to-service networking is broken when deployed on OpenShift. OpenShift users are advised to avoid deploying this version of consul-k8s.
+    A fix is present in the v1.3.6 release [[GH-4037](https://github.com/hashicorp/consul-k8s/pull/4037)].
 
 ## Changelogs
 
diff --git a/website/content/docs/release-notes/consul-k8s/v1_4_x.mdx b/website/content/docs/release-notes/consul-k8s/v1_4_x.mdx
index 8d72391677..ec264b1359 100644
--- a/website/content/docs/release-notes/consul-k8s/v1_4_x.mdx
+++ b/website/content/docs/release-notes/consul-k8s/v1_4_x.mdx
@@ -41,6 +41,13 @@ Refer to [Supported Consul and Kubernetes versions](/consul/docs/v1.18.x/k8s/com
 
 For more detailed information, please refer to the [upgrade details page](/consul/docs/upgrading/upgrade-specific) and the changelogs.
 
+## Known issues
+
+The following issues are known to exist in the v1.4.x releases:
+
+- v1.4.2 - Service-to-service networking is broken when deployed on OpenShift. OpenShift users are advised to avoid deploying this version of consul-k8s.
+    A fix is present in the v1.4.3 release [[GH-4034](https://github.com/hashicorp/consul-k8s/pull/4034)].
+
 ## Changelogs
 
 The changelogs for this major release version and any maintenance versions are listed below.
diff --git a/website/content/docs/release-notes/consul-k8s/v1_5_x.mdx b/website/content/docs/release-notes/consul-k8s/v1_5_x.mdx
new file mode 100644
index 0000000000..005a21df37
--- /dev/null
+++ b/website/content/docs/release-notes/consul-k8s/v1_5_x.mdx
@@ -0,0 +1,48 @@
+---
+layout: docs
+page_title: 1.5.x
+description: >-
+  Consul on Kubernetes release notes for version 1.5.x
+---
+
+# Consul on Kubernetes 1.5.0
+
+We are pleased to announce the following Consul updates.
+
+## Release highlights
+
+- **External Services CRD**: You can now configure and register external services, including their health checks, alongside existing Kubernetes application manifests with the new [`Registration` Custom Resource Definition (CRD)](/consul/docs/connect/config-entries/registration). This CRD changes the workflow to register a service running on an external node with the Consul catalog. A terminating gateway is still required to enable downstream services in the service mesh to communicate with external services. Refer to [Register external services on Kubernetes overview](/consul/docs/k8s/deployment-configurations/external-service) for more information.
+
+- **File system certificate**: A new [`file-system-certificate` configuration entry](/consul/docs/connect/config-entries/file-system-certificate) for Consul API Gateway on VMs supports specifying a filepath to the certificate and private key on the local system. Consul on Kubernetes deployments that use `consul-k8s` Helm chart v1.5.0 or later use file system certificates instead of inline certificates without additional configuration. For information about certificate configuration for Kubernetes environments, refer to [Gateway Resource Configuration](/consul/docs/connect/gateways/api-gateway/configuration/gateway).
+
+- **v2 catalog API deprecation**: Consul introduced an experimental v2 Catalog API in v1.17.0. This API supported multi-port Service configurations on Kubernetes, and it was made available for testing and development purposes. The v2 catalog and its support for multiport Kubernetes Services were deprecated in the v1.19.0 release.
+
+- **Argo Rollouts Plugin**: A new Argo Rollouts plugin for progressive delivery is now available for `consul-k8s`. This plugin supports canary deployments by allowing you to incrementally release and test new versions of applications and roll back to previous versions by splitting traffic between subsets of services. Refer to [Argo Rollouts Progressive Delivery with Consul on Kubernetes](/consul/docs/k8s/deployment-configurations/argo-rollouts-configuration) for more information.
+
+## Supported software
+
+This version of Consul on Kubernetes supports the following software versions:
+
+- Consul v1.19.x
+- Consul Dataplane v1.5.x. Refer to [Envoy and Consul Dataplane](/consul/docs/v1.19.x/connect/proxies/envoy#envoy-and-consul-dataplane) for details about Consul Dataplane versions and the available packaged Envoy version.
+- Kubernetes 1.27.x - 1.30.x
+- kubectl 1.27.x - 1.30.x
+- Helm 3.11.3+
+
+Refer to [Supported Consul and Kubernetes versions](/consul/docs/v1.19.x/k8s/compatibility#supported-consul-and-kubernetes-versions) for more information.
+
+## Upgrading
+
+For more detailed information, please refer to the [upgrade details page](/consul/docs/upgrading/upgrade-specific) and the changelogs.
+
+## Known issues
+
+The following issues are known to exist in the v1.5.x releases:
+
+## Changelogs
+
+The changelogs for this major release version and any maintenance versions are listed below.
+
+ These links take you to the changelogs on the GitHub website. 
+
+- [1.5.0](https://github.com/hashicorp/consul-k8s/releases/tag/v1.5.0)
diff --git a/website/content/docs/release-notes/consul-k8s/v1_6_x.mdx b/website/content/docs/release-notes/consul-k8s/v1_6_x.mdx
new file mode 100644
index 0000000000..98aab6df59
--- /dev/null
+++ b/website/content/docs/release-notes/consul-k8s/v1_6_x.mdx
@@ -0,0 +1,51 @@
+---
+layout: docs
+page_title: 1.6.x
+description: >-
+  Consul on Kubernetes release notes for version 1.6.x
+---
+
+# Consul on Kubernetes 1.6.0
+
+We are pleased to announce the following Consul updates.
+
+## Release highlights
+
+
+- **Consul DNS views for Kubernetes**: Consul on Kubernetes now supports scheduling a dedicated Consul DNS proxy in a Kubernetes Pod instead of using client agents or dataplanes. The proxy is designed for deployment in a Kubernetes cluster with [external servers enabled](/consul/docs/k8s/deployment-configurations/servers-outside-kubernetes) to provide applications in Kubernetes the ability to resolve Consul DNS addresses. You can use the Consul DNS proxy to enable service discovery across admin partitions in Kubernetes deployments. For more information, refer to [Consul DNS views for Kubernetes](/consul/docs/k8s/dns/views).
+
+- **Catalog sync improvements**: Consul v1.20 includes improved metrics for Consul catalog sync. Consul catalog sync is a feature that automatically registers Kubernetes services with Consul’s catalog. Prior to this enhancement, operators had limited visibility into the status of Kubernetes services being synced to Consul and the performance of the sync catalog process. At scale, it was difficult to discern if the sync process was healthy and progressing normally. Catalog sync enhancements now provide more insights that include status and performance metrics of the sync process. New performance metrics include the rate of registered or deregistered services, the status and health of the sync process, and additional metadata related to registered services.
+
+- **OpenShift improvements**: Prior to this release, deploying Consul service mesh on Kubernetes required the transparent proxy to have elevated permissions through `anyuid` security context constraint (SCC) privileges. This requirement prevented users from deploying the transparent proxy in OpenShift environments where elevated pod permissions were prohibited. Consul v1.20 no longer requires elevated permissions for the transparent proxy in OpenShift deployments.
+
+- **Service dashboards for Grafana**: Consul v1.20 includes JSON templates for Grafana dashboards to help you monitor Consul services, service-to-service communication, and Consuld dataplane operations. You can [find these dashboards in the GitHub repo](https://github.com/hashicorp/consul/tree/main/grafana). For more information about configuring Consul to use Grafana, refer to [Configure Metrics for Consul on Kubernetes](/consul/docs/k8s/connect/observability/metrics).
+
+## Supported software
+
+This version of Consul on Kubernetes supports the following software versions:
+
+- Consul v1.20.x
+- Consul Dataplane v1.6.x. Refer to [Envoy and Consul Dataplane](/consul/docs/v1.19.x/connect/proxies/envoy#envoy-and-consul-dataplane) for details about Consul Dataplane versions and the available packaged Envoy version.
+- Kubernetes 1.28.x - 1.31.x
+- kubectl 1.28.x - 1.31.x
+- Helm 3.11.3+
+
+Refer to [Supported Consul and Kubernetes versions](/consul/docs/v1.19.x/k8s/compatibility#supported-consul-and-kubernetes-versions) for more information.
+
+## Upgrading
+
+For more detailed information, please refer to the [upgrade details page](/consul/docs/upgrading/upgrade-specific) and the changelogs.
+
+## Known issues
+
+There are no known issues at this time. These release notes are updated as issues are discovered.
+
+[Report issues on Github](https://github.com/hashicorp/consul-k8s/issues).
+
+## Changelogs
+
+The changelogs for this major release version and any maintenance versions are listed below.
+
+ These links take you to the changelogs on the GitHub website. 
+
+- [1.6.0](https://github.com/hashicorp/consul-k8s/releases/tag/v1.6.0)
diff --git a/website/content/docs/release-notes/consul-terraform-sync/v0_6_x.mdx b/website/content/docs/release-notes/consul-terraform-sync/v0_6_x.mdx
index b5ddfaea08..9f71f17d67 100644
--- a/website/content/docs/release-notes/consul-terraform-sync/v0_6_x.mdx
+++ b/website/content/docs/release-notes/consul-terraform-sync/v0_6_x.mdx
@@ -16,9 +16,9 @@ We have implemented the following features in this release:
 
 You can now execute Terraform tasks in `remote` or `cloud agent` mode. For more information, refer to the [per-task execution mode documentation](/consul/docs/nia/network-drivers/terraform-cloud#remote-workspaces).
 
-### HCP Consul Support  
+### HCP Consul Dedicated Support  
 
-CTS now supports interoperability with HCP Consul. CTS retrieves licenses from HCP Consul so that users can keep their HCP Consul license or Consul enterprise deployment license in sync.
+CTS now supports interoperability with HCP Consul Dedicated. CTS retrieves licenses from HCP Consul Dedicated so that users can keep their HCP Consul Dedicated license or Consul enterprise deployment license in sync.
 
 ### Auto Service-registration with Consul and Health API Endpoint 
 
@@ -36,7 +36,7 @@ CTS includes a new [API endpoint](/consul/docs/nia/api/health#health) that provi
 ## Supported Software Versions
 
 - Consul: v1.9+
--  HCP Consul: Latest
+-  HCP Consul Dedicated: Latest
 - Terraform CLI: v0.13 - v1.1
 -  HCP Terraform: Latest
 -  Terraform Enterprise: v202010-2 - Latest
diff --git a/website/content/docs/release-notes/consul/v1_17_x.mdx b/website/content/docs/release-notes/consul/v1_17_x.mdx
index da782d234b..378df6721e 100644
--- a/website/content/docs/release-notes/consul/v1_17_x.mdx
+++ b/website/content/docs/release-notes/consul/v1_17_x.mdx
@@ -15,13 +15,13 @@ We are pleased to announce the following Consul updates.
 
   These APIs are the foundation for future versions of Consul and enable new functionalities, such as multi-port and host-name-based canary routing and routing traffic through headless services in native Kubernetes deployments.
 
-  For more information, refer to the [Catalog API v2](/consul/docs/k8s/multiport#catalog-api-v2-beta) section in the documentation.
+  For more information, refer to the [Catalog API v2](/consul/docs/v1.17.x/k8s/multiport#catalog-api-v2-beta) section in the documentation.
 
    These APIs are in beta and under active development, so we do not recommend using them in production. 
 
 - **Multi-port services in Consul:** You can now register services with multiple ports per service. The v2 catalog API enables a single sidecar proxy to support workloads on different ports. This significantly reduces the operational overhead for managing Consul service mesh. Support for other runtimes outside of Kubernetes is planned for future releases of Consul.
 
-  Refer to the [Multi-port services for service mesh](/consul/docs/k8s/multiport#catalog-api-v2-beta) and [Configure multi-port services](/consul/docs/k8s/multiport/configure) for more information.
+  Refer to the [Multi-port services for service mesh](/consul/docs/v1.17.x/k8s/multiport#catalog-api-v2-beta) and [Configure multi-port services](/consul/docs/k8s/multiport/configure) for more information.
 
    Multi-port is currently a beta feature in Consul v1.17. 
 
diff --git a/website/content/docs/release-notes/consul/v1_18_x.mdx b/website/content/docs/release-notes/consul/v1_18_x.mdx
index e419118f42..24c23d0608 100644
--- a/website/content/docs/release-notes/consul/v1_18_x.mdx
+++ b/website/content/docs/release-notes/consul/v1_18_x.mdx
@@ -11,11 +11,11 @@ We are pleased to announce the following Consul updates.
 
 ## Release highlights
 
-- **Consul v2 Catalog API and Traffic Permission API updates:** Additional improvements were made to the v2 catalog API, which enables support for [multi-port services on Kubernetes](/consul/docs/k8s/multiport). End user facing changes include the ability to use a service's ports in xRoute configurations as opposed to the workload port for reference in the parentRef stanza of the xRoute configuration. In addition, the Traffic Permissions API was changed to only allow `ACTION_DENY` for Consul Enterprise as it will enable future governance related workflows. 
+- **Consul v2 Catalog API and Traffic Permission API updates:** Additional improvements were made to the v2 catalog API, which enables support for [multi-port services on Kubernetes](/consul/docs/k8s/multiport). End user facing changes include the ability to use a service's ports in xRoute configurations as opposed to the workload port for reference in the parentRef stanza of the xRoute configuration. In addition, the Traffic Permissions API was changed to only allow `ACTION_DENY` for Consul Enterprise as it will enable future governance related workflows.
 
   The v2 Catalog API is currently available for Consul on Kubernetes deployments only. Refer to [Consul v2 architecture](/consul/docs/architecture/v2) for more information.
 
-   The v2 Catalog API and the Traffic Permssions API are in beta and under active development, so we do not recommend using them in production. 
+   The v2 Catalog API and the Traffic Permissions API are in beta and under active development, so we do not recommend using them in production. 
 
 - **Consul Long Term Support (LTS) (Enterprise):** Consul Enterprise users can now receive Long Term Support for approximately 2 years on some Consul releases, starting with Consul Enterprise v1.15. During the LTS window, eligible fixes are provided through a new minor release on the affected LTS release branch.
 
@@ -37,7 +37,7 @@ We are pleased to announce the following Consul updates.
 
   Refer to [transparent proxy overview](/consul/docs/k8s/connect/transparent-proxy) and [Consul on ECS overview](/consul/docs/ecs) for more information.
 
-- **Downgrade from Consul Enterprise to Consul Community Edition**: Consul now provides the ability for enterprise users to migrate their deployments to Community edition and disable enterprise features for business continuity. Refer to [Downgrade from Consul Enterprise to the community edition](/consul/docs/enterprise/ent-to-ce-downgrades) for more information. 
+- **Downgrade from Consul Enterprise to Consul Community Edition**: Consul now provides the ability for enterprise users to migrate their deployments to Community edition and disable enterprise features for business continuity. Refer to [Downgrade from Consul Enterprise to the community edition](/consul/docs/enterprise/ent-to-ce-downgrades) for more information.
 
 - **Consul Snapshot Agent support for multiple destinations (Enterprise):** Consul Enterprise users can now specify [multiple local and remote destinations](/consul/commands/snapshot/agent) for Consul snapshot backups.
 
diff --git a/website/content/docs/release-notes/consul/v1_19_x.mdx b/website/content/docs/release-notes/consul/v1_19_x.mdx
index f89cdc71b6..1c753d00f3 100644
--- a/website/content/docs/release-notes/consul/v1_19_x.mdx
+++ b/website/content/docs/release-notes/consul/v1_19_x.mdx
@@ -11,7 +11,19 @@ We are pleased to announce the following Consul updates.
 
 ## Release highlights
 
-- TBD
+- **External Services CRD**: You can now configure and register external services, including their health checks, alongside existing Kubernetes application manifests with the new [`Registration` Custom Resource Definition (CRD)](/consul/docs/connect/config-entries/registration). This CRD changes the workflow to register a service running on an external node with the Consul catalog. A terminating gateway is still required to enable downstream services in the service mesh to communicate with external services. Refer to [Register external services on Kubernetes overview](/consul/docs/k8s/deployment-configurations/external-service) for more information.
+
+- **Transparent Proxy on Nomad**: Consul’s CNI plugin enables the use of transparent proxy for seamlessly redirecting traffic through the Envoy proxy without requiring application changes, or elevated network privileges for the workload. As a result, you can onboard applications without additional configuration between a service and its upstreams.
+
+- **API Gateway metrics**: The Consul API Gateway now provides a Prometheus metrics endpoint you can use to gather information about the health of the gateway, as well as traffic for proxied connections or requests.
+
+- **File system certificate configuration entry**: A new [`file-system-certificate` configuration entry](/consul/docs/connect/config-entries/file-system-certificate) supports specifying a filepath to the certificate and private key for Consul API Gateway on VMs on the local system. Previously, the certificate and private key were specified directly in the `inline-certificate` configuration entry. When using the file system certificates, the Consul server never sees the contents of these files.
+
+  File system certificates also include a file system watch that allows for changing the certificate and key without restarting the gateway. This feature requires that you have access to the gateway’s file system in order to place the certificate or update it.
+
+  Consul on Kubernetes deployments that use `consul-k8s` Helm chart v1.5.0 or later use file system certificates without additional configuration. For more information, refer to [File system certificate configuration reference](/consul/docs/connect/config-entries/file-system-certificate).
+
+- **Argo Rollouts Plugin**: A new Argo Rollouts plugin for progressive delivery is now available for `consul-k8s`. This plugin supports canary deployments by allowing you to incrementally release and test new versions of applications and roll back to previous versions by splitting traffic between subsets of services. Refer to [Argo Rollouts Progressive Delivery with Consul on Kubernetes](/consul/docs/k8s/deployment-configurations/argo-rollouts-configuration) for more information.
 
 ## What's deprecated
 
@@ -25,6 +37,16 @@ For more detailed information, please refer to the [upgrade details page](/consu
 
 The following issues are known to exist in the v1.19.x releases:
 
+- v1.19.0 & v1.19.1 - There are known issues with the DNS server implementation.
+To revert to the old DNS behavior on 1.19.0 and 1.19.1, set `experiments: [ "v1dns" ]` in the agent configuration.
+In v1.19.2, the modified DNS subsystem will be reverted and the old DNS behavior will be restored resolving these issues.
+  - DNS SRV records for registrations that specify a service address instead of a node address return identical `.service` hostnames instead of unique `.addr` addresses.
+  As a result, it is impossible to resolve the individual service addresses.
+  This bug can affect Nomad installations using Consul for service discovery because the service address field is always specified to Consul.
+  [[GH-21325](https://github.com/hashicorp/consul/issues/21325)].
+  - DNS Tags are not resolved when using the Standard query format, `tag.name.service.consul`.
+  [[GH-21326](https://github.com/hashicorp/consul/issues/21336)].
+
 ## Changelogs
 
 The changelogs for this major release version and any maintenance versions are listed below.
diff --git a/website/content/docs/release-notes/consul/v1_20_x.mdx b/website/content/docs/release-notes/consul/v1_20_x.mdx
new file mode 100644
index 0000000000..17e19a3de5
--- /dev/null
+++ b/website/content/docs/release-notes/consul/v1_20_x.mdx
@@ -0,0 +1,38 @@
+---
+layout: docs
+page_title: 1.20.x
+description: >-
+  Consul release notes for version 1.20.x
+---
+
+# Consul 1.20.0
+
+We are pleased to announce the following Consul updates.
+
+## Release highlights
+
+- **Consul DNS views for Kubernetes**: Consul on Kubernetes now supports scheduling a dedicated Consul DNS proxy in a Kubernetes Pod instead of using client agents or dataplanes. The proxy is designed for deployment in a Kubernetes cluster with [external servers enabled](/consul/docs/k8s/deployment-configurations/servers-outside-kubernetes) to provide applications in Kubernetes the ability to resolve Consul DNS addresses. You can use the Consul DNS proxy to enable service discovery across admin partitions in Kubernetes deployments. For more information, refer to [Consul DNS views for Kubernetes](/consul/docs/k8s/dns/views).
+
+- **Catalog sync improvements**: Consul v1.20 includes improved metrics for Consul catalog sync. Consul catalog sync is a feature that automatically registers Kubernetes services with Consul’s catalog. Prior to this enhancement, operators had limited visibility into the status of Kubernetes services being synced to Consul and the performance of the sync catalog process. At scale, it was difficult to discern if the sync process was healthy and progressing normally. Catalog sync enhancements now provide more insights that include status and performance metrics of the sync process. New performance metrics include the rate of registered or deregistered services, the status and health of the sync process, and additional metadata related to registered services.
+
+- **OpenShift improvements**: Prior to this release, deploying Consul service mesh on Kubernetes required the transparent proxy to have elevated permissions through `anyuid` security context constraint (SCC) privileges. This requirement prevented users from deploying the transparent proxy in OpenShift environments where elevated pod permissions were prohibited. Consul v1.20 no longer requires elevated permissions for the transparent proxy in OpenShift deployments.
+
+- **Service dashboards for Grafana**: Consul v1.20 includes JSON templates for Grafana dashboards to help you monitor Consul services, service-to-service communication, and Consuld dataplane operations. You can [find these dashboards in the GitHub repo](https://github.com/hashicorp/consul/tree/main/grafana). For more information about configuring Consul to use Grafana, refer to [Configure Metrics for Consul on Kubernetes](/consul/docs/k8s/connect/observability/metrics).
+
+## Upgrading
+
+For more detailed information, please refer to the [upgrade details page](/consul/docs/upgrading/upgrade-specific) and the changelogs.
+
+## Known issues
+
+There are no known issues at this time. These release notes are updated as issues are discovered.
+
+[Report issues on Github](https://github.com/hashicorp/consul/issues).
+
+## Changelogs
+
+The changelogs for this major release version and any maintenance versions are listed below.
+
+ These links take you to the changelogs on the GitHub website. 
+
+- [1.20.0](https://github.com/hashicorp/consul/releases/tag/v1.20.0)
diff --git a/website/content/docs/security/acl/acl-roles.mdx b/website/content/docs/security/acl/acl-roles.mdx
index 5812703831..834e433349 100644
--- a/website/content/docs/security/acl/acl-roles.mdx
+++ b/website/content/docs/security/acl/acl-roles.mdx
@@ -62,7 +62,7 @@ Roles may contain the following attributes:
 - `Name`: A unique meaningful name for the role. You can specify the role `Name` when linking it to tokens.
 - `Description`: (Optional) A human-readable description of the role.
 - `Policies`: Specifies a the list of policies that are applicable for the role. The object can reference the policy `ID` or `Name` attribute.
-- `TemplatedPolicies`: Specifies a list of templated polcicies that are applicable for the role. See [Templated Policies](#templated-policies) for details.
+- `TemplatedPolicies`: Specifies a list of templated policies that are applicable for the role. See [Templated Policies](#templated-policies) for details.
 - `ServiceIdentities`: Specifies a list of services that are applicable for the role. See [Service Identities](#service-identities) for details.
 - `NodeIdentities`: Specifies a list of nodes that are applicable for the role. See [Node Identities](#node-identities) for details.
 - `Namespace`:  The namespace that the policy resides in. Roles can only be linked to policies that are defined in the same namespace. See [Namespaces](/consul/docs/enterprise/namespaces) for additional information. Requires Consul Enterprise 1.7.0+
@@ -72,7 +72,7 @@ Roles may contain the following attributes:
 
 You can specify a templated policy when configuring roles or linking tokens to policies. Templated policies enable you to quickly construct policies for common Consul use cases, rather than creating identical policies for each use cases.
 
-Consul uses templated policies during the authorization process to automatically generate a policy for the use case specified. Consul links the generated policy to the role or token so that it will have permission for the specific use case.  
+Consul uses templated policies during the authorization process to automatically generate a policy for the use case specified. Consul links the generated policy to the role or token so that it will have permission for the specific use case.
 
 ### Templated policy specification
 
@@ -398,4 +398,4 @@ service_prefix "" {
 }
 ```
 
-
\ No newline at end of file
+
diff --git a/website/content/docs/security/acl/acl-rules.mdx b/website/content/docs/security/acl/acl-rules.mdx
index 24f6069c08..a89985d528 100644
--- a/website/content/docs/security/acl/acl-rules.mdx
+++ b/website/content/docs/security/acl/acl-rules.mdx
@@ -19,7 +19,6 @@ The following table provides an overview of the resources you can use to create
 | `partition`
`partition_prefix` | Controls access to one or more admin partitions.
See [Admin Partition Rules](#admin-partition-rules) for details. | Yes | | `agent`
`agent_prefix` | Controls access to the utility operations in the [Agent API](/consul/api-docs/agent), such as `join` and `leave`.
See [Agent Rules](#agent-rules) for details. | Yes | | `event`
`event_prefix` | Controls access to event operations in the [Event API](/consul/api-docs/event), such as firing and listing events.
See [Event Rules](#event-rules) for details. | Yes | -| `identity`
`identity_prefix` | Controls access to workload identity operations in the [Catalog v2 group](/consul/docs/architecture/v2/catalog). | `key`
`key_prefix`   | Controls access to key/value store operations in the [KV API](/consul/api-docs/kv).
Can also use the `list` access level when setting the policy disposition.
Has additional value options in Consul Enterprise for integrating with [Sentinel](https://docs.hashicorp.com/sentinel/consul).
See [Key/Value Rules](#key-value-rules) for details. | Yes | | `keyring`       | Controls access to keyring operations in the [Keyring API](/consul/api-docs/operator/keyring).
See [Keyring Rules](#keyring-rules) for details. | No | | `mesh`       | Provides operator-level permissions for resources in the admin partition, such as ingress gateways or mesh proxy defaults. See [Mesh Rules](#mesh-rules) for details. | No | @@ -248,48 +247,6 @@ operation, so to enable this feature in a Consul environment with ACLs enabled, give agents a token with access to this event prefix, in addition to configuring [`disable_remote_exec`](/consul/docs/agent/config/config-files#disable_remote_exec) to `false`. -## Identity Rules - -The `identity` and `identity_prefix` resources control workload-identity-level registration and read access to the [Catalog v2 API group](/consul/docs/architecture/v2/catalog). -Specify the resource label in identity rules to set the scope of the rule. -The resource label in the following example is empty. As a result, the rules allow read-only access to any workload identity name with the empty prefix. -The rules also allow read-write access to the `app` identity and deny all access to the `admin` identity: - - - -```hcl -identity_prefix "" { - policy = "read" -} -identity "app" { - policy = "write" -} -identity "admin" { - policy = "deny" -} -``` - -```json -{ - "identity_prefix": { - "": { - "policy": "read" - } - }, - "identity": { - "app": { - "policy": "write" - }, - "admin": { - "policy": "deny" - } - } -} -``` - - - - ## Key/Value Rules The `key` and `key_prefix` resources control access to key/value store operations in the [KV API](/consul/api-docs/kv). @@ -638,7 +595,7 @@ Read access to all imported nodes is granted when either of the following rule s - `service:write` is granted to any service. - `node:read` is granted to all nodes. -For Consul Enterprise, either set of rules must be scoped to the requesting services's partition and at least one namespace. +For Consul Enterprise, either set of rules must be scoped to the requesting service's partition and at least one namespace. You may need similarly scoped [Service Rules](#reading-imported-services) to read Consul data, depending on the endpoint (e.g. `/v1/health/service/:name`). These permissions are satisfied when using a [service identity](/consul/docs/security/acl/acl-roles#service-identities). @@ -877,7 +834,7 @@ Read access to all imported services is granted when either of the following rul - `service:write` is granted to any service. - `service:read` is granted to all services. -For Consul Enterprise, either set of rules must be scoped to the requesting services's partition and at least one namespace. +For Consul Enterprise, either set of rules must be scoped to the requesting service's partition and at least one namespace. You may need similarly scoped [Node Rules](#reading-imported-nodes) to read Consul data, depending on the endpoint (e.g. `/v1/health/service/:name`). These permissions are satisfied when using a [service identity](/consul/docs/security/acl/acl-roles#service-identities). diff --git a/website/content/docs/security/acl/index.mdx b/website/content/docs/security/acl/index.mdx index b4f589447e..f8b8b97ec8 100644 --- a/website/content/docs/security/acl/index.mdx +++ b/website/content/docs/security/acl/index.mdx @@ -9,20 +9,11 @@ description: >- This topic describes core concepts associated with the optional access control list (ACL) system shipped with Consul. ACLs authenticate requests and authorize access to resources. They also control access to the Consul UI, API, and CLI, as well as secure service-to-service and agent-to-agent communication. -Refer to the following tutorials for step-by-step instructions on how to get started using ACLs: - -- [Bootstrap and Explore ACLs] -- [Secure Consul with ACLs] -- [Troubleshoot the ACL System](/consul/tutorials/security/access-control-troubleshoot) - -[bootstrap and explore acls]: /consul/tutorials/security/access-control-setup-production?utm_source=docs -[secure consul with acls]: /consul/tutorials/security/access-control-setup-production - Refer to the [ACL API reference](/consul/api-docs/acl) and [ACL CLI reference](/consul/commands/acl) for additional usage information. ## Workflow overview -Implementations may vary depending on the needs of the organization, but the following procedure describes the basic workflow for for creating and implementing ACLs: +Implementations may vary depending on the needs of the organization, but the following procedure describes the basic workflow for creating and implementing ACLs: 1. The person responsible for administrating ACLs in your organization specifies one or more authentication rules to define a [policy](#policies). 1. The ACL administrator uses the Consul API to generate and link a [token](#tokens) to one or more policies. The following diagram illustrates the relationship between rules, policies, and tokens: diff --git a/website/content/docs/security/encryption.mdx b/website/content/docs/security/encryption.mdx deleted file mode 100644 index 6cbbf6b321..0000000000 --- a/website/content/docs/security/encryption.mdx +++ /dev/null @@ -1,111 +0,0 @@ ---- -layout: docs -page_title: Encryption Systems -description: >- - Consul supports encrypting all of its network traffic. Remote Process Calls (RPCs) between client and server agents can be encrypted with TLS and authenticated with certificates. Gossip communication between all agents can also be encrypted. ---- - -# Encryption - -The Consul agent supports encrypting all of its network traffic. The exact -method of encryption is described on the [encryption internals page](/consul/docs/security). -There are two separate encryption systems, one for gossip traffic and one for RPC. - -To configure the encryption systems on a new cluster, review this following tutorials to -[enable gossip encryption](/consul/tutorials/security/gossip-encryption-secure?utm_source=consul.io&utm_medium=docs) and -[TLS encryption for agent communication](/consul/tutorials/security/tls-encryption-secure?utm_source=docs). - -## Gossip Encryption - -Enabling gossip encryption only requires that you set an encryption key when -starting the Consul agent. The key can be set via the `encrypt` parameter. - -~> **WAN Joined Datacenters Note:** If using multiple WAN joined datacenters, be sure to use _the same encryption key_ in all datacenters. - -The key must be 32-bytes, Base64 encoded. As a convenience, Consul provides the -[`consul keygen`](/consul/commands/keygen) command to generate a -cryptographically suitable key: - -```shell-session -$ consul keygen -pUqJrVyVRj5jsiYEkM/tFQYfWyJIv4s3XkvDwy7Cu5s= -``` - -With that key, you can enable encryption on the agent. If encryption is enabled, -the output of [`consul agent`](/consul/commands/agent) will include "Encrypt: true": - -```shell-session -$ cat encrypt.json -{"encrypt": "pUqJrVyVRj5jsiYEkM/tFQYfWyJIv4s3XkvDwy7Cu5s="} - -$ consul agent -data-dir=/tmp/consul -config-file=encrypt.json -==> WARNING: LAN keyring exists but -encrypt given, using keyring -==> WARNING: WAN keyring exists but -encrypt given, using keyring -==> Starting Consul agent... -==> Starting Consul agent RPC... -==> Consul agent running! - Node name: 'Armons-MacBook-Air.local' - Datacenter: 'dc1' - Server: false (bootstrap: false) - Client Addr: 127.0.0.1 (HTTP: 8500, HTTPS: -1, DNS: 8600, RPC: 8400) - Cluster Addr: 10.1.10.12 (LAN: 8301, WAN: 8302) - Gossip encrypt: true, RPC-TLS: false, TLS-Incoming: false -... -``` - -All nodes within a Consul cluster must share the same encryption key in -order to send and receive cluster information. - -## Configuring Gossip Encryption on an existing cluster - -As of version 0.8.4, Consul supports upshifting to encrypted gossip on a running cluster -through the following process. Review this [step-by-step tutorial](/consul/tutorials/security/gossip-encryption-secure?utm_source=consul.io&utm_medium=docs#enable-gossip-encryption-existing-cluster) -to encrypt gossip on an existing cluster. - -## RPC Encryption with TLS - -Consul supports using TLS to verify the authenticity of servers and clients. To enable this, -Consul requires that all clients and servers have key pairs that are generated by a single -Certificate Authority. This can be a private CA, used only internally. The -CA then signs keys for each of the agents, as in -[this tutorial on generating both a CA and signing keys](/consul/tutorials/security/tls-encryption-secure?utm_source=docs). - -~> Certificates need to be created with x509v3 extendedKeyUsage attributes for both clientAuth and serverAuth since Consul uses a single cert/key pair for both server and client communications. - -TLS can be used to verify the authenticity of the servers or verify the authenticity of clients. -These modes are controlled by the [`verify_outgoing`](/consul/docs/agent/config/config-files#tls_internal_rpc_verify_outgoing), -[`verify_server_hostname`](/consul/docs/agent/config/config-files#tls_internal_rpc_verify_server_hostname), -and [`verify_incoming`](/consul/docs/agent/config/config-files#tls_internal_rpc_verify_incoming) options, respectively. - -If [`verify_outgoing`](/consul/docs/agent/config/config-files#tls_internal_rpc_verify_outgoing) is set, agents verify the -authenticity of Consul for outgoing connections. Server nodes must present a certificate signed -by a common certificate authority present on all agents, set via the agent's -[`ca_file`](/consul/docs/agent/config/config-files#tls_internal_rpc_ca_file) and [`ca_path`](/consul/docs/agent/config/config-files#tls_internal_rpc_ca_path) -options. All server nodes must have an appropriate key pair set using [`cert_file`](/consul/docs/agent/config/config-files#tls_internal_rpc_cert_file) and [`key_file`](/consul/docs/agent/config/config-files#tls_internal_rpc_key_file). - -If [`verify_server_hostname`](/consul/docs/agent/config/config-files#tls_internal_rpc_verify_server_hostname) is set, then -outgoing connections perform hostname verification. All servers must have a certificate -valid for `server..` or the client will reject the handshake. This is -a new configuration as of 0.5.1, and it is used to prevent a compromised client from being -able to restart in server mode and perform a MITM (Man-In-The-Middle) attack. New deployments should set this -to true, and generate the proper certificates, but this is defaulted to false to avoid breaking -existing deployments. - -If [`verify_incoming`](/consul/docs/agent/config/config-files#tls_internal_rpc_verify_incoming) is set, the servers verify the -authenticity of all incoming connections. All clients must have a valid key pair set using -[`cert_file`](/consul/docs/agent/config/config-files#tls_internal_rpc_cert_file) and -[`key_file`](/consul/docs/agent/config/config-files#tls_internal_rpc_key_file). Servers will -also disallow any non-TLS connections. To force clients to use TLS, -[`verify_outgoing`](/consul/docs/agent/config/config-files#tls_internal_rpc_verify_outgoing) must also be set. - -TLS is used to secure the RPC calls between agents, but gossip between nodes is done over UDP -and is secured using a symmetric key. See above for enabling gossip encryption. - -## Configuring TLS on an existing cluster - -As of version 0.8.4, Consul supports migrating to TLS-encrypted traffic on a running cluster -without downtime. This process assumes a starting point with no TLS settings configured and involves -an intermediate step in order to get to full TLS encryption. Review the -[Securing RPC Communication with TLS Encryption tutorial](/consul/tutorials/security/tls-encryption-secure?utm_source=docs) -for the step-by-step process to configure TLS on a new or existing cluster. Note the call outs there -for existing cluster configuration. diff --git a/website/content/docs/security/encryption/gossip.mdx b/website/content/docs/security/encryption/gossip.mdx new file mode 100644 index 0000000000..4a04e16bd7 --- /dev/null +++ b/website/content/docs/security/encryption/gossip.mdx @@ -0,0 +1,302 @@ +--- +layout: docs +page_title: Manage gossip encryption +description: >- + Consul supports encrypting all of its network traffic. Learn how to secure gossip communication between all agents by creating and rotating a symmetric key. +--- + +# Manage gossip encryption + +This topic describes the steps to enable gossip encryption on a Consul datacenter and rotate the gossip encryption key to maintain secure communication between agents. + +## Workflows + +We recommend enabling gossip encryption to all new deployed Consul datacenters. You can also update the agents in an existing datacenter to use gossip encryption. + +The workflow to enable gossip encryption changes depending on whether your datacenter has client agents running. + +To [enable gossip encryption on a new datacenter](#enable-gossip-encryption-on-a-new-datacenter): + +1. Use `consul keygen` to generate a new gossip encryption key. +1. Create a configuration file that sets the `encrypt` parameter to the newly generated key. +1. Distribute the configuration file to all agent nodes that are part of the datacenter. Then start the Consul agent on all the nodes. + +To [enable gossip encryption on an existing datacenter](#enable-gossip-encryption-on-an-existing-datacenter): + +1. Use `consul keygen` to generate a new gossip encryption key. +1. Create a configuration file that sets the `encrypt` parameter to the newly generated key and sets `encrypt_verify_incoming` and `encrypt_verify_outgoing` to `false`. +1. Distribute the configuration file to all agent nodes that are part of the datacenter. Then perform a rolling restart of all Consul agents. +1. Update the `encrypt_verify_outgoing` setting to `true` and perform a rolling restart of all Consul agents. +1. Update the `encrypt_verify_incoming` setting to `true` and perform a rolling restart of all Consul agents. + +If you have multiple datacenters joined in WAN federation, be sure to use _the same encryption key_ in all datacenters. + +## Enable gossip encryption on a new datacenter + +We recommend enabling gossip encryption on all new Consul datacenters. + +### Generate a gossip encryption key + +First, generate an encryption key on a Consul server. The Consul CLI includes a `consul keygen` command to generate a key. + +```shell-session +$ consul keygen +YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA= +``` + +You can generate a new gossip key using any method that can create 32 random bytes encoded in base64. For example, you can use `openssl` or `dd` to create a key on Linux with one of the following commands: + +- `openssl rand -base64 32` +- `dd if=/dev/urandom bs=32 count=1 status=none | base64` + +### Add the key to the agent configuration + +Create a configuration that sets `encrypt` parameter to the newly generated key. You can edit the existing agent configuration, or you can add a file to the configuration directory. For more information, refer to [configuring Consul agents](/consul/docs/agent#configuring-consul-agents). + + + +```hcl +encrypt = "YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA=" +``` + +```json +{ + "encrypt": "YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA=" +} +``` + + + +### Add the key to all agent configurations + +Distribute the gossip key to all the agent nodes that need to be pert of the datacenter. Then start the Consul agent on all the nodes. + +When gossip encryption is properly configured, `Gossip Encryption: true` appears in the Consul logs at startup. + + + +```log +==> Starting Consul agent... + Version: '1.19.0' +Build Date: '2024-06-12 13:59:10 +0000 UTC' + Node ID: 'e74b1ade-e932-1707-cdf1-6579b8b2536c' + Node name: 'consul-server-0' +Datacenter: 'dc1' (Segment: '') +Server: true (Bootstrap: false) + Client Addr: [127.0.0.1] (HTTP: 8500, HTTPS: 8443, gRPC: -1, gRPC-TLS: 8503, DNS: 53) + Cluster Addr: 172.19.0.7 (LAN: 8301, WAN: 8302) + Gossip Encryption: true + Auto-Encrypt-TLS: true + ACL Enabled: true + Reporting Enabled: false +ACL Default Policy: deny + HTTPS TLS: Verify Incoming: false, Verify Outgoing: true, Min Version: TLSv1_2 + gRPC TLS: Verify Incoming: false, Min Version: TLSv1_2 + Internal RPC TLS: Verify Incoming: true, Verify Outgoing: true (Verify Hostname: true), Min Version: TLSv1_2 +## ... +``` + + + +## Enable gossip encryption on an existing datacenter + +You can also enable gossip encryption on existing Consul datacenters. + +### Generate a gossip encryption key + +First, generate an encryption key on a Consul server. The Consul CLI includes a `consul keygen` command to generate a key. + +```shell-session +$ consul keygen +YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA= +``` + +### Add the key to the agent configuration + +Create a configuration that sets the `encrypt` parameter to the newly generated key and sets the `encrypt_verify_incoming` and `encrypt_verify_outgoing` parameters to `false`. You can edit the existing agent configuration, or you can add a file to the configuration directory. For more information, refer to [configuring Consul agents](/consul/docs/agent#configuring-consul-agents). + + + + + +```hcl +encrypt = "YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA=" +encrypt_verify_incoming = false +encrypt_verify_outgoing = false +``` + + + + + +```json +{ +"encrypt": "YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA=", +"encrypt_verify_incoming" : false, +"encrypt_verify_outgoing" : false +} +``` + + + + + +### Add the configuration to all agents + +Distribute the configuration to all the agent nodes that need to be part of the datacenter, and then initiate a rolling update that restarts each agent. + +You must restart all of the agents. The `consul reload` and `kill -HUP ` commands are not sufficient when changing the gossip configuration. + +### Update outgoing encryption + +The agents can decrypt gossip communication with the `encrypt` parameter set, but they are not able to send encrypted traffic. + +Update the `encrypt_verify_outgoing` setting to `true` and then perform another rolling update of all Consul agents. Complete the process on all the nodes before you begin [updates to incoming encryption](#update-the-incoming-encryption). + + + + + +```hcl +encrypt = "YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA=" +encrypt_verify_incoming = false +encrypt_verify_outgoing = true + ``` + + + + + +```json +{ +"encrypt": "YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA=", +"encrypt_verify_incoming": false, +"encrypt_verify_outgoing": true +} +``` + + + + + +### Update incoming encryption + +The agents can send encrypted gossip but still allow unencrypted incoming traffic. Update the `encrypt_verify_incoming` setting to `true` and then perform a final rolling update on all the agents. + + + + + +```hcl +encrypt = "YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA=" +encrypt_verify_incoming = true +encrypt_verify_outgoing = true +``` + + + + + +```json +{ +"encrypt": "YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA=", +"encrypt_verify_incoming": true, +"encrypt_verify_outgoing": true +} +``` + + + + + +## Rotate the gossip encryption key + +It is important to periodically rotate the gossip encryption key your Consul datacenter uses. + +The process of rotating the gossip encryption key is centralized so that you can perform it on a single datacenter node. + +The process to rotate a gossip encryption key consists of the following steps: + +1. Generate a new encryption key using the `consul keygen` command. +1. Install the new encryption key using the `consul keyring -install` command. +1. Verify the new key is installed in your Consul datacenter with the `consul keyring -list` command. +1. Instruct Consul to use the new key with the `consul keyring -use` command. +1. Remove the old key using the `consul keyring -remove` command. + +### Generate a new encryption key + +Generate a new key using `consul keygen`: + +```shell-session +$ consul keygen +FfRV9j6NXU9LlCI4zLZjjpZdj4Nrqsdm7R8YgzSHzHw= +``` + +### Add new key to the keyring + +Add your newly generated key to the keyring. + +```shell-session +$ consul keyring -install FfRV9j6NXU9LlCI4zLZjjpZdj4Nrqsdm7R8YgzSHzHw= +==> Installing new gossip encryption key... +``` + +### Verify that the new key is installed + +After you add the key to one of the Consul agents, Consul propagates it across the entire datacenter. You do not need to repeat the command on other agents. + +To ensure that the key has been propagated to all agents, list the installed encryption keys and verify that the number of agents that recognize the key is equal to the total number of agents in the datacenter. + +```shell-session +$ consul keyring -list +==> Gathering installed encryption keys... + +WAN: + FfRV9j6NXU9LlCI4zLZjjpZdj4Nrqsdm7R8YgzSHzHw= [1/1] + YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA= [1/1] + +dc1 (LAN): + YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA= [7/7] + FfRV9j6NXU9LlCI4zLZjjpZdj4Nrqsdm7R8YgzSHzHw= [7/7] +``` + +Confirm that the two keys are installed in the datacenter and recognized by all agents, including server agents. The server agents are listed in the `WAN` section. Do not proceed to the next step unless all agents have the new key. + +### Promote the new key to primary + +After all agents recognize the key, it is possible to promote it to be the new primary encryption key. + +```shell-session +$ consul keyring -use FfRV9j6NXU9LlCI4zLZjjpZdj4Nrqsdm7R8YgzSHzHw= +==> Changing primary gossip encryption key... +``` + +### Remove the old key from the keyring + +Unused keys in the keyring are a potential security risk to your Consul cluster. We recommended that you remove the former primary key from the keyring after a new key is installed. + +```shell-session +$ consul keyring -remove YwgWlBvicJN17UOYcutXLpJSZsw5aWpLEEWqgK635zA= +==> Removing gossip encryption key... +``` + +Verify that the keyring contains only one key. + +```shell-session +$ consul keyring -list +==> Gathering installed encryption keys... + +WAN: + FfRV9j6NXU9LlCI4zLZjjpZdj4Nrqsdm7R8YgzSHzHw= [1/1] + +dc1 (LAN): + FfRV9j6NXU9LlCI4zLZjjpZdj4Nrqsdm7R8YgzSHzHw= [7/7] +``` + +## Next steps + +Documentation for the commands used in this topic is available at [Consul agent configuration - Encryption Parameters](/consul/docs/agent/config/config-files#encryption-parameters). You can find more information over the gossip protocol used by Consul at [Gossip Protocol](/consul/docs/architecture/gossip). + +After you enable gossip encryption, you can continue to process to secure your Consul datacenter by enabling mutual TLS encryption. For more information, refer to [Mutual TLS (mTLS) Encryption](/consul/docs/security/encryption/mtls). + +To learn how to automate gossip key rotation using HashiCorp Vault and consul-template, refer to the [Automatically Rotate Gossip Encryption Keys Secured in Vault tutorial](/consul/tutorials/operate-consul/vault-kv-consul-secure-gossip). \ No newline at end of file diff --git a/website/content/docs/security/encryption/index.mdx b/website/content/docs/security/encryption/index.mdx new file mode 100644 index 0000000000..60a5352b8a --- /dev/null +++ b/website/content/docs/security/encryption/index.mdx @@ -0,0 +1,55 @@ +--- +layout: docs +page_title: Encrypted communication between Consul agents +description: >- + Consul supports encrypting all of its network traffic. Remote Procedure Calls (RPCs) between client and server agents can be encrypted with TLS and authenticated with certificates. Gossip communication between all agents can also be encrypted. +--- + +# Encrypted communication between Consul agents + +This topic provides an overview of the two distinct encryption systems available in Consul. [Gossip encryption](/consul/docs/security/encryption#gossip-encryption) and [Mutual TLS encryption](/consul/docs/security/encryption#mutual-tls-mtls-encryption) are the foundations of a secure Consul datacenter. + +The guidelines in the [Consul security model](/consul/docs/security/security-models/core) for operating a secure Consul deployment recommends using both encryption systems. + +## Gossip Encryption + +Consul uses a [gossip protocol](/consul/docs/architecture/gossip) to perform the following cluster operations: + +- Identify datacenter members. +- Quickly detect failed members and notify the rest of the cluster. +- Broadcast events and queries that can trigger custom workflows. + +The gossip protocol, as well as its membership management and message broadcasting features, use the [Serf library](https://github.com/hashicorp/serf/). + +In a default Consul configuration, the gossip protocol uses [port `8301`](/consul/docs/install/ports#lan-serf) for LAN communications and [port `8302`](/consul/docs/install/ports#lan-serf) for WAN communications between federated datacenters. Enabling gossip encryption on a Consul datacenter is required to secure traffic on these two ports. + +Gossip encryption is symmetric and based on a single key that is shared across all members of the datacenter. You can configure gossip encryption in Consul using the following parameters: + +- [`encrypt`](/consul/docs/agent/config/config-files#encrypt). +- [`encrypt_verify_incoming`](/consul/docs/agent/config/config-files#encrypt_verify_incoming). Only used when upshifting from unencrypted to encrypted gossip on a running cluster. +- [`encrypt_verify_outgoing`](/consul/docs/agent/config/config-files#encrypt_verify_outgoing). Only used when upshifting from unencrypted to encrypted gossip on a running cluster. + +To learn more about enabling gossip encryption on your Consul datacenter and rotating gossip keys, refer to [manage gossip encryption](/consul/docs/security/encryption/gossip). + +## Mutual TLS (mTLS) Encryption + +Consul uses several communication protocols over different ports that you can secure using mTLS: + +- A [consensus protocol](/consul/docs/architecture/consensus) provides data consistency between Consul servers. It typically uses [port `8300`](/consul/docs/install/ports#server-rpc). +- Remote Procedure Calls (RPC) forward requests from client agents to server agents. They use the same port the consensus protocol uses. +- An HTTP or HTTPS interface permits client communication with the Consul API, CLI, and UI. It typically uses [port `8500`](/consul/docs/install/ports#http) and [port `8501`](/consul/docs/install/ports#https). +- A gRPC interface receives incoming traffic from the gateways and Envoy proxies registered to the agent node. It typically uses [port `8502`](/consul/docs/install/ports#client-grpc) and [port `8503`](/consul/docs/install/ports#client-grpc-tls). + +Consul uses mTLS to verify the authenticity of server and client agents. It requires that all clients and servers have key pairs that are generated by a single Certification Authority (CA). We recommend using a private CA that is not shared with other applications. + +You can configure mTLS in Consul using the [`tls` stanza in agent configuration files](/consul/docs/agent/config/config-files#tls-1). + +You can configure mTLS encryption for each protocol separately using the following parameters in the agent configuration file: + +- [`tls.defaults`](/consul/docs/agent/config/config-files#tls_defaults) provides default settings that Consul applies to every interface unless explicitly overridden by protocol-specific configurations. +- [`tls.internal_rpc`](/consul/docs/agent/config/config-files#tls_internal_rpc) provides settings for the internal server RPC interface. +- [`tls.https`](/consul/docs/agent/config/config-files#tls_https) provides settings for the HTTP/HTTPS interface. +- [`tls.grpc`](/consul/docs/agent/config/config-files#tls_grpc) provides settings for the gRPC/xDS interface. + +To learn more about enabling mTLS on your Consul datacenter, refer to [Manage mTLS encryption](/consul/docs/security/encryption/mTLS). + diff --git a/website/content/docs/security/encryption/mtls.mdx b/website/content/docs/security/encryption/mtls.mdx new file mode 100644 index 0000000000..5017c7b509 --- /dev/null +++ b/website/content/docs/security/encryption/mtls.mdx @@ -0,0 +1,553 @@ +--- +layout: docs +page_title: Manage agent mTLS encryption +description: >- + Consul supports encrypting all of its network traffic. Learn how to encrypt and authenticate Remote Process Calls (RPCs) between client and server agents with TLS certificates. +--- + +# Manage agent mTLS encryption + +This topic describes mutual TLS (mTLS) encryption between agents in a Consul datacenter. + +This mTLS encryption is distinct from Consul service mesh mTLS encryption. To learn about encrypted mTLS communication between services, refer to [service mesh certificate authority](/consul/docs/connect/ca). + +## Benefits of TLS encryption + +Consul supports TLS certificates that verify the authenticity of servers and clients to secure `RCP`, `gRPC`, and `HTTP` traffic. + +Implementing TLS encryption in Consul datacenters improves your deployment's security in the following ways: + +- **Encrypt data in transit:** TLS encrypts data transmitted between Consul datacenter nodes and user interfaces such as the UI or CLI to ensure that sensitive information is not exposed to unauthorized parties. + +- **Agent authentication:** TLS ensures that only verified parties can communicate with each other using certificates. This security measure prevents unauthorized nodes, services, and users from interacting with your Consul datacenter. + +- **Prevent man-in-the-middle (MITM) attacks:** Without TLS, attackers may intercept and change communications within your Consul deployments. TLS mitigates this risk by encrypting data and verifying the identity of the communication participants. + +- **Comply with security regulations and standards:** Compliance frameworks and regulations like PCI-DSS and HIPAA mandate the encryption of data in transit, which makes TLS a requirement for Consul deployments in regulated environments. + +Mutual TLS (mTLS) requires that all clients and servers have key pairs that are generated by a single Certification Authority (CA). We recommend using a private CA that is not shared with other applications. + +The following parameters in agent configuration files define the agent verification behavior: + +- [`tls.defaults.verify_incoming`](/consul/docs/agent/config/config-files#tls_defaults_verify_incoming) +- [`tls.defaults.verify_outgoing`](/consul/docs/agent/config/config-files#tls_defaults_verify_outgoing) +- [`tls.defaults.verify_server_hostname`](/consul/docs/agent/config/config-files#tls_default_verify_server_hostname) + +## Workflow + +The process to enable TLS encryption in Consul deployments consists of the following steps: + +1. [Initialize the built-in CA](/consul/docs/security/encryption/mtls#initialize-the-built-in-ca) used to sign all certificates. +1. [Create server certificates](/consul/docs/security/encryption/mtls#create-server-certificates) to secure Consul servers. +1. [Configure server agents](/consul/docs/security/encryption/mtls#configure-server-agents) for TLS encryption. +1. [Start server agents](/consul/docs/security/encryption/mtls#start-consul-servers). +1. [Configure Consul client agents](/consul/docs/security/encryption/mtls#configure-client-agents). +1. [Start client agents](/consul/docs/security/encryption/mtls#start-consul-clients). + +## Initialize the built-in CA + +The first step to configure mTLS for Consul is to initialize the certificate authority (CA) that signs the certificates. To prevent unauthorized datacenter access, Consul requires that all certificates are signed by the same CA. We recommend using a private CA because any certificate it signs will be allowed to communicate with the Consul datacenter. + +Consul supports a variety of tools for creating and managing your own CA, like the [PKI secrets engine in Vault](/vault/docs/secrets/pki) or the [Terraform TLS provider](https://registry.terraform.io/providers/hashicorp/tls/latest/docs). Consul also ships with built-in TLS tools to create and manage certificates. + +If you have the Consul binary installed on your path, you can create the CA and certificates, even before you start a Consul server agent. + +```shell-session +$ consul tls ca create -domain=consul +==> Saved consul-agent-ca.pem +==> Saved consul-agent-ca-key.pem +``` + +The command generates two files, `-agent-ca.pem` and `-agent-ca-key.pem`. In this example, the domain used to generate the certificates is the default one, `consul`. + +The CA certificate, `consul-agent-ca.pem`, contains the public key necessary to validate Consul certificates. You must distribute this certificate to every node where a Consul agent runs. + +The CA key, `consul-agent-ca-key.pem`, signs certificates for Consul nodes. You must keep this key private. Possession of this key allows anyone to run Consul as a trusted server or generate new valid certificates for the datacenter. Malicious actors may use this key to obtain access to all Consul data, including ACL tokens. + +## Create server certificates + +To authenticate Consul servers, servers are provided with a special certificate that lists `server..` in the `Common Name` and `Subject Alternative Name` fields. When you enable [`tls.defaults.verify_server_hostname`](/consul/docs/agent/config/config-files#tls_default_verify_server_hostname), only agents that provide this certificate are allowed to boot as a server. + +Without `tls.defaults.verify_server_hostname = true`, an attacker who compromises a Consul client agent can restart the agent as a server to get access to all the data in your datacenter. You must keep the server key private to protect your Consul data. + +The following example creates a server certificate for datacenter `dc1` in the `consul` domain. If your datacenter or domain is different, modify the command to use the appropriate flag values. + +```shell-session +$ consul tls cert create -server -dc=dc1 -domain=consul +==> WARNING: Server Certificates grants authority to become a + server and access all state in the cluster including root keys + and all ACL tokens. Do not distribute them to production hosts + that are not server nodes. Store them as securely as CA keys. +==> Using consul-agent-ca.pem and consul-agent-ca-key.pem +==> Saved dc1-server-consul-0.pem +==> Saved dc1-server-consul-0-key.pem +``` + +Repeat this process on the same node where you created the CA until the number of certificates is equal to the number of servers in the datacenter. You can run the command multiple times in a row, and it automatically increases the certificate and key numbers each time. + +### Federated Consul datacenter + +A federated Consul environment requires the server certificate to include the names of all Consul datacenters that are within the federated environment. + +Use the `-additional-dnsname` flag to provide an additional DNS names for Subject Alternative Names. `localhost` is always included. You can provide this flag multiple times in a single command. + +The following example creates a certificate for a federated environment containing two Consul datacenters named `dc1` and `dc2`. + +```shell-session +$ consul tls cert create -server -dc dc1 -domain=consul -additional-dnsname="server.dc2.consul" +==> WARNING: Server Certificates grants authority to become a + server and access all state in the cluster including root keys + and all ACL tokens. Do not distribute them to production hosts + that are not server nodes. Store them as securely as CA keys. +==> Using consul-agent-ca.pem and consul-agent-ca-key.pem +==> Saved dc1-server-consul-0.pem +==> Saved dc1-server-consul-0-key.pem +``` + +## Configure server agents + +After you generate the server certificates, distribute them to the Consul servers and modify the agent configuration file to include their local location. + +Copy the following files to each Consul server: + +- `consul-agent-ca.pem`: CA public certificate. +- `dc1-server-consul-0.pem`: Consul server node public certificate for first server node in the `dc1` datacenter in the `consul` domain. +- `dc1-server-consul-0-key.pem`: Consul server node private key for first server node in the `dc1` datacenter in the `consul` domain. + +There are two methods for configuring Consul server agents depending on the way you want to distribute certificates to the client agents: + +- The _auto encryption method_ uses the Consul Connect CA to generate client certificates and then automatically distributes them to all Consul clients. +- The _operator method_ requires you to manually generate client certificates and distribute them to each client agent individually. + +We recommend using the auto-encryption method with the built-in CA because Consul can then automatically rotate certificates without requiring operator intervention. + +Use the operator method if you need to use a third-party CA or need more fine-grained control over certificate management. + + + + + + +```hcl +addresses = { + https = "0.0.0.0" +} +ports { + https = 8501 +} +tls { + defaults { + ca_file = "consul-agent-ca.pem" + cert_file = "dc1-server-consul-0.pem" + key_file = "dc1-server-consul-0-key.pem" + verify_incoming = true + verify_outgoing = true + verify_server_hostname = true + } +} +auto_encrypt { + allow_tls = true +} +``` + +```json +{ + "addresses": { + "https" : "0.0.0.0" + }, + "ports": { + "https" : 8501 + }, + "tls" : { + "defaults": { + "ca_file": "consul-agent-ca.pem", + "cert_file": "dc1-server-consul-0.pem", + "key_file": "dc1-server-consul-0-key.pem", + "verify_incoming": true, + "verify_outgoing": true, + "verify_server_hostname": true + } + }, + "auto_encrypt" : { + "allow_tls" : true + } +} +``` + + + + + + + + + +```hcl +addresses = { + https = "0.0.0.0" +} +ports { + https = 8501 +} +tls { + defaults { + ca_file = "consul-agent-ca.pem" + cert_file = "dc1-server-consul-0.pem" + key_file = "dc1-server-consul-0-key.pem" + verify_incoming = true + verify_outgoing = true + verify_server_hostname = true + } +} +``` + +```json +{ + "addresses": { + "https": "0.0.0.0" + }, + "ports": { + "https": 8501 + }, + "tls": { + "defaults": { + "ca_file": "consul-agent-ca.pem", + "cert_file": "dc1-server-consul-0.pem", + "key_file": "dc1-server-consul-0-key.pem", + "verify_incoming": true, + "verify_outgoing": true, + "verify_server_hostname": true + } + } +} +``` + + + + + + + +Consul does not enable TLS for HTTP unless the `https` port is assigned a port number greater than `0`. We recommend using `8501`, the default number for the `https` port, because this default is designed to work automatically with other tools. + +## Start Consul servers + +After you configure your servers, start the Consul process. + +```shell-session +$ systemctl start consul +``` + +Your Consul servers can now communicate with TLS encryption for RPC and consensus. + +## Configure client agents + +Next, configure your client agents with the same method you used to [configure the server agent](#distribute-the-server-certificates) + + + + +The only file you need on the local disk to configure the Consul client agents using auto-encryption is the CA certificate `consul-agent-ca-.pem`. + + + +```hcl +addresses = { + https = "0.0.0.0" +} +ports { + https = 8501 +} +tls { + defaults { + ca_file = "consul-agent-ca.pem" + verify_incoming = true + verify_outgoing = true + verify_server_hostname = true + } +} +auto_encrypt = { + tls = true +} +``` + +```json +{ + "addresses": { + "https": "0.0.0.0" + }, + "ports": { + "https": 8501 + }, + "tls": { + "defaults": { + "ca_file": "consul-agent-ca.pem", + "verify_incoming": true, + "verify_outgoing": true, + "verify_server_hostname": true + } + }, + "auto_encrypt" : { + "tls" : true + } +} +``` + + + + + + + +On the node where you created the CA and server certificates, generate a client certificate with `consul tls cert create -client` command. + +```shell-session +$ consul tls cert create -client -dc=dc1 -domain=consul +==> Using consul-agent-ca.pem and consul-agent-ca-key.pem +==> Saved dc1-client-consul-0.pem +==> Saved dc1-client-consul-0-key.pem +``` + +The client certificate is also signed by the same CA used for the server certificates, but it does not contain `server..` in the `Common Name` and in the `Subject Alternative Name`. Because `verify_server_hostname` is enabled, a compromised client cannot use this certificate to start as a server and access datacenter data. + +Distribute the client certificates and the CA certificate `consul-agent-ca.pem` to every Consul client in the datacenter. Then add them to the client agent configuration. + + + +```hcl +addresses = { + https = "0.0.0.0" +} +ports { + https = 8501 +} +tls { + defaults { + ca_file = "consul-agent-ca.pem" + cert_file = "dc1-client-consul-0.pem" + key_file = "dc1-client-consul-0-key.pem" + verify_incoming = true + verify_outgoing = true + verify_server_hostname = true + } +} +``` + +```json +{ + "addresses": { + "https": "0.0.0.0" + }, + "ports": { + "https": 8501 + }, + "tls": { + "defaults": { + "ca_file": "consul-agent-ca.pem", + "cert_file": "dc1-client-consul-0.pem", + "key_file": "dc1-client-consul-0-key.pem", + "verify_incoming": true, + "verify_outgoing": true, + "verify_server_hostname": true + } + } +} +``` + + + + + + + +## Start Consul clients + +After you configure each client, start the Consul process on the node. + +```shell-session +$ systemctl start consul +``` + +Your client agents now communicate using mutual TLS encryption. + +## Rotate TLS certificates + +To maintain the security offered by TLS encryption, we recommend that you rotate TLS certificates often. + +TLS certificates are part of [Consul's reloadable configuration](/consul/docs/agent/config#reloadable-configuration), so you do not need to restart the Consul agents when you renew certificates. As a result, there is no risk of downtime. + +To rotate certificates for Consul server agents complete the following steps: +1. [Generate new certificates for all server agents](/consul/docs/security/encryption/mtls#create-server-certificates) to replace the old ones. +1. Distribute the new certificates to the server nodes. +1. Reload Consul configuration on each server with the `consul reload` command. + +To rotate certificates for Consul client agents complete the following steps: + + + + +When using the auto-encryption method, Consul automatically rotates the client certificates without operator intervention. + + + + + +1. [Generate new certificates for all client agents](/consul/docs/security/encryption/mtls#configure-client-agents) to replace the old ones. +1. Distribute the new certificates to the client nodes. +1. Reload Consul configuration on all clients with `consul reload` command. + + + + + +## API, CLI, and UI interactions + +The configuration snippets provided in this page are valid to configure complete mTLS for your Consul datacenter. This means that all interfaces require the client to provide a valid certificate in order to communicate with the Consul agent. This is valid for all requests, API, CLI, and UI. + +Since Consul v1.12 it is possible to have different settings for: + + - the HTTP protocol, used for the Consul's REST API, the CLI integration, and the UI + - the RPC protocol, used for internal communications between the Consul agents. + +### Interact with Consul without a client certificate + +If you want to avoid the need to present a valid client certificate every time you interact with Consul using the HTTP API, CLI, or UI, configure Consul to trust all incoming HTTPS connections by setting `tls.https.verify_incoming` to `false`. RPC communications are still mTLS encrypted. + + + +```hcl +addresses = { + https = "0.0.0.0" +} +ports { + https = 8501 + http = -1 +} +tls { + https { + ca_file = "/etc/consul.d/consul-agent-ca.pem" + cert_file = "/etc/consul.d/consul-agent.pem" + key_file = "/etc/consul.d/consul-agent-key.pem" + verify_incoming = false + verify_outgoing = true + } + internal_rpc { + ca_file = "/etc/consul.d/consul-agent-ca.pem" + cert_file = "/etc/consul.d/consul-agent.pem" + key_file = "/etc/consul.d/consul-agent-key.pem" + verify_incoming = true + verify_outgoing = true + verify_server_hostname = true + } +} +``` + +```json +{ + "addresses": { + "https": "0.0.0.0", + "http": "127.0.0.1", + }, + "ports": { + "https": 8501, + "http": 8500 + }, + "tls": { + "https": { + "ca_file": "consul-agent-ca.pem", + "cert_file": "consul-agent.pem", + "key_file": "consul-agent-key.pem", + "verify_incoming": false, + "verify_outgoing": true + }, + "internal_rpc": { + "ca_file": "consul-agent-ca.pem", + "cert_file": "consul-agent.pem", + "key_file": "consul-agent-key.pem", + "verify_incoming": true, + "verify_outgoing": true, + "verify_server_hostname": true + } + } +} +``` + + + + +### Use HTTP for local client interaction + +If you want to avoid the need to present a valid client certificate or to use the CA certificate every time you interact with the local Consul agent, configure Consul to keep the HTTP listener active on the `localhost` interface only and set `tls.https.verify_incoming` to `false`. External requests to the API or UI are still protected by TLS encryption, but requests that originate locally do not need to present a client certificate. RPC communications are still mTLS encrypted. + + + +```hcl +addresses = { + https = "0.0.0.0" + http = "127.0.0.1" +} +ports { + https = 8501 + http = 8500 +} +tls { + https { + ca_file = "/etc/consul.d/consul-agent-ca.pem" + cert_file = "/etc/consul.d/consul-agent.pem" + key_file = "/etc/consul.d/consul-agent-key.pem" + verify_incoming = false + verify_outgoing = true + } + internal_rpc { + ca_file = "/etc/consul.d/consul-agent-ca.pem" + cert_file = "/etc/consul.d/consul-agent.pem" + key_file = "/etc/consul.d/consul-agent-key.pem" + verify_incoming = true + verify_outgoing = true + verify_server_hostname = true + } +} +``` + +```json +{ + "addresses": { + "https": "0.0.0.0", + "http": "127.0.0.1", + }, + "ports": { + "https": 8501, + "http": 8500 + }, + "tls": { + "https": { + "ca_file": "consul-agent-ca.pem", + "cert_file": "consul-agent.pem", + "key_file": "consul-agent-key.pem", + "verify_incoming": false, + "verify_outgoing": true + }, + "internal_rpc": { + "ca_file": "consul-agent-ca.pem", + "cert_file": "consul-agent.pem", + "key_file": "consul-agent-key.pem", + "verify_incoming": true, + "verify_outgoing": true, + "verify_server_hostname": true + } + } +} +``` + + + +## Next steps + +Your agents are now configured with mTLS for encrypted communication. With the auto encryption method, your +client certificates are managed by the server. With the operator method, you distributed all the certificates manually, but have a more flexible configuration. + +Documentation for the commands used in this topic is available at [Consul agent configuration - TLS configuration reference](/consul/docs/agent/config/config-files#tls-configuration-reference) and the [`consul tls` CLI command reference](/consul/commands/tls). + +To learn how to automate TLS certificate generation and rotation, refer to the [Generate mTLS Certificates for Consul with Vault](/consul/tutorials/operate-consul/vault-pki-consul-secure-tls) tutorial. + +To continue securing your Consul deployment, add [gossip encryption](/consul/docs/security/encryption/gossip) and enable [Access Control Lists (ACLs)](/consul/docs/security/acl) with the default deny policy. diff --git a/website/content/docs/security/index.mdx b/website/content/docs/security/index.mdx index 0ec501aabf..1f89ad104d 100644 --- a/website/content/docs/security/index.mdx +++ b/website/content/docs/security/index.mdx @@ -5,11 +5,15 @@ description: >- Security requirements and recommendations for Consul vary depending on workloads and environments. Learn how ACLs and encryption can protect access to and communication within your datacenter. --- +# Consul security + +This topic describes the security requirements and recommendations for a Consul deployment. + ## Security Models Requirements and recommendations for operating a secure Consul deployment may vary drastically depending on your intended workloads, operating system, and environment. You can find detailed information about the various personas, -recommendations, requirements, and threats [here](/consul/docs/security/security-models). +recommendations, requirements, and threats in the [Security Models](/consul/docs/security/security-models) section. ## ACLs @@ -18,6 +22,9 @@ to data and APIs. ## Encryption -The Consul agent supports encrypting all of its network traffic. The exact method of encryption is described on the -[encryption security page](/consul/docs/security/encryption). There are two separate encryption systems, one for gossip -traffic and one for HTTP + RPC. +The Consul agent supports encryption for all of its network traffic. There are two separate encryption systems: + +- A gossip encryption system +- An mTLS encryption system for HTTP and RPC + +For more information about these two different encryption systems, as well as configuration guidance, refer to [Consul encryption](/consul/docs/security/encryption). diff --git a/website/content/docs/security/security-models/core.mdx b/website/content/docs/security/security-models/core.mdx index 1140cdab30..9b0da86089 100644 --- a/website/content/docs/security/security-models/core.mdx +++ b/website/content/docs/security/security-models/core.mdx @@ -5,7 +5,7 @@ description: >- The security model for Consul Core details requirements and recommendations for securing your deployment of Consul. Learn about potential threats and how to protect Consul from malicious actors. --- -## Overview +# Consul security model overview Consul enables automation of network configurations, service discovery, and secure network connectivity across any cloud or runtime. @@ -26,13 +26,17 @@ environment, but the general mechanisms for a secure Consul deployment revolve a [authentication methods](/consul/docs/security/acl/auth-methods) can be used to enable trusted external parties to authorize ACL token creation. +- **Intentions** - If in use, configure service intentions to use a default-deny policy. If L7 intentions are + in use, enable [Mesh request normalization](/consul/docs/connect/config-entries/mesh#request-normalization) + and review your [header match rules](/consul/docs/connect/config-entries/service-intentions#spec-sources-permissions-http-header) to prevent malformed requests from bypassing intentions. + - **Namespaces** - Read and write operations can be scoped to a logical namespace to restrict access to Consul components within a multi-tenant environment. - **Sentinel Policies** - Sentinel policies enable policy-as-code for granular control over the built-in key-value store. -### Personas +## Personas It helps to consider the following types of personas when managing the security requirements of a Consul deployment. The granularity may change depending on your team's requirements. @@ -60,14 +64,14 @@ The granularity may change depending on your team's requirements. be public facing on the internet such as a web server, typically through a load-balancer, or ingress gateway. This is someone who should not have any network access to the Consul agent APIs. -### Secure Configuration +## Secure Configuration Consul's security model is applicable only if all parts of the system are running with a secure configuration; **Consul is not secure-by-default.** Without the following mechanisms enabled in Consul's configuration, it may be possible to abuse access to a cluster. Like all security considerations, administrators must determine what is appropriate for their environment and adapt these configurations accordingly. -#### Requirements +## Requirements - **mTLS** - Mutual authentication of both the TLS server and client x509 certificates prevents internal abuse through unauthorized access to Consul agents within the cluster. @@ -178,6 +182,13 @@ environment and adapt these configurations accordingly. - **🏷 Namespace** - a named, logical scoping of Consul Enterprise resources, typically to enable multi-tenant environments. Consul CE clusters always operate within the "default" namespace. +- **Intentions** - Service intentions control traffic communication between services at the network layer (L4) and + application layer (L7). If in use, we strongly recommend configuring intentions to use a default-deny policy. + When L7 intentions are in use, review your configuration for [Mesh request normalization](/consul/docs/connect/config-entries/mesh#request-normalization) + and use the strictest set of options suitable to your environment. At minimum, we + recommend keeping path normalization enabled, because this default setting prevents requests that do not conform to [RFC 3986]( + https://tools.ietf.org/html/rfc3986#section-6) from bypassing path match rules. + - **Gossip Encryption** - A shared, base64-encoded 32-byte symmetric key is required to [encrypt Serf gossip communication](/consul/tutorials/security/gossip-encryption-secure?utm_source=consul.io&utm_medium=docs) within a cluster using AES GCM. The key size determines which AES encryption types to use; 16, 24, or 32 bytes to select AES-128, AES-192, @@ -212,7 +223,7 @@ environment and adapt these configurations accordingly. commands across the cluster. This is disabled by default since 0.8.0. We recommend leaving it disabled. If enabled, extreme care must be taken to ensure correct ACLs restrict access to execute arbitrary code on the cluster. -#### Recommendations +## Recommendations - **Rotate Credentials** - Using short-lived credentials and rotating them frequently is highly recommended for production environments to limit the blast radius from potentially compromised secrets, and enabling basic auditing. @@ -252,6 +263,10 @@ environment and adapt these configurations accordingly. } ``` +- **Customize Mesh HTTP Request Normalization** - If L7 intentions are in use, we recommend configuring request normalization to + avoid match rule circumvention. Other normalization options, such as dropping or rejecting headers with underscores, + may also be appropriate depending on your requirements. Review the options in the [Mesh configuration entry](/consul/docs/connect/config-entries/mesh#request-normalization) to determine the appropriate settings for your use case. + - **Customize Default Limits** - Consul has a number of builtin features with default connection limits that should be tuned to fit your environment. @@ -307,7 +322,7 @@ environment and adapt these configurations accordingly. } ``` -### Threat Model +## Threat Model The following are parts of the core Consul threat model: @@ -381,7 +396,7 @@ The following are not part of the threat model for client agents: endpoint. If any of this isn't performed correctly, the proxy or service may allow unauthenticated or unauthorized connections. -#### Internal Threats +## Internal Threats - **Operator** - A malicious internal Consul operator with a valid mTLS certificate and ACL token may still be a threat to your cluster in certain situations, especially in multi-team deployments. They may accidentally or intentionally @@ -409,7 +424,7 @@ The following are not part of the threat model for client agents: information. When ACLs and HTTPS are enabled, the gRPC endpoint serving up the xDS service requires (m)TLS and a valid ACL token. -#### External Threats +## External Threats - **Agents** - External access to the Consul agent's various network endpoints should be considered including the gossip, HTTP, RPC, and gRPC ports. Furthermore, access through other services like SSH or `exec` functionality in diff --git a/website/content/docs/security/security-models/index.mdx b/website/content/docs/security/security-models/index.mdx index bcb8f639af..eea5cd6ab2 100644 --- a/website/content/docs/security/security-models/index.mdx +++ b/website/content/docs/security/security-models/index.mdx @@ -5,20 +5,20 @@ description: >- Security models are the set of requirements and recommendations for securely operating a Consul deployment. Learn about security models and how they differ between environments. --- -## Overview +# Security models overview Requirements and recommendations for operating a secure Consul deployment may vary drastically depending on your intended workloads, operating system, and environment. Consul is not secure by default, but can be configured to satisfy the security requirements for a wide-range of use cases from local developer environments without any configuration to container orchestrators in-production with ACL authorization, and mTLS authentication. -### Core +## Core The core Consul product provides several options for enabling encryption, authentication, and authorization controls for a cluster. You can read more about the various personas, recommendations, requirements, and threats [here](/consul/docs/security/security-models/core). -### NIA +## NIA [Network Infrastructure Automation](/consul/docs/nia) (NIA) enables dynamic updates to network infrastructure devices triggered by service changes. Both the core Consul product's configuration and the configuration for the `consul-terraform-sync` diff --git a/website/content/docs/security/security-models/nia.mdx b/website/content/docs/security/security-models/nia.mdx index 0e6de4982b..6770890654 100644 --- a/website/content/docs/security/security-models/nia.mdx +++ b/website/content/docs/security/security-models/nia.mdx @@ -5,7 +5,7 @@ description: >- The NIA security model details requirements and recommendations for securing your Consul-Terraform-Sync (CTS) deployment. Learn about potential threats and how to protect CTS from malicious actors. --- -## Overview +# Network Infrastructure Automation (NIA) overview Network Infrastructure Automation (NIA) enables dynamic updates to network infrastructure devices triggered by service changes using the [Consul Terraform Sync](https://github.com/hashicorp/consul-terraform-sync) (`consul-terraform-sync`) daemon. This daemon uses Consul's catalog to monitor networking information about services along with [Terraform](https://www.terraform.io/)'s provider ecosystem to apply relevant changes to network infrastructure. @@ -13,7 +13,7 @@ The [Secure Consul-Terraform-Sync for Production](/consul/tutorials/network-infr tutorial contains a checklist of best practices to secure your Consul-Terraform-Sync installation for a production environment. -### Personas +## Personas When considering Consul NIA's security model, it helps to think of the following personas. @@ -32,7 +32,7 @@ When considering Consul NIA's security model, it helps to think of the following have no knowledge or access to the daemon's API endpoints, ACL tokens, certificates, or any other piece of the system. -### Secure Configuration +## Secure Configuration Consul NIA's security model is applicable only if all parts of the system are running with a secure configuration; `consul-terraform-sync` is not secure-by-default. Without the following mechanisms enabled in the @@ -40,7 +40,7 @@ daemon's configuration, it may be possible to abuse access to the daemon. Like a considerations, one must determine what concerns are appropriate for their environment, and adapt these security concerns accordingly. -#### Requirements +## Requirements - **Protect Configuration Files and Directories** - A dedicated NIA user and group with limited permissions should be created for production, along with directory, and file permissions appropriately @@ -68,7 +68,7 @@ security concerns accordingly. - **Read** permission for Consul Catalog for all of the selected services to be monitored, and their namespaces. - **Read + Write** permission to update health checks, when using NIA health monitoring. -#### Recommendations +## Recommendations - **Use Dedicated Host** - The NIA daemon will potentially have access to critical secrets for your environment's network infrastructure. Using a hardened, dedicated host, for supporting these sensitive operations is highly recommended. @@ -85,7 +85,7 @@ security concerns accordingly. are configured with the NIA daemon should be audited to ensure you're only using providers from sources that you trust. -### Threat Model +## Threat Model The following are the parts of the NIA threat model: @@ -131,7 +131,7 @@ a production deployment: - **Access to the Consul-Terraform-Sync Binary** - Direct access to the system binary used to start the NIA daemon can allow an attacker to extract sensitive information. -#### Internal Threats +## Internal Threats - **NIA Operator** - Someone with access to the NIA Host, and it's related binaries or configuration files may be a threat to your deployment, especially considering multi-team deployments. They may accidentally or intentionally use a @@ -150,7 +150,7 @@ a production deployment: means. Extra steps to configuring OS, cluster, service, user, directory, and file permissions are essential steps for implementing defense-in-depth within a production environment. -#### External Threats +## External Threats - **Terraform Providers and Modules** - Potentially malicious providers or modules, or any malicious dependencies part of the Terraform ecosystem could cause harm to the network, and may have access to secrets in order to make necessary diff --git a/website/content/docs/services/discovery/dns-cache.mdx b/website/content/docs/services/discovery/dns-cache.mdx index daf8584625..68b531d1b6 100644 --- a/website/content/docs/services/discovery/dns-cache.mdx +++ b/website/content/docs/services/discovery/dns-cache.mdx @@ -1,22 +1,33 @@ --- layout: docs -page_title: Enable dynamic DNS queries +page_title: Scale Consul DNS description: -> You tune Consul DNS query handling to balance between current information and reducing request response time. Learn how to enable caching by modifying TTL values, how to return stale results from the DNS cache, and how to configure Consul for negative response caching. --- -# DNS caching +# Scale Consul DNS This page describes the process to return cached results in response to DNS lookups. Consul agents can use DNS caching to reduce response time, but might provide stale information in the process. -## Introduction +## Scaling techniques -By default, Consul serves all DNS results with a `0` TTL value, which prevents any -caching. This configuration returns the most recent information because each DNS lookup -runs every time. However, this configuration adds latency to each lookup and can potentially -exhaust the query throughput of a datacenter. +By default, Consul serves all DNS results with a `0` TTL value so that it returns the most recent information. When operating at scale, this configuration may result in additional latency because servers must respond to every DNS query. There are several strategies for distributing this burden in your datacenter: -There are several ways you can modify to fine-tune Consul DNS lookup behavior to best suit your network's requirements. +- [Allow Stale Reads](#stale-reads). Allows other servers besides the leader to answer the query rather than forwarding it to the leader. +- [Configure DNS TTLs](#ttl-values). Configure DNS time-to-live (TTL) values for nodes or services so that the DNS subsystem on the container’s operating system can cache responses. Services then resolve DNS queries locally without any external requests. +- [Add Read Replicas](/consul/docs/enterprise/read-scale). Enterprise users can use read replicas, which are additional Consul servers that replicate cluster data without participating in the Raft quorum. +- [Use Cache to prevent server requests](/consul/docs/agent/config/config-files#dns_use_cache). Configure the Consul client to use its agent cache to subscribe to events about a service or node. After you establish the watch, the local Consul client agent can resolve DNS queries about the service or node without querying Consul servers. + +The following table describes the availability of each scaling technique depending on whether you configure Consul to offload DNS requests from the cluster leader to a client agent, dataplane, or DNS proxy. + +| Scaling technique | Supported by client agents | Supported by dataplanes | Supported by Consul DNS Proxy | +| :---------------------------------- | :---------------------------: | :---------------------------: | :---------------------------: | +| Configure DNS TTLs | ✅ | ✅ | ✅ | +| Allow Stale Reads | ✅ | ✅ | ✅ | +| Add Read Replicas | ✅ | ✅ | ✅ | +| Use Cache to prevent server request | ✅ | ❌ | ❌ | + +For more information about considerations for Consul deployments that operate at scale, refer to [Operating Consul at Scale](/consul/docs/architecture/scale). ## TTL values ((#ttl)) diff --git a/website/content/docs/services/discovery/dns-configuration.mdx b/website/content/docs/services/discovery/dns-configuration.mdx index 3ce3205860..5e78494ce7 100644 --- a/website/content/docs/services/discovery/dns-configuration.mdx +++ b/website/content/docs/services/discovery/dns-configuration.mdx @@ -33,6 +33,8 @@ You can specify a list of addresses in the agent's [`recursors`](/consul/docs/ag Nodes that query records outside the `consul.` domain resolve to an upstream DNS. You can specify IP addresses or use `go-sockaddr` templates. Consul resolves IP addresses in the specified order and ignores duplicates. +We recommend that you configure DNS resolvers to point the `consul.` domain towards your Consul DNS servers. Misconfigurations may cause other DNS infrastructure to route queries for the `consul.` domain outside of your network instead, leaking DNS queries to root DNS servers. Refer to [Forward DNS for Consul Service Discovery](/consul/tutorials/networking/dns-forwarding) for instructions. + ### Enable non-Consul queries You enable non-Consul queries to be resolved by setting Consul as the DNS server for a node and providing a [`recursors`](/consul/docs/agent/config/config-files#recursors) configuration. @@ -66,4 +68,4 @@ Responses to pointer record (PTR) queries, such as `.in-addr.arpa.`, always ### Caching -By default, DNS results served by Consul are not cached. Refer to [DNS caching](/consul/docs/services/discovery/dns-cache) for instructions on how to enable caching. \ No newline at end of file +By default, DNS results served by Consul are not cached. Refer to [DNS caching](/consul/docs/services/discovery/dns-cache) for instructions on how to enable caching. diff --git a/website/content/docs/services/discovery/dns-forwarding/enable.mdx b/website/content/docs/services/discovery/dns-forwarding/enable.mdx new file mode 100644 index 0000000000..e7def0b4bc --- /dev/null +++ b/website/content/docs/services/discovery/dns-forwarding/enable.mdx @@ -0,0 +1,420 @@ +--- +layout: docs +page_title: Enable DNS forwarding +description: -> + Learn how to configure different DNS servers to perform DNS forwarding to Consul servers. +--- + +# Enable DNS forwarding + +This page describes the process to enable DNS forwarding to Consul servers. + +You can apply these operations on every node where a Consul agent is running. + +## Requirements + +To enable DNS forwarding, your deployment must have the following: + +- A running Consul server instance +- One or more Consul client nodes with registered services in the Consul catalog +- The `iptables` command available, or one of the following local DNS servers: + - [systemd-resolved](#systemd-resolved) + - [BIND](#bind) + - [Dnsmasq](#dnsmasq) + - [Unbound](#unbound) + - [macOS system resolver](#macos) + +### Network address configuration + +The example configurations on this page assumes Consul's DNS server is listening on the loopback interface on the same node of the local DNS server. + +If Consul is not listening on the loopback IP, replace the references to `localhost` and `120.0.0.1` in the configuration and commands with the appropriate IP address for your environment. + +## systemd-resolved + +[`systemd-resolved`](https://www.freedesktop.org/software/systemd/man/latest/systemd-resolved.service.html) is a system service that provides network name resolution to local applications. It is the default local DNS server for many Linux distributions. + +To configure the `systemd-resolved` service so that it sends `.consul` domain queries to Consul, create a `consul.conf` file located in the `/etc/systemd/resolved.conf.d/` directory. + + + + +Add a `[Resolve]` section to your resolved configuration. + + + +```ini +[Resolve] +DNS=127.0.0.1 +DNSSEC=false +Domains=~consul +``` + + + +### Define port for Consul DNS server + +When using systemd 245 and older, you cannot specify port numbers in the `DNS` configuration field. systemd-resolved only uses port `53`, which is a privileged port. + +When you cannot specify ports for the system's configuration, there are two workarounds: + - [Configure Consul DNS service to listen on port `53`](/consul/docs/agent/config/config-files#dns_port) instead of `8600`. + - Map port `53` to `8600` using `iptables`. + +Binding to port `53` usually requires running Consul as a privileged user or running Linux with the `CAP_NET_BIND_SERVICE` capability. +When using the Consul Docker image, add the following to the environment to allow Consul to use the port: `CONSUL_ALLOW_PRIVILEGED_PORTS=yes`. + +To avoid running Consul as a privileged user, the following `iptables` commands are sufficient to map port `53` to `8600` and redirect DNS queries to Consul. + +```shell-session +# iptables --table nat --append OUTPUT --destination localhost --protocol udp --match udp --dport 53 --jump REDIRECT --to-ports 8600 && \ + iptables --table nat --append OUTPUT --destination localhost --protocol tcp --match tcp --dport 53 --jump REDIRECT --to-ports 8600 +``` + + + + + +Systemd 246 and newer allow you to specify the DNS port directly in the `systemd-resolved` configuration file. +Previous versions of systemd required iptables rules to direct DNS traffic to Consul. + +Add a `[Resolve]` section to your resolved configuration. + + + +```ini +[Resolve] +DNS=127.0.0.1:8600 +DNSSEC=false +Domains=~consul +``` + + + + + + +PTR record queries are still sent to the other configured resolvers, in addition to Consul. + +After creating the resolved configuration, restart `systemd-resolved`. + +```shell-session +# systemctl restart systemd-resolved +``` + +The command produces no output. + +### Validate the systemd-resolved configuration + +Validate that `systemd-resolved` is active. + +```shell-session +# systemctl is-active systemd-resolved +active +``` + +Verify that `systemd-resolved` is configured to forward queries for the `consul` domain to Consul. + +```shell-session +# resolvectl domain +Global: ~consul +Link 2 (eth0): +``` + +Verify that `systemd-resolved` is able to resolve the Consul server address. + +```shell-session +# resolvectl query consul.service.consul +consul.service.consul: 127.0.0.1 + +-- Information acquired via protocol DNS in 6.6ms. +-- Data is authenticated: no +``` + +Confirm that `/etc/resolv.conf` points to the `stub-resolv.conf` file managed by `systemd-resolved`. + +```shell-session +$ ls -l /etc/resolv.conf +lrwxrwxrwx 1 root root 37 Jul 14 10:10 /etc/resolv.conf -> /run/systemd/resolve/stub-resolv.conf +``` + +Confirm that the IP address for `systemd-resolved`'s stub resolver is the configured `nameserver`. + + + +```shell-session +$ cat /etc/resolv.conf +## This file is managed by man:systemd-resolved(8). Do not edit. +## +## This is a dynamic resolv.conf file for connecting local clients to the +## internal DNS stub resolver of systemd-resolved. This file lists all +## configured search domains. +## +## Run "resolvectl status" to see details about the uplink DNS servers +## currently in use. +## +## Third party programs must not access this file directly, but only through the +## symlink at /etc/resolv.conf. To manage man:resolv.conf(5) in a different way, +## replace this symlink by a static file or a different symlink. +## +## See man:systemd-resolved.service(8) for details about the supported modes of +## operation for /etc/resolv.conf. + +nameserver 127.0.0.53 +options edns0 +``` + + + +Ensure that the operating system can resolve DNS queries to the `.consul` domain. + +```shell-session +$ host consul.service.consul +consul.service.consul has address 127.0.0.1 +``` + +### Using any local resolver with systemd + +By default, the local resolver stub in the `resolved.conf` file is configured to listen for UDP and TCP requests at `127.0.0.53:53`. However, you can set the `DNSStubListener` option to `false` so that your system can use any DNS configuration, as long as it loads earlier than `resolved`. + + + +```plaintext +DNSStubListener=false +``` + + + +## Dnsmasq + +Use [dnsmasq](http://www.thekelleys.org.uk/dnsmasq/doc.html) if you have a small network and need a lightweight DNS solution. + + + +If your distribution uses systemd, disable `systemd-resolved` before you follow these steps. + + + +Configure the `dnsmasq.conf` file or a series of files in the `/etc/dnsmasq.d` directory. Add server settings to your configuration file so that requests for the `consul` domain are forwarded to Consul DNS. + + + +```plaintext +# Enable forward lookup of the 'consul' domain: +server=/consul/127.0.0.1#8600 + +# Uncomment and modify as appropriate to enable reverse DNS lookups for +# common netblocks found in RFC 1918, 5735, and 6598: +#rev-server=0.0.0.0/8,127.0.0.1#8600 +#rev-server=10.0.0.0/8,127.0.0.1#8600 +#rev-server=100.64.0.0/10,127.0.0.1#8600 +#rev-server=127.0.0.1/8,127.0.0.1#8600 +#rev-server=169.254.0.0/16,127.0.0.1#8600 +#rev-server=172.16.0.0/12,127.0.0.1#8600 +#rev-server=192.168.0.0/16,127.0.0.1#8600 +#rev-server=224.0.0.0/4,127.0.0.1#8600 +#rev-server=240.0.0.0/4,127.0.0.1#8600 +# Accept DNS queries only from hosts whose address is on a local subnet. +#local-service +# Don't poll /etc/resolv.conf for changes. +#no-poll +# Don't read /etc/resolv.conf. Get upstream servers only from the command +# line or the dnsmasq configuration file (see the "server" directive below). +#no-resolv +# Specify IP address(es) of other DNS servers for queries not handled +# directly by consul. There is normally one 'server' entry set for every +# 'nameserver' parameter found in '/etc/resolv.conf'. See dnsmasq(8)'s +# 'server' configuration option for details. +#server=1.2.3.4 +#server=208.67.222.222 +#server=8.8.8.8 +# Set the size of dnsmasq's cache. The default is 150 names. Setting the +# cache size to zero disables caching. +#cache-size=65536 +``` + + + +Restart the `dnsmasq` service after creating the configuration. + +Refer to [`dnsmasq(8)`](http://www.thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html) for additional configuration settings such as specifying IP addresses for queries not handled directly by Consul. + + +## BIND + +[BIND](https://www.isc.org/bind/) is a robust DNS system. Its most prominent component, `named`, performs both of the main DNS server roles, acts as an authoritative name server for DNS zones, and is a recursive resolver in the network. + + + +If your distribution uses systemd, disable `systemd-resolved` before you follow these steps. + + + +To configure the BIND service to send `.consul` domain queries to Consul: + +1. Create a `named` configuration file with `DNSSEC` disabled. +1. Create a zone configuration file to manage the `.consul` domain. + +### Named configuration file + +Edit the `/etc/named.conf` to configure your BIND instance. Remember to disable `DNSSEC` so that Consul and BIND can communicate. Add an `include` section to include the zone file that you create in the next step. + +The following example shows a BIND configuration with `DNSSEC` disabled. + + + +```plaintext +options { + listen-on port 53 { 127.0.0.1; }; + listen-on-v6 port 53 { ::1; }; + directory "/var/named"; + dump-file "/var/named/data/cache_dump.db"; + statistics-file "/var/named/data/named_stats.txt"; + memstatistics-file "/var/named/data/named_mem_stats.txt"; + allow-query { localhost; }; + recursion yes; + + dnssec-enable no; + dnssec-validation no; + + /* Path to ISC DLV key */ + bindkeys-file "/etc/named.iscdlv.key"; + + managed-keys-directory "/var/named/dynamic"; +}; + +include "/etc/named/consul.conf"; +``` + + + +### Zone configuration file + +Set up a zone for your Consul-managed records in `consul.conf`. + + + +```dns-zone-file +zone "consul" IN { + type forward; + forward only; + forwarders { 127.0.0.1 port 8600; }; +}; +``` + + + +## Unbound + +Use [Unbound](https://www.unbound.net/) when you need a fast and lean DNS resolver for Linux and macOS. + + + +If your distribution uses systemd, disable `systemd-resolved` before you follow these steps. + + + + +The following example demonstrates a configuration for the `consul.conf` file in the `/etc/unbound/unbound.conf.d` directory. + +Add `server` and `stub-zone` settings to your Unbound configuration file. + + + +```plaintext +#Allow insecure queries to local resolvers +server: + do-not-query-localhost: no + domain-insecure: "consul" + +#Add consul as a stub-zone +stub-zone: + name: "consul" + stub-addr: 127.0.0.1@8600 +``` + + + +You may have to add the following line to the bottom of your `/etc/unbound/unbound.conf` file for the new configuration to be included. + + + +```plaintext +include: "/etc/unbound/unbound.conf.d/*.conf" +``` + + + +## iptables + +[iptables](https://www.netfilter.org/projects/iptables/index.html) is a generic firewalling software that allows you to define traffic rules for your system. + +If you do not have a local DNS server on the Consul agent node, use `iptables` to forward DNS requests on port `53` to the Consul agent running on the same machine without using a secondary service. + +This configuration realizes full DNS forwarding, which means that all DNS queries for the host are forwarded to Consul, not just the ones for the `.consul` top level domain. Consul's default configuration resolves only the `.consul` top level domain, so you must set the [`recursors`](/consul/docs/agent/config/config-files#recursors) flag if you want your node to be able to resolve other domains when using `iptables` configuration. + +If you use DNS relay hosts in your network, do not place them on the same host as Consul. The redirects may intercept the traffic. + +### Configure Consul recursors + +Add recursors to your Consul configuration. + + + +```hcl +# DNS recursors +recursors = [ "1.1.1.1" ] +``` + + + +Recursors should not include the `localhost` address because the `iptables` redirects would intercept the requests. + +You can replace the `1.1.1.1` address in the example with another DNS server address. This is suitable for situations where an external DNS +service is already running in your infrastructure and is used as the recursor. + +### Create iptables rules + +After you configure Consul to use a valid recursor, add rules to `iptables` to redirect traffic from port `53` to port `8600`. + +```shell-session +# iptables -t nat -A PREROUTING -p udp -m udp --dport 53 -j REDIRECT --to-ports 8600 \ + iptables -t nat -A PREROUTING -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 8600 \ + iptables -t nat -A OUTPUT -d localhost -p udp -m udp --dport 53 -j REDIRECT --to-ports 8600 \ + iptables -t nat -A OUTPUT -d localhost -p tcp -m tcp --dport 53 -j REDIRECT --to-ports 8600 +``` + +## macOS + +On macOS systems, use the macOS system resolver to point all `.consul` requests to Consul. +The `man 5 resolver` command describes this feature in more detail. + +The following instructions require `sudo` or root access. + +To configure the macOS system resolver to forward DNS queries to Consul, add a resolver entry in the `/etc/resolver/` directory that points at the Consul agent. + +If you do not have this folder, create it. + +```shell-session +# mkdir -p /etc/resolver +``` + +Create a new file `/etc/resolver/consul` with `nameserver` and `port` entries. + + + +```plaintext +nameserver 127.0.0.1 +port 8600 +``` + + + +The configuration informs the macOS resolver daemon to forward all `.consul` TLD requests to `127.0.0.1` on port `8600`. + +## Next steps + +This instructions on this page helped you configure your node to forward DNS requests to Consul. + +To learn more on how to query Consul DNS once forwarding is enabled, refer to [DNS forwarding workflow](/consul/docs/services/discovery/dns-forwarding#workflow). + +For more information on other DNS features and configurations available in Consul, refer to [DNS usage overview](/consul/docs/services/discovery/dns-overview). diff --git a/website/content/docs/services/discovery/dns-forwarding/index.mdx b/website/content/docs/services/discovery/dns-forwarding/index.mdx new file mode 100644 index 0000000000..d47638bdfa --- /dev/null +++ b/website/content/docs/services/discovery/dns-forwarding/index.mdx @@ -0,0 +1,187 @@ +--- +layout: docs +page_title: DNS forwarding +description: -> + Learn how to configure your local DNS servers to perform DNS forwarding to Consul servers. +--- + +# DNS forwarding + +This topic describes the process to configure different DNS servers to enable DNS forwarding to Consul servers. + +You can apply these operations on every node where a Consul agent is running. + +## Introduction + +You deployed a Consul datacenter and want to use Consul DNS interface for name resolution. + +When configured with default values, Consul exposes the DNS interface on port `8600`. By default, DNS is served from port `53`. On most operating systems, this requires elevated privileges. It is also common, for most operating systems, to have a local DNS server already running on port `53`. + +Instead of running Consul with an administrative or root account, you can forward appropriate queries to Consul, running on an unprivileged port, from another DNS server or using port redirect. + +There are two configurations for a node's DNS forwarding behavior: + + - **Conditional DNS forwarding**: the local DNS servers are configured to forward to Consul only queries relative to the `.consul` zone. All other queries are still served via the default DNS server in the node. + - **Full DNS forwarding**: Consul serves all DNS queries and forwards to a remote DNS server the ones outside `.consul` domain. + +### Conditional DNS forwarding + +We recommend the conditional DNS forwarding approach. This configuration lowers the Consul agent's resource consumption by limiting the number of DNS requests it handles. + +![Consul DNS conditional forwarding - Only .consul requests are routed to Consul](/img/consul-dns-conditional-forwarding.png#light-theme-only) +![Consul DNS conditional forwarding - Only .consul requests are routed to Consul](/img/consul-dns-conditional-forwarding-dark.png#dark-theme-only) + +In this configuration, Consul only serves queries relative to the `.consul` domain. There is no unnecessary load on Consul servers to serve queries from different domains. + +This behavior is not enabled by default. + +### Full DNS forwarding + +This approach can be useful in scenarios where the Consul agent's node is allocated limited resources and you want to avoid the overhead of running a local DNS server. In this configuration, Consul serves all DNS queries for all domains and forwards the ones outside the `.consul` domain to one or more configured forwarder servers. + +![Consul DNS forwarding - All requests are routed to Consul](/img/consul-dns-forwarding.png#light-theme-only) +![Consul DNS forwarding - All requests are routed to Consul](/img/consul-dns-forwarding-dark.png#dark-theme-only) + +This behavior is not enabled by default. Consul standard configuration only resolves DNS records inside the `.consul` zone. To enable DNS forwarding, you need to set the [recursors](/consul/docs/agent/config/config-files#recursors) option in your Consul configuration. + +In this scenario, if a Consul DNS reply includes a `CNAME` record pointing outside the `.consul` top level domain, then the DNS reply only includes `CNAME` records by default. + +When `recursors` is set and the upstream resolver is functioning correctly, Consul tries to resolve CNAMEs and include any records (for example, A, AAAA, PTR) for them in its DNS reply. In these scenarios, Consul is used for full DNS forwarding and is able to serve queries for all domains. + +## Workflow + +To use DNS forwarding in Consul deployments, complete the following steps: + +1. Configure the local DNS service to enable DNS forwarding to Consul. Follow the instructions for one of the following services: + + - [systemd-resolved](/consul/docs/services/discovery/dns-forwarding/enable#systemd-resolved) + - [BIND](/consul/docs/services/discovery/dns-forwarding/enable#bind) + - [Dnsmasq](/consul/docs/services/discovery/dns-forwarding/enable#dnsmasq) + - [Unbound](/consul/docs/services/discovery/dns-forwarding/enable#unbound) + - [iptables](/consul/docs/services/discovery/dns-forwarding/enable#iptables) + - [macOS system resolver](/consul/docs/services/discovery/dns-forwarding/enable#macOS) + +1. Query the Consul DNS to confirm that DNS forwarding functions correctly. + + ```shell-session + $ dig consul.service.consul A + + ; <<>> DiG 9.16.48-Debian <<>> consul.service.consul A + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51736 + ;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1 + + ;; OPT PSEUDOSECTION: + ; EDNS: version: 0, flags:; udp: 65494 + ;; QUESTION SECTION: + ;consul.service.consul. IN A + + ;; ANSWER SECTION: + consul.service.consul. 0 IN A 10.0.4.140 + consul.service.consul. 0 IN A 10.0.4.121 + consul.service.consul. 0 IN A 10.0.4.9 + + ;; Query time: 4 msec + ;; SERVER: 127.0.0.53#53(127.0.0.53) + ;; WHEN: Wed Jun 26 20:47:05 UTC 2024 + ;; MSG SIZE rcvd: 98 + + ``` + +1. Optionally, verify reverse DNS. + + ```shell-session + $ dig 140.4.0.10.in-addr.arpa. PTR + + ; <<>> DiG 9.16.48-Debian <<>> 140.4.0.10.in-addr.arpa. PTR + ;; global options: +cmd + ;; Got answer: + ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 35085 + ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1 + + ;; OPT PSEUDOSECTION: + ; EDNS: version: 0, flags:; udp: 65494 + ;; QUESTION SECTION: + ;140.4.0.10.in-addr.arpa. IN PTR + + ;; ANSWER SECTION: + 140.4.0.10.in-addr.arpa. 0 IN PTR consul-server-0.node.dc1.consul. + + ;; Query time: 0 msec + ;; SERVER: 127.0.0.53#53(127.0.0.53) + ;; WHEN: Wed Jun 26 20:47:57 UTC 2024 + ;; MSG SIZE rcvd: 97 + + ``` + + You can use the `short` option for `dig` to only get the node name instead of the full output. + + ```shell-session + $ dig +short -x 10.0.4.140 + consul-server-0.node.dc1.consul. + ``` + +## Troubleshooting + +If your DNS server does not respond but you do get an answer from Consul, turn on your DNS server's query log to check for errors. + +### systemd-resolved + +Enable query logging for `systemd-resolved`: + +```shell-session +# resolvectl log-level debug +``` + +Check query log: + +```shell-session +# journalctl -r -u systemd-resolved +``` + +Disable query logging: + +```shell-session +# resolvectl log-level info +``` + +DNS forwarding may fail if you use the default `systemd-resolved` configuration and attempt to bind to `0.0.0.0`. The default configuration uses a DNS stub that listens for UDP and TCP requests at `127.0.0.53`. As a result, attempting to bind to `127.0.0.53` conflicts with the running stub. You can disable the stub as described in the [Using any local resolver with systemd](/consul/docs/services/discovery/dns-forwarding/enable#using-any-local-resolver-with-systemd) section to troubleshoot this problem. + +### Dnsmasq + +To enable query log refer to [Dnsmasq documentation](https://thekelleys.org.uk/dnsmasq/docs/dnsmasq-man.html). + +In particular, look for the `log-queries` and `log-facility` configuration option. + +When query log is enabled, it is possible to force Dnsmasq to emit a full cache dump using the `SIGUSR1` signal. + +### BIND + +Enable query log: + +```shell-session +$ rndc querylog +``` + +Check logs: + +```shell-session +$ tail -f /var/log/messages +``` + +The log may show errors like this: + + + +```plaintext +error (no valid RRSIG) resolving +error (no valid DS) resolving +``` + + + +This error indicates that `DNSSEC` is not disabled properly. + +If you receive errors about network connections, verify that there are no firewall +or routing problems between the servers running BIND and Consul. diff --git a/website/content/docs/services/discovery/dns-overview.mdx b/website/content/docs/services/discovery/dns-overview.mdx index 34e92e5fe8..d961cc884d 100644 --- a/website/content/docs/services/discovery/dns-overview.mdx +++ b/website/content/docs/services/discovery/dns-overview.mdx @@ -10,6 +10,7 @@ description: >- This topic provides overview information about how to look up Consul nodes and services using the Consul DNS. ## Consul DNS + The Consul DNS is the primary interface for discovering services registered in the Consul catalog. The DNS enables you to look up services and nodes registered with Consul using terminal commands instead of making HTTP API requests to Consul. We recommend using the DNS for service discovery in virtual machine (VM) environments because it removes the need to modify native applications so that they can consume the Consul service discovery APIs. @@ -17,20 +18,37 @@ We recommend using the DNS for service discovery in virtual machine (VM) environ The DNS has several default configurations, but you can customize how the server processes lookups. Refer to [Configure DNS Behaviors](/consul/docs/services/discovery/dns-configuration) for additional information. ### DNS versus native app integration + You can use DNS to reach services registered with Consul or modify your application to natively consume the Consul service discovery HTTP APIs. We recommend using the DNS because it is less invasive. You do not have to modify your application with Consul to retrieve the service’s connection information. Instead, you can use a DNS fully qualified domain (FQDN) that conforms to Consul's lookup format to retrieve the relevant information. -Refer to [ Native App Integration](/consul/docs/connect/native) and its [Go package](/consul/docs/connect/native/go) for additional information. +Refer to [Native App Integration](/consul/docs/connect/native) and its [Go package](/consul/docs/connect/native/go) for additional information. ### DNS versus upstreams + If you are using Consul for service discovery and have not enabled service mesh features, then use the DNS to discover services and nodes in the Consul catalog. If you are using Consul for service mesh on VMs, you can use upstreams or DNS. We recommend using upstreams because you can query services and nodes without modifying the application code or environment variables. Refer to [Upstream Configuration Reference](/consul/docs/connect/proxies/proxy-config-reference#upstream-configuration-reference) for additional information. If you are using Consul on Kubernetes, refer to [the upstreams annotation documentation](/consul/docs/k8s/annotations-and-labels#consul-hashicorp-com-connect-service-upstreams) for additional information. +## Consul DNS on Kubernetes + +Consul on Kubernetes supports approaches to using the Consul DNS in Kubernetes clusters. + +For service discovery operations, refer to [Consul DNS views on Kubernetes](/consul/docs/k8s/dns/views). + +For service mesh operations, refer to [Resolve Consul DNS requests in Kubernetes](/consul/docs/k8s/dns/enable). + +## DNS forwarding + +You can configure your local DNS servers to use Consul. + +Refer to [DNS Forwarding](/consul/docs/services/discovery/dns-forwarding) for additional information. + ## Static queries + Node lookups and service lookups are the fundamental types of static queries. Depending on your use case, you may need to use different query methods and syntaxes to query the DNS for services and nodes. Consul relies on a very specific format for queries to resolve names. Note that all queries are case-sensitive. @@ -38,4 +56,5 @@ Consul relies on a very specific format for queries to resolve names. Note that Refer to [Perform Static DNS Lookups](/consul/docs/services/discovery/dns-static-lookups) for details about how to perform node and service lookups. ## Prepared queries + Prepared queries are configurations that enable you to register complex DNS queries. They provide lookup features that extend Consul's service discovery capabilities, such as filtering by multiple tags and automatically querying remote datacenters for services if no healthy nodes are available in the local datacenter. You can also create prepared query templates that match names using a prefix match, allowing a single template to apply to potentially many services. Refer to [Enable Dynamic DNS Queries](/consul/docs/services/discovery/dns-dynamic-lookups) for additional information. diff --git a/website/content/docs/services/discovery/dns-static-lookups.mdx b/website/content/docs/services/discovery/dns-static-lookups.mdx index 56e9ccc02e..74807c756a 100644 --- a/website/content/docs/services/discovery/dns-static-lookups.mdx +++ b/website/content/docs/services/discovery/dns-static-lookups.mdx @@ -109,7 +109,9 @@ The `datacenter` subdomain is optional. By default, Consul interrogates the quer The `cluster-peer` name is optional, and specifies the [cluster peer](/consul/docs/connect/cluster-peering) whose [exported services](/consul/docs/connect/config-entries/exported-services) should be the target of the query. -The `sameness-group` name is optional, and specifies the [sameness group](/consul/docs/connect/cluster-peering/usage/create-sameness-groups) that should be the target of the query. When Consul receives a DNS request for a service that is tied to a sameness group, it returns service instances from the first healthy member of the sameness group. If the local partition is a member of a sameness group, its service instances take precedence over the members of its sameness group. Optionally, you can include a namespace or admin partition when performing a lookup on a sameness group. Refer to [Service lookups for Consul Enterprise](#service-lookups-for-consul-enterprise) for more information. +The `sameness-group` name is optional, and specifies the [sameness group](/consul/docs/connect/cluster-peering/usage/create-sameness-groups) that should be the target of the query. When Consul receives a DNS request for a service that is a member of a sameness group and the sameness groups is configured with `DefaultForFailover` set to `true`, it returns service instances from the first healthy member of the sameness group. If the local partition is a member of a sameness group, local service instances take precedence over the members of its sameness group. Optionally, you can include a namespace or admin partition when performing a lookup on a sameness group. + +Only sameness groups with `DefaultForFailover` set `true` can be queried through DNS. If `DefaultForFailover` is not true, then Consul DNS returns an error response. Refer to [Service lookups for Consul Enterprise](#service-lookups-for-consul-enterprise) for more information. By default, the lookups query in the `consul` domain. Refer to [Configure DNS Behaviors](/consul/docs/services/discovery/dns-configuration) for information about using alternate domains. diff --git a/website/content/docs/services/usage/register-services-checks.mdx b/website/content/docs/services/usage/register-services-checks.mdx index 07a3f20ad9..3cfe03c852 100644 --- a/website/content/docs/services/usage/register-services-checks.mdx +++ b/website/content/docs/services/usage/register-services-checks.mdx @@ -2,25 +2,25 @@ layout: docs page_title: Register services and health checks description: -> - Learn how to register services and health checks with Consul agents. + Learn how to register services and health checks with Consul agents. --- # Register services and health checks -This topic describes how to register services and health checks with Consul in networks running on virtual machines (VM). Refer to [Define Services](/consul/docs/services/usage/define-services) and [Define Health Checks](/consul/docs/services/usage/checks) for information about how to define services and health checks. +This topic describes how to register services and health checks with Consul in networks running on virtual machines (VM). Refer to [Define Services](/consul/docs/services/usage/define-services) and [Define Health Checks](/consul/docs/services/usage/checks) for information about how to define services and health checks. ## Overview Register services and health checks in VM environments by providing the service definition to a Consul agent. You can use several different methods to register services and health checks. - Start a Consul agent and pass the service definition in the [agent's configuration directory](/consul/docs/agent#configuring-consul-agents). - Reload the a running Consul agent and pass the service definition in the [agent's configuration directory](/consul/docs/agent#configuring-consul-agents). Use this method when implementing changes to an existing service or health check definition. -- Use the [`consul services register` command](/consul/commands/services/register) to register new service and health checks with a running Consul agent. -- Call the [`/agent/service/register`](/consul/api-docs/agent/service#register-service) HTTP API endpoint to to register new service and health checks with a running Consul agent. +- Use the [`consul services register` command](/consul/commands/services/register) to register new service and health checks with a running Consul agent. +- Call the [`/agent/service/register`](/consul/api-docs/agent/service#register-service) HTTP API endpoint to register new service and health checks with a running Consul agent. - Call the [`/agent/check/register`](/consul/api-docs/agent/check#register-check) HTTP API endpoint to register a health check independent from the service. -When a service is registered using the HTTP API endpoint or CLI command, the checks persist in the Consul data folder. If the agent restarts, Consul uses the service and check configurations in the configuration directory to start the services. +When a service is registered using the HTTP API endpoint or CLI command, the checks persist in the Consul data folder. If the agent restarts, Consul uses the service and check configurations in the configuration directory to start the services. -Note that health checks associated with a service are application-level checks. +Note that health checks associated with a service are application-level checks. ## Start an agent We recommend registering services on startup because the service persists if the agent fails. Specify the directory containing the service definition with the `-config-dir` option on startup. When the Consul agent starts, it processes all configurations in the directory and registers any services contained in the configurations. In the following example, the Consul agent starts and loads the configurations contained in the `configs` directory: @@ -48,7 +48,7 @@ Refer to [Consul Agent Service Registration](/consul/commands/services/register) Use the following methods to register services and health checks using the HTTP API. ### Register services -Send a `PUT` request to the `/agent/service/register` API endpoint to dynamically register a service and its associated health checks. To register health checks independently, [call the checks API endpoint](#call-the-checks-http-api-endpoint). +Send a `PUT` request to the `/agent/service/register` API endpoint to dynamically register a service and its associated health checks. To register health checks independently, [call the checks API endpoint](#call-the-checks-http-api-endpoint). The following example request registers the service defined in the `service.json` file. diff --git a/website/content/docs/troubleshoot/faq.mdx b/website/content/docs/troubleshoot/faq.mdx index c57ad30232..5e733f1cd1 100644 --- a/website/content/docs/troubleshoot/faq.mdx +++ b/website/content/docs/troubleshoot/faq.mdx @@ -69,7 +69,7 @@ and [`disable_update_check`](/consul/docs/agent/config/config-files#disable_upda ### Q: Does Consul rely on UDP Broadcast or Multicast? -Consul uses the [Serf](https://www.serf.io) gossip protocol which relies on +Consul uses the [Serf](https://github.com/hashicorp/serf/) gossip protocol which relies on TCP and UDP unicast. Broadcast and Multicast are rarely available in a multi-tenant or cloud network environment. For that reason, Consul and Serf were both designed to avoid any dependence on those capabilities. diff --git a/website/content/docs/upgrading/upgrade-specific.mdx b/website/content/docs/upgrading/upgrade-specific.mdx index 0adb8750c3..c7fec8cb34 100644 --- a/website/content/docs/upgrading/upgrade-specific.mdx +++ b/website/content/docs/upgrading/upgrade-specific.mdx @@ -16,6 +16,11 @@ upgrade flow. ## Consul v1.19.x +### Health endpoint status filtering is now processed on the server side when using client agents +In previous versions of Consul, client agents responded to health queries by requesting all results from the Consul servers and then locally filtering out service nodes with a `critical` status. The client agent also processed the `?passing` parameter by filtering node results locally. This process was not resource efficient in large deployments because large numbers of services and health check results must propagate to many clients before Consul can return a healthy node. + +In this release, the Consul server is responsible for filtering services according to their health status before it sends the data to a client agent. When upgrading the version of Consul your deployment runs to this release, you must upgrade the Consul servers before you upgrade the client agents. We recommend this upgrade order to avoid a scenario where Consul returns results for an unhealthy service because the client agent no longer filters nodes but the servers do not yet understand the `?passing` query argument. + ### Metrics removal In previous versions, Consul emitted redundant state store usage metrics that contained two instances of `consul` in the metric name. As an example, config entry usage counts were emitted as both: @@ -38,6 +43,15 @@ The Kubernetes-only legacy API gateway is superseded by the modern, multi-runtim [API gateway](/consul/docs/connect/config-entries/api-gateway). On Kubernetes, the modern API gateway is associated with the `connectInject.apiGateway` stanza. +### Mesh traffic request path normalization enabled by default + +As of Consul v1.19.3, inbound traffic to mesh proxies will have Envoy request [path normalization](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-normalize-path) applied by default. This should not interfere with the majority of service traffic, but can be disabled if needed by setting `http.incoming.request_normalization.insecure_disable_path_normalization` to `true` in the [global `mesh` configuration entry](/consul/docs/connect/config-entries/mesh#request-normalization). This setting is generally safe to change if not using L7 intentions with path matching. + +## Consul 1.18.x + +### Mesh traffic request path normalization enabled by default + +As of Consul v1.18.5, inbound traffic to mesh proxies will have Envoy request [path normalization](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-normalize-path) applied by default. This should not interfere with the majority of service traffic, but can be disabled if needed by setting `http.incoming.request_normalization.insecure_disable_path_normalization` to `true` in the [global `mesh` configuration entry](/consul/docs/connect/config-entries/mesh#request-normalization). This setting is generally safe to change if not using L7 intentions with path matching. ## Consul 1.17.x @@ -60,6 +74,10 @@ service-defaults are configured in each partition and namespace before upgrading #### ACL tokens with templated policies [ACL templated policies](/consul/docs/security/acl#templated-policies) were added to 1.17.0 to simplify obtaining the right permissions for ACL tokens. When performing a [rolling upgrade](/consul/tutorials/datacenter-operations/upgrade-federated-environment#server-rolling-upgrade) and a version of Consul prior to 1.17.x is presented with a token created Consul v1.17.x or newer that contains templated policies, the templated policies field is not recognized. As a result, the token might not have the expected permissions on the older version of Consul. +### Mesh traffic request path normalization enabled by default + +As of Consul v1.17.8, inbound traffic to mesh proxies will have Envoy request [path normalization](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-normalize-path) applied by default. This should not interfere with the majority of service traffic, but can be disabled if needed by setting `http.incoming.request_normalization.insecure_disable_path_normalization` to `true` in the [global `mesh` configuration entry](/consul/docs/connect/config-entries/mesh#request-normalization). This setting is generally safe to change if not using L7 intentions with path matching. + ## Consul 1.16.x ### Known issues @@ -236,6 +254,10 @@ In Consul v1.15 and higher: +### Mesh traffic request path normalization enabled by default + +As of Consul v1.15.15, inbound traffic to mesh proxies will have Envoy request [path normalization](https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto#envoy-v3-api-field-extensions-filters-network-http-connection-manager-v3-httpconnectionmanager-normalize-path) applied by default. This should not interfere with the majority of service traffic, but can be disabled if needed by setting `http.incoming.request_normalization.insecure_disable_path_normalization` to `true` in the [global `mesh` configuration entry](/consul/docs/connect/config-entries/mesh#request-normalization). This setting is generally safe to change if not using L7 intentions with path matching. + ## Consul 1.14.x ### Service Mesh Compatibility diff --git a/website/content/partials/cli-http-api-partition-options.mdx b/website/content/partials/cli-http-api-partition-options.mdx new file mode 100644 index 0000000000..eb96310a37 --- /dev/null +++ b/website/content/partials/cli-http-api-partition-options.mdx @@ -0,0 +1 @@ +- `-partition=` - Specifies the admin partition to query. If not provided, the partition is inferred from the request's ACL token, or defaults to the `default` partition. diff --git a/website/content/partials/http-api-body-options-partition.mdx b/website/content/partials/http-api-body-options-partition.mdx new file mode 100644 index 0000000000..cf2acd813c --- /dev/null +++ b/website/content/partials/http-api-body-options-partition.mdx @@ -0,0 +1,2 @@ + +- `Partition` `(string: "")` - The admin partition to use. If not provided, the partition is inferred from the request's ACL token, or defaults to the `default` partition. \ No newline at end of file diff --git a/website/content/partials/http-api-query-parms-partition.mdx b/website/content/partials/http-api-query-parms-partition.mdx new file mode 100644 index 0000000000..8d954d86c5 --- /dev/null +++ b/website/content/partials/http-api-query-parms-partition.mdx @@ -0,0 +1 @@ +- `partition` `(string: "")` - The admin partition to use. If not provided, the partition is inferred from the request's ACL token, or defaults to the `default` partition. \ No newline at end of file diff --git a/website/content/partials/http_api_partition_options.mdx b/website/content/partials/http_api_partition_options.mdx deleted file mode 100644 index a290c38201..0000000000 --- a/website/content/partials/http_api_partition_options.mdx +++ /dev/null @@ -1 +0,0 @@ -- `-partition=` - Specifies the partition to query. If not provided, the partition will be inferred from the request's ACL token, or will default to the `default` partition. Partitions are a Consul Enterprise feature added in v1.11.0. diff --git a/website/data/commands-nav-data.json b/website/data/commands-nav-data.json index 967a8e9177..2199c036df 100644 --- a/website/data/commands-nav-data.json +++ b/website/data/commands-nav-data.json @@ -494,15 +494,6 @@ "title": "reload", "path": "reload" }, - { - "title": "resource", - "path": "resource", - "badge": { - "text": "v2", - "type": "outlined", - "color": "neutral" - } - }, { "title": "rtt", "path": "rtt" @@ -543,6 +534,10 @@ "title": "agent", "path": "snapshot/agent" }, + { + "title": "decode", + "path": "snapshot/decode" + }, { "title": "inspect", "path": "snapshot/inspect" diff --git a/website/data/docs-nav-data.json b/website/data/docs-nav-data.json index 7a9b3f6392..204303ef9b 100644 --- a/website/data/docs-nav-data.json +++ b/website/data/docs-nav-data.json @@ -49,7 +49,7 @@ "path": "install" }, { - "title": "Learn HCP Consul", + "title": "Learn HCP Consul Dedicated", "href": "https://developer.hashicorp.com/consul/tutorials/get-started-hcp" }, { @@ -106,23 +106,6 @@ "title": "v1 Catalog", "path": "architecture/catalog" }, - { - "title": "v2 architecture", - "routes": [ - { - "title": "Overview", - "path": "architecture/v2" - }, - { - "title": "Catalog", - "path": "architecture/v2/catalog" - }, - { - "title": "Resource groups", - "path": "architecture/v2/groups" - } - ] - }, { "title": "Improving Consul Resilience", "path": "architecture/improving-consul-resilience" @@ -150,6 +133,10 @@ { "title": "Consul at Scale", "path": "architecture/scale" + }, + { + "title": "Consul Capacity Planning", + "path": "architecture/capacity-planning" } ] }, @@ -166,6 +153,10 @@ { "title": "Consul", "routes": [ + { + "title": "v1.20.x", + "path": "release-notes/consul/v1_20_x" + }, { "title": "v1.19.x", "path": "release-notes/consul/v1_19_x" @@ -215,6 +206,14 @@ { "title": "Consul K8s", "routes": [ + { + "title": "v1.6.x", + "path": "release-notes/consul-k8s/v1_6_x" + }, + { + "title": "v1.5.x", + "path": "release-notes/consul-k8s/v1_5_x" + }, { "title": "v1.4.x", "path": "release-notes/consul-k8s/v1_4_x" @@ -398,6 +397,19 @@ "title": "Configure DNS behavior", "path": "services/discovery/dns-configuration" }, + { + "title": "DNS forwarding", + "routes": [ + { + "title": "Overview", + "path": "services/discovery/dns-forwarding" + }, + { + "title": "Enable DNS forwarding", + "path": "services/discovery/dns-forwarding/enable" + } + ] + }, { "title": "Perform static DNS lookups", "path": "services/discovery/dns-static-lookups" @@ -497,6 +509,10 @@ "title": "Proxy defaults", "path": "connect/config-entries/proxy-defaults" }, + { + "title": "Registration", + "path": "connect/config-entries/registration" + }, { "title": "Sameness Group", "path": "connect/config-entries/sameness-group" @@ -667,6 +683,10 @@ "title": "Access Logs", "path": "connect/observability/access-logs" }, + { + "title": "Monitor service-to-service communication", + "path": "connect/observability/service" + }, { "title": "UI Visualization", "path": "connect/observability/ui-visualization" @@ -1042,7 +1062,16 @@ }, { "title": "Sessions", - "path": "dynamic-app-config/sessions" + "routes": [ + { + "title": "Overview", + "path": "dynamic-app-config/sessions" + }, + { + "title": "Application leader election", + "path": "dynamic-app-config/sessions/application-leader-election" + } + ] }, { "title": "Watches", @@ -1057,6 +1086,23 @@ "title": "Overview", "path": "security" }, + { + "title": "Security Models", + "routes": [ + { + "title": "Overview", + "path": "security/security-models" + }, + { + "title": "Core", + "path": "security/security-models/core" + }, + { + "title": "Network Infrastructure Automation", + "path": "security/security-models/nia" + } + ] + }, { "title": "Access Control (ACLs)", "routes": [ @@ -1167,22 +1213,18 @@ }, { "title": "Encryption", - "path": "security/encryption" - }, - { - "title": "Security Models", "routes": [ { "title": "Overview", - "path": "security/security-models" + "path": "security/encryption" }, { - "title": "Core", - "path": "security/security-models/core" + "title": "Gossip encryption", + "path": "security/encryption/gossip" }, { - "title": "Network Infrastructure Automation", - "path": "security/security-models/nia" + "title": "mTLS", + "path": "security/encryption/mtls" } ] } @@ -1251,8 +1293,21 @@ "path": "agent/config-entries" }, { - "title": "Telemetry", - "path": "agent/telemetry" + "title": "Monitor Consul", + "routes": [ + { + "title": "Agent telemetry", + "path": "agent/monitor/telemetry" + }, + { + "title": "Monitor components", + "path": "agent/monitor/components" + }, + { + "title": "Recommendations", + "path": "agent/monitor/alerts" + } + ] }, { "title": "Sentinel", @@ -1369,6 +1424,10 @@ "title": "Consul Enterprise", "path": "k8s/deployment-configurations/consul-enterprise" }, + { + "title": "Argo Rollouts Progressive Delivery", + "path": "k8s/deployment-configurations/argo-rollouts-configuration" + }, { "title": "Multi-Cluster Federation", "routes": [ @@ -1451,6 +1510,10 @@ { "title": "Datadog metrics", "path": "k8s/deployment-configurations/datadog" + }, + { + "title": "Register external services", + "path": "k8s/deployment-configurations/external-service" } ] }, @@ -1580,48 +1643,6 @@ } ] }, - { - "title": "V2 Catalog API", - "routes": [ - { - "title": "Overview", - "path": "k8s/multiport" - }, - { - "title": "Configure multi-port services", - "path": "k8s/multiport/configure" - }, - { - "title": "Split TCP traffic between multiple ports", - "path": "k8s/multiport/traffic-split" - }, - { - "title": "Reference", - "routes": [ - { - "title": "GRPCRoute resource", - "path": "k8s/multiport/reference/grpcroute" - }, - { - "title": "HTTPRoute resource", - "path": "k8s/multiport/reference/httproute" - }, - { - "title": "ProxyConfiguration resource", - "path": "k8s/multiport/reference/proxyconfiguration" - }, - { - "title": "TCPRoute resource", - "path": "k8s/multiport/reference/tcproute" - }, - { - "title": "TrafficPermissions resource", - "path": "k8s/multiport/reference/trafficpermissions" - } - ] - } - ] - }, { "title": "L7 traffic management", "routes": [ @@ -1662,7 +1683,25 @@ }, { "title": "Consul DNS", - "path": "k8s/dns" + "routes": [ + { + "title": "DNS proxy for service discovery", + "routes": [ + { + "title": "Overview", + "path": "k8s/dns/views" + }, + { + "title": "Enable Consul DNS proxy", + "path": "k8s/dns/views/enable" + } + ] + }, + { + "title": "Enable on dataplanes", + "path": "k8s/dns/enable" + } + ] }, { "title": "Upgrade", @@ -1852,7 +1891,7 @@ "divider": true }, { - "title": "HCP Consul", + "title": "HCP Consul Dedicated", "href": "https://cloud.hashicorp.com/docs/consul" }, { diff --git a/website/public/img/architecture/cluster-peering-diagram-dark.png b/website/public/img/architecture/cluster-peering-diagram-dark.png new file mode 100644 index 0000000000..2c414a7e39 Binary files /dev/null and b/website/public/img/architecture/cluster-peering-diagram-dark.png differ diff --git a/website/public/img/architecture/cluster-peering-diagram-light.png b/website/public/img/architecture/cluster-peering-diagram-light.png new file mode 100644 index 0000000000..3d0a5959e8 Binary files /dev/null and b/website/public/img/architecture/cluster-peering-diagram-light.png differ diff --git a/website/public/img/architecture/cluster-peering-diagram.png b/website/public/img/architecture/cluster-peering-diagram.png new file mode 100644 index 0000000000..66a81dfc21 Binary files /dev/null and b/website/public/img/architecture/cluster-peering-diagram.png differ diff --git a/website/public/img/architecture/consul-redundancy-zones-dark.png b/website/public/img/architecture/consul-redundancy-zones-dark.png new file mode 100644 index 0000000000..c7c5ba065b Binary files /dev/null and b/website/public/img/architecture/consul-redundancy-zones-dark.png differ diff --git a/website/public/img/architecture/consul-redundancy-zones-light.png b/website/public/img/architecture/consul-redundancy-zones-light.png new file mode 100644 index 0000000000..d374f1bb7d Binary files /dev/null and b/website/public/img/architecture/consul-redundancy-zones-light.png differ diff --git a/website/public/img/architecture/consul-singleDC-redundancyzones.png b/website/public/img/architecture/consul-singleDC-redundancyzones.png new file mode 100644 index 0000000000..e57955280f Binary files /dev/null and b/website/public/img/architecture/consul-singleDC-redundancyzones.png differ diff --git a/website/public/img/consul-dns-conditional-forwarding-dark.png b/website/public/img/consul-dns-conditional-forwarding-dark.png new file mode 100644 index 0000000000..b54ed817eb Binary files /dev/null and b/website/public/img/consul-dns-conditional-forwarding-dark.png differ diff --git a/website/public/img/consul-dns-conditional-forwarding.png b/website/public/img/consul-dns-conditional-forwarding.png new file mode 100644 index 0000000000..82ea08a11b Binary files /dev/null and b/website/public/img/consul-dns-conditional-forwarding.png differ diff --git a/website/public/img/consul-dns-forwarding-dark.png b/website/public/img/consul-dns-forwarding-dark.png new file mode 100644 index 0000000000..9ff518397b Binary files /dev/null and b/website/public/img/consul-dns-forwarding-dark.png differ diff --git a/website/public/img/consul-dns-forwarding.png b/website/public/img/consul-dns-forwarding.png new file mode 100644 index 0000000000..bffce6e333 Binary files /dev/null and b/website/public/img/consul-dns-forwarding.png differ diff --git a/website/redirects.js b/website/redirects.js index aabd6e1d78..536a11abad 100644 --- a/website/redirects.js +++ b/website/redirects.js @@ -212,16 +212,6 @@ module.exports = [ destination: '/consul/docs/v1.8.x/agent/config-entries/:slug', permanent: true, }, - { - source: '/consul/docs/k8s/multiport/reference/resource-command/:slug', - destination: '/consul/commands/resource/:slug', - permanent: true, - }, - { - source: '/consul/commands/:version(v1\.(?:8|9|10|11|12|13|14|15|16|17)\.x)/resource/:slug*', - destination: '/consul/docs/:version/k8s/multiport/reference/resource-command/:slug', - permanent: true - }, { source: '/consul/docs/architecture/catalog/v1/:slug', destination: '/consul/docs/architecture/catalog/:slug', @@ -230,26 +220,41 @@ module.exports = [ { source: '/consul/docs/:version(v1\.(?:8|9|10|11|12|13|14|15|16|17)\.x)/architecture/catalog/:slug*', destination: '/consul/docs/:version/architecture/catalog/v1/:slug', - permanent: true - }, - { - source: '/consul/docs/architecture/catalog/v2/:slug', - destination: '/consul/docs/architecture/v2/catalog/:slug', permanent: true, }, - { - source: '/consul/docs/:version(v1\.(?:8|9|10|11|12|13|14|15|16|17)\.x)/architecture/v2/catalog/:slug*', - destination: '/consul/docs/:version/architecture/catalog/v2/:slug', - permanent: true - }, { source: '/consul/docs/nia/network-drivers/terraform-cloud', destination: '/consul/docs/nia/network-drivers/hcp-terraform', - permanent: true + permanent: true, }, { source: '/consul/docs/:version(v1\.(?:8|9|10|11|12|13|14|15|16|17)\.x)/nia/network-drivers/hcp-terraform', destination: '/consul/docs/:version/nia/network-drivers/terraform-cloud', - permanent: true + permanent: true, }, + { + source: '/consul/docs/k8s/multiport/:slug', + destination: '/consul/docs/architecture/catalog#v2-catalog', + permanent: true, + }, + { + source: 'consul/docs/architecture/v2/:slug*', + destination: '/consul/docs/architecture/catalog#v2-catalog', + permanent: true, + }, + { + source: '/consul/commands/resource/:slug', + destination: '/consul/docs/architecture/catalog#v2-catalog', + permanent: true, + }, + { + source: '/consul/docs/k8s/dns', + destination: '/consul/docs/k8s/dns/enable', + permanent: true, + }, + { + source: '/consul/docs/:version(v1\.(?:11|12|13|14|15|16|17|18)\.x)/k8s/dns/enable', + destination: '/consul/docs/:version/k8s/dns', + permanent: true, + } ]